UINavigationController就一本書一樣,而UIViewController就是它的每一頁碱屁,我們可以一頁一頁的翻刁绒,也可以瞬間跳到某一頁,或者回歸的過去的某一頁坊罢。
構(gòu)成
我們首先要有一個rootViewController续担,裝進UINavigationController。
例如在AppDelegate的didFinishLaunchingWithOptions中
let navVC = UINavigationController(rootViewController: ViewController())
window?.rootViewController = navVC
window?.backgroundColor = UIColor.gray
window?.makeKeyAndVisible()
用ViewController()
創(chuàng)建一個UIViewController來初始化UINavigationController
, 這樣ViewController()
作為導航欄的第一頁顯示出來活孩。
UINavigationController
的組成物遇, 引用一下官網(wǎng)的
UI元素除了UIViewController的數(shù)組外,還有navigationBar憾儒,toolbar询兴。
接下里我們豐富一下,將其UI元素全部用上起趾。
在ViewController
的viewDidLoad中加入代碼
// 顯示toolbar
self.navigationController?.isToolbarHidden = false
self.setToolbarItems([UIBarButtonItem(barButtonSystemItem: .camera, target: nil, action: nil), UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)], animated: false)
// 設置navigationBar的title
self.title = "root view"
// 設置navigationBar按鈕
self.navigationItem.setLeftBarButton(UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil), animated: false)
self.navigationItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil), animated: false)
注意:toolbar的items的設置诗舰,是與UIViewController關聯(lián)的,不要UINavigationController上setItems是不會顯示的
跳轉(zhuǎn)
Push
push就是將VC一個一個疊上去训裆,默認行為是從右側(cè)進入
self.navigationItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.pushAction(sender:))), animated: false)
func pushAction(sender:UIBarButtonItem) {
self.navigationController?.pushViewController(ViewController2(), animated: true)
}
我們將done
按鈕加入push動作, 點擊done眶根,從右到左進入,這時候系統(tǒng)會添加一個navigationBar item边琉,用于pop到下層頁面属百,title就是下層頁面設置的title
push對應的返回接口是pop
self.navigationController?.popViewController(animated: true)
Present
說道跳轉(zhuǎn),這里也提一下present
变姨,present
跳到另外一個頁面族扰,默認行為是從下而上,除了表現(xiàn)上與push不一樣
- 不是UINavigationController的管理范疇定欧,UIViewController可直接
present
到其他頁面 - 同一個
UIViewController
不可以present
兩次渔呵,但present起來的vc是可以再次present
的 -
present
類似模態(tài)窗口,在這個窗口dissmiss
前忧额,交互只能在這個窗口做厘肮,之前的頁面不可交互和變化, 就仿佛你放下一本書,就拿起另外一本書睦番,不能同時看兩本書
present對應的消失接口是dissmiss
self.dismiss(animated: true, completion: nil)
Show
8.0以后提供了show方法类茂,這個是根據(jù)viewController所處容器耍属,使用容器的跳轉(zhuǎn)行為,例如viewController所處容器為navigationController(viewController的容器還有splitViewController)巩检,就和push一樣
open func show(_ vc: UIViewController, sender: Any?)
自定義跳轉(zhuǎn)方式(轉(zhuǎn)場動畫)
自定義轉(zhuǎn)場動畫厚骗,需要制定兩個因素
- 從哪個viewController跳轉(zhuǎn)到哪個viewController
- 動畫內(nèi)容
自定義 Present
我們嘗試改寫present
的動畫效果,它默認是由下而上兢哭,改成自上而下
func pushAction(sender:UIBarButtonItem) {
let vc = ViewController2()
vc.transitioningDelegate = self // 實現(xiàn)UIViewControllerTransitioningDelegate協(xié)議
self.present(vc, animated: true, completion: nil)
}
實現(xiàn)UIViewControllerTransitioningDelegate
協(xié)議, 下面就是用來實現(xiàn)领舰,present和dismiss動畫的
@available(iOS 2.0, *)
optional public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
@available(iOS 2.0, *)
optional public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
這兩個接口需要返回id<UIViewControllerAnimatedTransitioning>
UIViewControllerAnimatedTransitioning
// 轉(zhuǎn)場時間
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// 動畫
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
接下來需要創(chuàng)建一個id<UIViewControllerAnimatedTransitioning>
的類,
class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let containerView = transitionContext.containerView
// 將跳轉(zhuǎn)后的頁面迟螺,設置到屏幕上方
toViewController?.view.frame = (fromViewController?.view.frame)!
toViewController?.view.frame.origin.y -= (toViewController?.view.frame.height)!
// 添加到轉(zhuǎn)場動畫容器上冲秽,
containerView.addSubview((toViewController?.view)!)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { () -> Void in
// 回歸的屏幕位置
toViewController?.view.frame = (fromViewController?.view.frame)!
}, completion: { (complete:Bool) -> Void in
transitionContext.completeTransition(true)
})
}
}
經(jīng)過我打印地址研究,得出以下結(jié)論
- containerView是做動畫用的容器, 初始狀態(tài)是將跳轉(zhuǎn)前頁面(較為頂層的一個view矩父,如果vc在navigationController中就是naviController的view)添加為自己的subview
- fromViewController不是跳轉(zhuǎn)前vc, 只是將跳轉(zhuǎn)前頁面添加為自己的subview锉桑,用來做舊頁面的退出動畫
- toViewController則是跳轉(zhuǎn)后vc,做新頁面的登臺動畫
- transitionDuration時間是提供給系統(tǒng)的窍株,提供跳轉(zhuǎn)時系統(tǒng)的默認表現(xiàn)民轴,自定義animate的時間最好和其保持一致
- containerView臨時產(chǎn)生,在動畫結(jié)束后球订,從UI上撤掉后裸,但view的改變會遺留下來
注意:transitionContext.completeTransition(true)
是必須要調(diào)用的,否則系統(tǒng)認為轉(zhuǎn)場為完成冒滩,第二頁不能進行任何交互
自定義 Dismiss
dismiss 與 present 類似微驶,只是fromViewController變成了第二頁,toViewController變成了第一頁, 記住上述的那幾條結(jié)論來理解containerView.addSubview
class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let containerView = transitionContext.containerView
containerView.addSubview((toViewController?.view)!)
containerView.addSubview((fromViewController?.view)!)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { () -> Void in
fromViewController?.view.alpha = 0.0
}, completion: { (complete:Bool) -> Void in
transitionContext.completeTransition(true)
})
}
}
自定義 Push
只是掛接delegate的位置不同
// 自定義navigationController動畫
self.navigationController?.delegate = self
實現(xiàn)同樣和上面一樣旦部,返回id<UIViewControllerAnimatedTransitioning>
, 為了簡單祈搜,我們做之前present
和dismiss
一樣的動畫
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
return PresentAnimator();
} else {
return DismissAnimator();
}
}
交互式動畫
最后在說一個present
的交互動畫
交互式動畫就隨手勢做的動畫,手拖的距離士八,來決定動畫執(zhí)行的進度
需要再實現(xiàn)UIViewControllerTransitioningDelegate
協(xié)議中的交互式接口容燕,要求返回一個id<UIViewControllerInteractiveTransitioning>
optional public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
optional public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
這個交互式動畫,依賴于之前設置的動畫方式婚度,只是用來設置百分比蘸秘,這個百分比需要你自己不斷提供給他,一般用手勢來不斷調(diào)用id<UIViewControllerInteractiveTransitioning>
的update接口蝗茁,我們?yōu)榱撕唵未茁玻胻imer來模擬
創(chuàng)建一個id<UIViewControllerInteractiveTransitioning>
class InteractiveAnimator: UIPercentDrivenInteractiveTransition {
}
viewController中加入屬性
var interactiveAnimator:InteractiveAnimator = InteractiveAnimator()
var timer:Timer? = nil
var times:CGFloat = 0;
修改接口
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timeUpdate), userInfo: nil, repeats: true);
return interactiveAnimator
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timeUpdate), userInfo: nil, repeats: true);
return interactiveAnimator
}
func timeUpdate() {
times += 1
if times <= 5 {
let per:CGFloat = times/5
interactiveAnimator.update(per)
} else {
timer?.invalidate()
interactiveAnimator.finish()
}
}
timeUpdate的會該5次百分比,這個動畫也分5下完成, 每秒變化一次哮翘。timeUpdate這是里timer來調(diào)用的颈嚼,你可以用任意的交互方式來調(diào)用,百分比自己來計算
demo 地址 記得 pod install