Swift: 是用Custom Segue还是用Transition动画

用一个很简单的场景做为例子:在storyboard上,你有用UINavigationController串起来两个UIViewController。这两个controller之间要互相跳转,A->B, B->A。跳转的时候默认的那个push来push去的效果你觉得很傻X,所以想换一个效果。比如,不那么二的fade in/out效果。

很多的例子会说写一个cusom的UIStoryboardSegue,然后在这个里面写一个UIView.animationWithDuration来实现这个效果。千万别这么干!从iOS7开始就有一个更加方便简洁的方法可以实现这个效果。

下面就开始介绍这个很牛X的方法。首先创建一个single view的项目。名称可以叫做transitionDemo。各个controller之间的关系是这样的:


一个UINavigationController作为启动controller,串起来一个UITableViewController(root controller)和一个UIViewController。在Prototype Cellsctrl+drag到view controller上,并选择show

下面分别为table view controller创建类TDTableViewController为view controller创建类TDViewController之后分别在storyboard里关联起来。把从table view controller到view controller的segue的identitifer设置为TDViewController

segue

接下来给TDTableViewController添加数据源:

class TDTableViewController: UITableViewController {

var tableViewDataSource: [String]?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        createDataSource()
    }
    
    func createDataSource() {
        if let _ = self.tableViewDataSource {
            return
        }
        
        self.tableViewDataSource = [String]()
        for i in 0..<100 {
            self.tableViewDataSource!.append("item :- [\(i)]")
        }
    }
    //............
}

打开storyboard,在TDViewController里添加一个label,给这个label添加约束,随便是什么约束都可以只要是正确的。然后在controller里添加这个label的outlet并关联。

TDViewController代码中添加数据属性,并在viewDidLoad方法里给这label的text赋值:

class TDViewController: UIViewController {

    @IBOutlet weak var dataLabel: UILabel!
    
    var singleData: String!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.dataLabel.text = self.singleData
    }
}

使用默认的segue,这里现在是show模式,跳转。并从table view controller出传递数据给view controller:

// 使用segue跳转
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        self.dataItem = self.tableViewDataSource![indexPath.row]
        self.performSegueWithIdentifier("TDViewController", sender: nil)
    }

// 传递数据
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier != "TDViewController" {
            return
        }
        
        let destinationController = segue.destinationViewController as! TDViewController
        destinationController.singleData = self.dataItem!
    }

run一把来看看:

Segue和Transition

custom segue

直接用代码把两种方式都实现出来,分别代替上面使用的默认的实现方式。

首先弄一个custom segue。要开发一个custom segue,首先需要创建一个类并继承自UIStoryboardSegue,我们为这个类命名为DetailStoryboardSegue。在这个类中关键就是实现方法perform()

import UIKit

class DetailStoryboardSegue: UIStoryboardSegue {
    override func perform() {
        let sourceView = self.sourceViewController.view // 1
        let destView = self.destinationViewController.view
        
        let window = (UIApplication.sharedApplication().delegate as! AppDelegate).window
        window?.insertSubview(destView, aboveSubview: sourceView) // 2
        
        destView.alpha = 0.0
        
        UIView.animateWithDuration(0.3, animations: { // 3
            destView.alpha = 1.0
        })
    }
}
  1. self.sourceViewControllerself.destinationViewController都是UIStoryboardSegue类本身就有的属性。从A controller跳转到B controller,那么source就是A,destination就是B。我们的fade in/out效果就是通过source和destination controller的view的切换和alpha实现的。
  2. 在window上做source和destination view的切换。把destination view覆盖到source view上。
  3. destination view的alpha在上一步设置为了0,也就是完全透明的。在动画中把destination view的alpha设置回完全不透明,把view呈现在用户面前达到fade in的效果。

实现完成后,在storyboard中把segue的Kind 设置为custom,然后给Class设置为类DetailStoryboardSegue

其实很简单,运行起来看看。


你会看到,运行的结果出了一点问题。之前在正确位置显示的label,在这里居然出现在了屏幕的顶端。这是因为前面TDViewController是用navigation controller的push出来的,屏幕的最顶端有一个navigation bar,所以label的top约束是有效的。而我们的custom segue的切换中并不存在navigation controller的push。而是简单的view的覆盖替换,没有navigation bar。所以labe的top约束失效了,直接被显示在了顶端。

这个错误其实引出了一个问题,但是这里我们暂时不做深入讨论。先看看Transitioning animtion动画是怎么运行的,然后我们讨论这个严肃的问题。

custom transitioning animation

实现自定义的切换动画就需要实现UIViewControllerAnimatedTransitioning这个protocol了。我们自定义一个类DetailTransitioningAnimator来实现这个protocol

这个protocol有两个方法是必须实现的,func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeIntervalfunc animateTransition(transitionContext: UIViewControllerContextTransitioning)。来看代码:

import UIKit

class DetailTransitioningAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let durationTimeInterval: NSTimeInterval
    // 1
    init(duration: NSTimeInterval){
        durationTimeInterval = duration
    }
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return self.durationTimeInterval
    }
    //2
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView()
        let sourceController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
        let destController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
        
        
        let sourceView = sourceController!.view
        let destView = destController!.view
        
        containerView?.addSubview(destView) // 3
        
        destView.alpha = 0.0
        
        UIView.animateWithDuration(0.3, animations: {
            destView.alpha = 1.0
        }, completion: {completed in
            let cancelled = transitionContext.transitionWasCancelled()
            transitionContext.completeTransition(!cancelled)
        })
    }
    
    func animationEnded(transitionCompleted: Bool) {
        
    }
}
  1. 这个init方法不是必需的,但是为了可以自定义动画的执行时间添加这个构造方法。
  2. transitioning动画的执行方法。在这个方法里实现我们在之前的custom segue里实现的效果。
  3. 注意这里,用的是let containerView = transitionContext.containerView()得到的container view。而不是之前用到的window。

下面把transitioning动画应用到代码中。在使用的时候需要实现UINavigationControllerDelegate。创建一个类NavigationControllerDelegate来实现这个protocol。

import UIKit

class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DetailTransitioningAnimator(duration: 0.3)
    }
}

这里没有太多需要讲的。只要知道是这个方法就可以了。

下面在storyboard中把类NavigationControllerDelegate应用在UINavigationController中。

  1. 把object这个东西拖动到项目中唯一存在的navigation controller上。
  2. 给刚刚的object设置custom class为类NavigationControllerDelegate

    只要关注右侧的Class的设置就可以。
  3. 给navigation controller的代理设置为刚刚拖动上去的delegate。


对上面的custom segue代码去掉,并稍作修改之后,运行起来。



木有问题,上面出现的错误也没有了。

这个问题不重要,但是还是要讨论一下:为什么custom segue会出现问题,而transitoning动画就没有问题呢?这里是因为实际上transitioning动画是在navigation controller的基础上改变的。Transitioning动画只是修改了navigation controller的push动画,改成了fade in的效果,而navigation controller的其他机制没有改动。custom segue则完全是两个view之间的动画。那么,这里就留下一个问题由读者去修改上面的custom segue代码来让这个navigation bar 出现出来。

但是什么情况下用custom segue,什么情况下用transition动画呢?Transitioning动画更加的灵活,不像custom segue是在storyboard里定死的。你可以根据不同的情况设定你自己想要的动画。

custom segue就是用来调用一些如:presentViewControllerdismissViewController之类的方法的。这些方法调用的时候两个view controller之间的转化动画则使用上面的方法来做。他们的职责是不同的。

最后补充一点。也是一个发挥custom segue的作用的地方。在多个storyboard的情况下可以使用custom segue。步骤:(假设你已经创建了另外一个storyboard,叫做Another.storyboard

  1. 拖一个storyboard reference到你现在的storyboard中。
  2. 点选这个storyboard reference,并在右侧工具栏的Storyboard下填写另外一个storyboard的名字,这里是Another.storyboard
  3. ctrl+drag,从一个view controller中的按钮等view连接到刚刚添加的storyboard reference上。(确保你的另外一个storyboard的view controller已经设置为默认启动)
  4. 如果你要启动的是另外一个storyboard的某一个特定的view controller,那么就可以写一个custom segue了。在这个custom segue中初始化出另外一个storyboard,从中获取到你要启动的view controller,然后在custom segue的perform方法中完成controller跳转的最后一步。

all code here

to be continued。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • /* UIViewController is a generic controller base class th...
    DanDanC阅读 1,789评论 0 2
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 不管任何App,对于跳转这个最基础的动画都是必须的,而iOS的转场动画不仅只是动画,还涉及层级、跳转限制、携带参数...
    HuberyQing阅读 2,069评论 0 0
  • 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上...
    伯恩的遗产阅读 53,783评论 37 379
  • 我叫简辞,遇见千花是在江苏的锦溪。 锦溪是我一直很想去的小城,夹岸桃李纷披,满溪跃金,灿烂若锦。 ...
    霓裳衣画阅读 229评论 0 0