版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2019.07.25 星期四 |
前言
iOS中有關(guān)視圖控件用戶(hù)能看到的都在UIKit框架里面秘蛇,用戶(hù)交互也是通過(guò)UIKit進(jìn)行的。感興趣的參考上面幾篇文章节仿。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用遇绞、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫(huà)的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫(huà)的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類(lèi)似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
UIPresentationController API
首先看下UIPresentationController
的API文檔
NS_ASSUME_NONNULL_BEGIN
@class UIPresentationController;
@protocol UIAdaptivePresentationControllerDelegate <NSObject>
@optional
/* For iOS8.0, the only supported adaptive presentation styles are UIModalPresentationFullScreen and UIModalPresentationOverFullScreen. */
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller;
// Returning UIModalPresentationNone will indicate that an adaptation should not happen.
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection NS_AVAILABLE_IOS(8_3);
/* If this method is not implemented, or returns nil, then the originally presented view controller is used. */
- (nullable UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style;
// If there is no adaptation happening and an original style is used UIModalPresentationNone will be passed as an argument.
- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(nullable id <UIViewControllerTransitionCoordinator>)transitionCoordinator NS_AVAILABLE_IOS(8_3);
@end
NS_CLASS_AVAILABLE_IOS(8_0) @interface UIPresentationController : NSObject <UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
@property(nonatomic, strong, readonly) UIViewController *presentingViewController;
@property(nonatomic, strong, readonly) UIViewController *presentedViewController;
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;
// The view in which a presentation occurs. It is an ancestor of both the presenting and presented view controller's views.
// This view is being passed to the animation controller.
@property(nullable, nonatomic, readonly, strong) UIView *containerView;
@property(nullable, nonatomic, weak) id <UIAdaptivePresentationControllerDelegate> delegate;
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(nullable UIViewController *)presentingViewController NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
// By default this implementation defers to the delegate, if one exists, or returns the current presentation style. UIFormSheetPresentationController, and
// UIPopoverPresentationController override this implementation to return UIModalPresentationStyleFullscreen if the delegate does not provide an
// implementation for adaptivePresentationStyleForPresentationController:
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) UIModalPresentationStyle adaptivePresentationStyle;
#else
- (UIModalPresentationStyle)adaptivePresentationStyle;
#endif
- (UIModalPresentationStyle)adaptivePresentationStyleForTraitCollection:(UITraitCollection *)traitCollection NS_AVAILABLE_IOS(8_3);
- (void)containerViewWillLayoutSubviews;
- (void)containerViewDidLayoutSubviews;
// A view that's going to be animated during the presentation. Must be an ancestor of a presented view controller's view
// or a presented view controller's view itself.
// (Default: presented view controller's view)
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, nullable) UIView *presentedView;
// Position of the presented view in the container view by the end of the presentation transition.
// (Default: container view bounds)
@property(nonatomic, readonly) CGRect frameOfPresentedViewInContainerView;
// By default each new presentation is full screen.
// This behavior can be overriden with the following method to force a current context presentation.
// (Default: YES)
@property(nonatomic, readonly) BOOL shouldPresentInFullscreen;
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the
// presentation transition
// (Default: NO)
@property(nonatomic, readonly) BOOL shouldRemovePresentersView;
#else
- (nullable UIView *)presentedView;
// Position of the presented view in the container view by the end of the presentation transition.
// (Default: container view bounds)
- (CGRect)frameOfPresentedViewInContainerView;
// By default each new presentation is full screen.
// This behavior can be overriden with the following method to force a current context presentation.
// (Default: YES)
- (BOOL)shouldPresentInFullscreen;
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the
// presentation transition
// (Default: NO)
- (BOOL)shouldRemovePresentersView;
#endif
- (void)presentationTransitionWillBegin;
- (void)presentationTransitionDidEnd:(BOOL)completed;
- (void)dismissalTransitionWillBegin;
- (void)dismissalTransitionDidEnd:(BOOL)completed;
// Modifies the trait collection for the presentation controller.
@property(nullable, nonatomic, copy) UITraitCollection *overrideTraitCollection;
@end
NS_ASSUME_NONNULL_END
#else
#import <UIKitCore/UIPresentationController.h>
#endif
開(kāi)始
先看下主要內(nèi)容
主要內(nèi)容:了解如何使用
UIPresentationController
構(gòu)建自定義視圖控制器轉(zhuǎn)換和展示鹉动。
接著看下寫(xiě)作環(huán)境
Swift 5, iOS 13, Xcode 11
從iOS早期開(kāi)始,View Controller
的演示一直是iOS開(kāi)發(fā)人員工具包中不可或缺的一部分宏邮。
您之前可能使用過(guò)present(_:animated:completion :)
但是泽示,如果您和很多開(kāi)發(fā)人員一樣缸血,那么您仍然使用iOS附帶的默認(rèn)轉(zhuǎn)場(chǎng)樣式。
在此UIPresentationController
教程中械筛,您將學(xué)習(xí)如何使用自定義轉(zhuǎn)場(chǎng)和自定義展示樣式呈現(xiàn)視圖控制器捎泻。
您不再局限于全屏或彈出式展示文稿和標(biāo)準(zhǔn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)。你將從一些單調(diào)埋哟,毫無(wú)生氣的視圖控制器演示開(kāi)始笆豁,并將它們變有活力。
當(dāng)你完成時(shí)赤赊,你將學(xué)習(xí)如何:
- 1) 創(chuàng)建
UIPresentationController
子類(lèi)闯狱。 - 2) 使用
UIPresentationController
進(jìn)行光滑的自定義展示。呈現(xiàn)的視圖控制器甚至不必知道它在那里抛计。 - 3) 通過(guò)調(diào)整展示參數(shù)扩氢,為各種控制器重用
UIPresentationController
。 - 4) 進(jìn)行支持應(yīng)用可能遇到的任何屏幕尺寸的自適應(yīng)展示爷辱。
隨著2020年夏季比賽一年之后录豺,一位客戶(hù)讓您創(chuàng)建一個(gè)應(yīng)用程序,以確定競(jìng)爭(zhēng)國(guó)家的獎(jiǎng)牌數(shù)量饭弓。
雖然功能要求非常簡(jiǎn)單双饥,但您的贊助商要求提供一個(gè)相當(dāng)酷的類(lèi)似滑入的轉(zhuǎn)換來(lái)呈現(xiàn)賽事列表。
起初弟断,你感到有些恐慌咏花。但是你意識(shí)到你確實(shí)擁有觸手可及的過(guò)渡構(gòu)建工具,你就會(huì)放心了阀趴。
打開(kāi)已有的創(chuàng)建項(xiàng)目昏翰,花一點(diǎn)時(shí)間熟悉項(xiàng)目和以下元素:
- MainViewController.swift:此項(xiàng)目的主控制器,所有展示都從該控制器開(kāi)始刘急。這將是您要修改的項(xiàng)目中唯一的現(xiàn)有文件棚菊。
- GamesTableViewController.swift:顯示用戶(hù)可以選擇的比賽列表。
- MedalCountViewController.swift:顯示所選體育賽事的獎(jiǎng)牌數(shù)叔汁。
- GamesDataStore.swift:表示項(xiàng)目中的模型層统求。它負(fù)責(zé)創(chuàng)建和存儲(chǔ)模型對(duì)象。
在開(kāi)始更改內(nèi)容之前据块,請(qǐng)構(gòu)建并運(yùn)行應(yīng)用程序以查看其功能码邻。您將看到以下屏幕:
首先,點(diǎn)擊Summer
以顯示夏季菜單另假,Games TableViewController
像屋。
請(qǐng)注意,菜單顯示是默認(rèn)菜單边篮,從底部開(kāi)始己莺。 客戶(hù)希望看到一個(gè)滑動(dòng)的幻燈片展示因苹。
接下來(lái),點(diǎn)擊London 2012
以關(guān)閉菜單并返回主屏幕篇恒。 這一次,您將看到更新的徽標(biāo)凶杖。
最后胁艰,點(diǎn)擊Medal Count
為2012年賽事調(diào)出MedalCountViewController
。
如您所見(jiàn)智蝠,此控制器還使用舊的自下而上的默認(rèn)展示腾么。點(diǎn)按屏幕即可將其關(guān)閉。
現(xiàn)在您已經(jīng)看到了要升級(jí)的應(yīng)用程序杈湾,現(xiàn)在是時(shí)候?qū)⒆⒁饬D(zhuǎn)向UIPresentationController
的一些核心概念和理論解虱。
Core Concepts for iOS Transition
當(dāng)你調(diào)用present(_:animated:completion :)
時(shí),iOS會(huì)做三件事漆撞。
首先殴泰,它實(shí)例化一個(gè)UIPresentationController
。其次浮驳,它將呈現(xiàn)的視圖控制器附加到自身悍汛。最后,它使用內(nèi)置的模態(tài)展示(modal presentation )
樣式之一呈現(xiàn)它至会。
您有能力重寫(xiě)此機(jī)制并為自定義展示提供您自己的UIPresentationController
子類(lèi)离咐。
如果您想在應(yīng)用程序中構(gòu)建精美的展示,必須了解這些關(guān)鍵組件:
- 1) 呈現(xiàn)的視圖控制器具有
transitioning delegate
奉件,其負(fù)責(zé)加載UIPresentationController
以及呈現(xiàn)和dismiss
動(dòng)畫(huà)控制器宵蛀。該代理是一個(gè)符合UIViewControllerTransitioningDelegate
的對(duì)象。 - 2)
UIPresentationController
子類(lèi)是一個(gè)具有許多展示自定義方法的對(duì)象县貌。您將在本教程后面看到其中的一些內(nèi)容术陶。 - 3)
animation controller
對(duì)象負(fù)責(zé)演示和dismiss
動(dòng)畫(huà)。它符合UIViewControllerAnimatedTransitioning
煤痕。請(qǐng)注意瞳别,某些用例需要兩個(gè)控制器:一個(gè)用于展示,一個(gè)用于dismiss
杭攻。 - 4) 展示控制器的代理告訴展示控制器在其特征集合發(fā)生變化時(shí)要做什么祟敛。為了適應(yīng)性,代理必須是符合
UIAdaptivePresentationControllerDelegate
的對(duì)象兆解。
在你深入之前馆铁,這就是你需要知道的!
Creating the Transitioning Delegate
transitioningDelegate
繼承自NSObject
并符合UIViewControllerTransitioningDelegate
锅睛。
UIViewControllerTransitioningDelegate
協(xié)議埠巨,聲明了五種用于管理轉(zhuǎn)換的可選方法历谍。
在本教程中,您將使用其中的三種方法辣垒。
1. Setting Up the Framework
轉(zhuǎn)到File ? New ? File…
望侈,選擇iOS ? Source ? Cocoa Touch Class
,然后單擊Next
勋桶。 將名稱(chēng)設(shè)置為SlideInPresentationManager
脱衙,使其成為NSObject
的子類(lèi)并將語(yǔ)言設(shè)置為Swift
。
單擊Next
例驹,將組設(shè)置為Presentation
捐韩,然后單擊Create
注意:您將
SlideInPresentationManager
聲明為NSObject
,因?yàn)?code>UIViewController的transitioningDelegate
必須符合NSObjectProtocol
鹃锈。
打開(kāi)SlideInPresentationManager.swift
并添加以下擴(kuò)展:
// MARK: - UIViewControllerTransitioningDelegate
extension SlideInPresentationManager: UIViewControllerTransitioningDelegate {
}
在這里荤胁,您使SlideInPresentationManager
符合UIViewControllerTransitioningDelegate
。
在MainViewController
中屎债,您可以看到兩個(gè)賽季的按鈕:左側(cè)為夏季仅政,右側(cè)為冬季。 底部還有Medal Count盆驹。
要使展示適合每個(gè)按鈕的上下文已旧,您將向SlideInPresentationManager
添加方向?qū)傩浴?稍后,您將此屬性傳遞給展示和動(dòng)畫(huà)控制器召娜。
將以下內(nèi)容添加到SlideInPresentationManager.swift
的頂部:
enum PresentationDirection {
case left
case top
case right
case bottom
}
在這里运褪,您聲明一個(gè)簡(jiǎn)單的枚舉來(lái)表示展示的方向。
接下來(lái)玖瘸,將以下屬性添加到SlideInPresentationManager
:
var direction: PresentationDirection = .left
在這里秸讹,您要添加一個(gè)direction
并給它一個(gè)默認(rèn)值left
。
要為您呈現(xiàn)的每個(gè)控制器指定SlideInPresentationManager
的實(shí)例作為transitioningDelegate
雅倒,請(qǐng)打開(kāi)MainViewController.swift
并在dataStore
定義以下添加下面代碼:
lazy var slideInTransitioningDelegate = SlideInPresentationManager()
你可能會(huì)問(wèn)自己為什么要在MainViewController
上添加它作為屬性璃诀。 有兩個(gè)原因:
- 1)
transitioningDelegate
是一個(gè)weak
屬性,因此您必須在某處保留對(duì)該委托的強(qiáng)引用蔑匣。 - 2) 您不希望在展示的控制器本身上保留此引用劣欢,因?yàn)槟赡芟M诓煌恼故緲邮缴现赜盟?確定要使用的展示類(lèi)型現(xiàn)在是演示控制器的任務(wù)。
接下來(lái)裁良,找到prepare(for:sender :)
凿将,并將其替換為以下內(nèi)容:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? GamesTableViewController {
if segue.identifier == "SummerSegue" {
controller.gamesArray = dataStore.allGames.summer
//1
slideInTransitioningDelegate.direction = .left
} else if segue.identifier == "WinterSegue" {
controller.gamesArray = dataStore.allGames.winter
//2
slideInTransitioningDelegate.direction = .right
}
controller.delegate = self
//3
controller.transitioningDelegate = slideInTransitioningDelegate
//4
controller.modalPresentationStyle = .custom
} else if let controller = segue.destination as? MedalCountViewController {
controller.medalWinners = presentedGames?.medalWinners
//5
slideInTransitioningDelegate.direction = .bottom
controller.transitioningDelegate = slideInTransitioningDelegate
controller.modalPresentationStyle = .custom
}
}
以下是您已設(shè)置內(nèi)容的逐節(jié)說(shuō)明:
- 1) 夏季賽季菜單的展示方向是
.left
。 - 2) 冬季賽季菜單的展示方向是
.right
价脾。 - 3) 賽季控制器的
transitioningDelegate
現(xiàn)在是之前聲明的slideInTransitioningDelegate
牧抵。 - 4)
modalPresentationStyle
是.custom
。這使得呈現(xiàn)的控制器期望自定義展示而不是iOS默認(rèn)展示。 - 5)
MedalCountViewController
的呈現(xiàn)方向現(xiàn)在是.bottom
犀变。transitioningDelegate
和modalPresentationStyle
的設(shè)置與步驟3和4中的設(shè)置相同妹孙。
你已經(jīng)到了本節(jié)的最后,并為你下一個(gè)重大事件所需的基礎(chǔ)工作做好準(zhǔn)備:UIPresentationController
子類(lèi)获枝。
Creating the UIPresentationController
坐下來(lái)想象一下:展示的控制器將占據(jù)屏幕的三分之二蠢正,剩下的三分之一將顯示為暗淡。要關(guān)閉控制器省店,只需點(diǎn)擊調(diào)暗部分即可嚣崭。你能想象嗎?
好的萨西,清醒一下,回到項(xiàng)目中旭旭。在這一部分中谎脯,您將處理三個(gè)關(guān)鍵部分:子類(lèi),暗視圖和自定義轉(zhuǎn)換持寄。
1. Creating and Initializing a UIPresentationController Subclass
轉(zhuǎn)到File ? New ? File…
源梭,選擇iOS ? Source ? Cocoa Touch Class
,然后單擊Next
稍味。將名稱(chēng)設(shè)置為SlideInPresentationController
废麻,使其成為UIPresentationController
的子類(lèi),并將語(yǔ)言設(shè)置為Swift
模庐。
單擊Next
烛愧,將組設(shè)置為Presentation
。
單擊Create
以創(chuàng)建新文件掂碱,并在SlideInPresentationController.swift
中的類(lèi)定義中添加以下內(nèi)容:
//1
// MARK: - Properties
private var direction: PresentationDirection
//2
init(presentedViewController: UIViewController,
presenting presentingViewController: UIViewController?,
direction: PresentationDirection) {
self.direction = direction
//3
super.init(presentedViewController: presentedViewController,
presenting: presentingViewController)
}
下面進(jìn)行細(xì)分:
- 1) 聲明
direction
以表示展示的方向怜姿。 - 2) 聲明一個(gè)初始化程序,它接受
presented and presenting view controllers
疼燥,以及展示的方向沧卢。 - 3) 為
UIPresentationController
調(diào)用指定的初始化程序。
2. Setting Up the Dimming View
如前所述醉者,presentation controller
將具有暗淡的背景但狭。 將以下內(nèi)容添加到SlideInPresentationController
,就在direction
上方:
private var dimmingView: UIView!
下面撬即,添加下面的擴(kuò)展
// MARK: - Private
private extension SlideInPresentationController {
func setupDimmingView() {
dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
dimmingView.alpha = 0.0
}
}
在此處創(chuàng)建dimming view
立磁,為自動(dòng)布局準(zhǔn)備并設(shè)置其背景顏色。 請(qǐng)注意剥槐,您尚未將其添加到superview
息罗。 您將在展示轉(zhuǎn)換開(kāi)始時(shí)執(zhí)行此操作,您將在本節(jié)后面看到才沧。
當(dāng)你點(diǎn)擊暗淡的視圖時(shí)迈喉,控制器需要讓自己變得scarce
绍刮。 在setupDimmingView()
下添加以下內(nèi)容以實(shí)現(xiàn)此目的:
@objc func handleTap(recognizer: UITapGestureRecognizer) {
presentingViewController.dismiss(animated: true)
}
在這里,您可以創(chuàng)建一個(gè)UITapGestureRecognizer
處理程序來(lái)dismiss
控制器挨摸。
當(dāng)然孩革,您需要編寫(xiě)UITapGestureRecognizer
,因此將以下內(nèi)容添加到setupDimmingView()
的底部:
let recognizer = UITapGestureRecognizer(
target: self,
action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)
這會(huì)為暗視圖添加一個(gè)點(diǎn)擊手勢(shì)得运,并將其鏈接到剛添加的操作方法膝蜈。
最后,在init(presentedViewController:presenting:direction:)
結(jié)尾處添加對(duì)setupDimmingView()
的調(diào)用:
setupDimmingView()
3. Override Presentation Controller Methods
在開(kāi)始自定義轉(zhuǎn)換之前熔掺,必須覆蓋UIPresentationController
中的四個(gè)方法和屬性饱搏。 默認(rèn)方法不執(zhí)行任何操作,因此無(wú)需調(diào)用super
置逻。
首先推沸,為了平滑過(guò)渡,重寫(xiě)presentationTransitionWillBegin()
以使暗視圖與展示一起淡入券坞。 將以下代碼添加到SlideInPresentationController.swift
內(nèi)的主類(lèi)定義中:
override func presentationTransitionWillBegin() {
guard let dimmingView = dimmingView else {
return
}
// 1
containerView?.insertSubview(dimmingView, at: 0)
// 2
NSLayoutConstraint.activate(
NSLayoutConstraint.constraints(withVisualFormat: "V:|[dimmingView]|",
options: [], metrics: nil, views: ["dimmingView": dimmingView]))
NSLayoutConstraint.activate(
NSLayoutConstraint.constraints(withVisualFormat: "H:|[dimmingView]|",
options: [], metrics: nil, views: ["dimmingView": dimmingView]))
//3
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 1.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 1.0
})
}
這是代碼的作用:
- 1)
UIPresentationController
有一個(gè)名為containerView
的屬性鬓催。 它包含presentation and presented controllers
的視圖層次結(jié)構(gòu)。 此部分是將dimmingView
插入視圖層次結(jié)構(gòu)后面的位置恨锚。 - 2) 接下來(lái)宇驾,將暗視圖約束到容器視圖的邊緣,以使其填滿(mǎn)整個(gè)屏幕猴伶。
- 3)
UIPresentationController
的transitionCoordinator
有一個(gè)非晨紊幔酷的方法來(lái)在轉(zhuǎn)換期間動(dòng)畫(huà)。 在本節(jié)中他挎,您在展示轉(zhuǎn)換時(shí)將暗視圖的alpha
設(shè)置為1.0布卡。
現(xiàn)在,當(dāng)您dismiss
顯示的控制器時(shí)雇盖,您將隱藏暗視圖忿等。 通過(guò)在上一個(gè)重寫(xiě)方法之后添加此代碼來(lái)覆蓋dismissalTransitionWillBegin()
:
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 0.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0.0
})
}
與presentationTransitionWillBegin()
類(lèi)似,隨著dismiss
轉(zhuǎn)場(chǎng)崔挖,您將暗視圖的alpha
設(shè)置為0.0
贸街。 這給出了暗視圖漸弱的效果。
下一個(gè)重寫(xiě)將響應(yīng)presentation controller
的containerView
中的布局更改狸相。 在上一個(gè)重寫(xiě)方法之后添加此代碼:
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
在這里薛匪,您重置presented view
的frame
以適應(yīng)對(duì)containerView frame
的任何更改。
接下來(lái)脓鹃,您將向presentation controller.
提供presented view controller
內(nèi)容的尺寸逸尖。 在上一個(gè)重寫(xiě)方法之后添加此代碼:
override func size(forChildContentContainer container: UIContentContainer,
withParentContainerSize parentSize: CGSize) -> CGSize {
switch direction {
case .left, .right:
return CGSize(width: parentSize.width*(2.0/3.0), height: parentSize.height)
case .bottom, .top:
return CGSize(width: parentSize.width, height: parentSize.height*(2.0/3.0))
}
}
此方法接收content container
和父視圖的大小,然后計(jì)算所呈現(xiàn)內(nèi)容的大小。 在此代碼中娇跟,通過(guò)返回水平寬度的2/3和垂直展示的高度岩齿,將顯示的視圖限制為屏幕的2/3。
除了計(jì)算顯示視圖的大小外苞俘,還需要返回其frame
盹沈。 為此,您將重寫(xiě)frameOfPresentedViewInContainerView
吃谣。 在類(lèi)定義頂部的屬性下面乞封,添加以下內(nèi)容:
override var frameOfPresentedViewInContainerView: CGRect {
//1
var frame: CGRect = .zero
frame.size = size(forChildContentContainer: presentedViewController,
withParentContainerSize: containerView!.bounds.size)
//2
switch direction {
case .right:
frame.origin.x = containerView!.frame.width*(1.0/3.0)
case .bottom:
frame.origin.y = containerView!.frame.height*(1.0/3.0)
default:
frame.origin = .zero
}
return frame
}
逐段查看此代碼:
- 1) 你聲明一個(gè)
frame
,并給它size(forChildContentContainer:withParentContainerSize:)
計(jì)算的size
岗憋。 - 2) 對(duì)于
.right
和.bottom
方向肃晚,您可以通過(guò)移動(dòng)x
原點(diǎn)(.right)
和y
原點(diǎn)(.bottom)
寬度或高度的1/3來(lái)調(diào)整原點(diǎn)。
完成所有重寫(xiě)后仔戈,您已準(zhǔn)備好完成轉(zhuǎn)場(chǎng)的最后一部分关串!
4. Implementing the Presentation Styles
請(qǐng)記住在上一節(jié)中您是如何創(chuàng)建transitioningDelegate
的? 好吧杂穷,它在那里悍缠,但目前做得不多卦绣。 它非常需要一些額外的實(shí)現(xiàn)耐量。
打開(kāi)SlideInPresentationManager.swift
,找到UIViewControllerTransitioningDelegate
擴(kuò)展并向其添加以下內(nèi)容:
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController
) -> UIPresentationController? {
let presentationController = SlideInPresentationController(
presentedViewController: presented,
presenting: presenting,
direction: direction
)
return presentationController
}
在這里滤港,您使用SlideInPresentationManager
的方向?qū)嵗?code>SlideInPresentationController廊蜒。 您將其返回以用于展示。
現(xiàn)在你正在做事溅漾! 構(gòu)建并運(yùn)行應(yīng)用程序山叮。 點(diǎn)擊Summer, Winter
和Medal Count
总放,以查看您喜愛(ài)的新展示風(fēng)格缀踪。
相當(dāng)不同狂巢,你不覺(jué)得嗎本缠? 新的演示樣式看起來(lái)很清晰融虽,但所有呈現(xiàn)的視圖仍然從底部滑入好唯。
客戶(hù)希望Summer and Winter
菜單從側(cè)面滑入勺卢。 你需要彎曲你的動(dòng)畫(huà)能力才能實(shí)現(xiàn)這一點(diǎn)疗杉。
Creating the Animation Controller
要添加自定義動(dòng)畫(huà)轉(zhuǎn)場(chǎng)往衷,您將創(chuàng)建符合UIViewControllerAnimatedTransitioning
的NSObject
子類(lèi)钞翔。
對(duì)于復(fù)雜的動(dòng)畫(huà),您通常會(huì)創(chuàng)建兩個(gè)控制器 - 一個(gè)用于presentation
席舍,一個(gè)用于dismissal
布轿。 在這個(gè)應(yīng)用程序的情況下,dismissal mirrors presentation
,所以你只需要一個(gè)動(dòng)畫(huà)控制器汰扭。
轉(zhuǎn)到File ? New ? File…
稠肘,選擇iOS ? Source ? Cocoa Touch Class
,然后單擊Next
东且。 將名稱(chēng)設(shè)置為SlideInPresentationAnimator
启具,使其成為NSObject
的子類(lèi)并將語(yǔ)言設(shè)置為Swift
。
單擊Next
珊泳,將組設(shè)置為Presentation
鲁冯,然后單擊Create
以創(chuàng)建新文件。 打開(kāi)SlideInPresentationAnimator.swift
并用以下內(nèi)容替換其內(nèi)容:
import UIKit
final class SlideInPresentationAnimator: NSObject {
// 1
// MARK: - Properties
let direction: PresentationDirection
//2
let isPresentation: Bool
//3
// MARK: - Initializers
init(direction: PresentationDirection, isPresentation: Bool) {
self.direction = direction
self.isPresentation = isPresentation
super.init()
}
}
在這里你聲明:
- 1)
direction
說(shuō)明了動(dòng)畫(huà)控制器應(yīng)該為視圖控制器設(shè)置動(dòng)畫(huà)的方向色查。 - 2)
isPresentation
告訴動(dòng)畫(huà)控制器是否展示或關(guān)閉視圖控制器薯演。 - 3) 一個(gè)初始化程序,它接受上面的兩個(gè)聲明值秧了。
接下來(lái)跨扮,通過(guò)添加以下擴(kuò)展名來(lái)添加對(duì)UIViewControllerAnimatedTransitioning
的遵守:
// MARK: - UIViewControllerAnimatedTransitioning
extension SlideInPresentationAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?
) -> TimeInterval {
return 0.3
}
func animateTransition(
using transitionContext: UIViewControllerContextTransitioning
) {}
}
該協(xié)議有兩個(gè)required
的方法 - 一個(gè)用于定義轉(zhuǎn)換所需的時(shí)間(在這種情況下為0.3秒)和一個(gè)用于執(zhí)行動(dòng)畫(huà)的方法。
用以下內(nèi)容替換animateTransition(using :)
:
func animateTransition(
using transitionContext: UIViewControllerContextTransitioning) {
// 1
let key: UITransitionContextViewControllerKey = isPresentation ? .to : .from
guard let controller = transitionContext.viewController(forKey: key)
else { return }
// 2
if isPresentation {
transitionContext.containerView.addSubview(controller.view)
}
// 3
let presentedFrame = transitionContext.finalFrame(for: controller)
var dismissedFrame = presentedFrame
switch direction {
case .left:
dismissedFrame.origin.x = -presentedFrame.width
case .right:
dismissedFrame.origin.x = transitionContext.containerView.frame.size.width
case .top:
dismissedFrame.origin.y = -presentedFrame.height
case .bottom:
dismissedFrame.origin.y = transitionContext.containerView.frame.size.height
}
// 4
let initialFrame = isPresentation ? dismissedFrame : presentedFrame
let finalFrame = isPresentation ? presentedFrame : dismissedFrame
// 5
let animationDuration = transitionDuration(using: transitionContext)
controller.view.frame = initialFrame
UIView.animate(
withDuration: animationDuration,
animations: {
controller.view.frame = finalFrame
}, completion: { finished in
if !self.isPresentation {
controller.view.removeFromSuperview()
}
transitionContext.completeTransition(finished)
})
}
以下是每個(gè)部分的作用:
- 1) 如果這是一個(gè)
presentation
验毡,該方法將詢(xún)問(wèn)與.to
相關(guān)聯(lián)的視圖控制器的transitionContext
衡创。這是您要移動(dòng)的視圖控制器。如果dismissal
晶通,它會(huì)詢(xún)問(wèn)與.from
關(guān)聯(lián)的視圖控制器的transitionContext
璃氢。這是您正在移動(dòng)的視圖控制器。 - 2) 如果操作是
presentation
狮辽,則代碼會(huì)將視圖控制器的視圖添加到視圖層次結(jié)構(gòu)中一也。此代碼使用transitionContext
來(lái)獲取container view
。 - 3) 計(jì)算您正在制作動(dòng)畫(huà)的
frame
喉脖。第一行獲取在展示視圖frame
時(shí)的transitionContext
椰苟。本節(jié)的其余部分解決了在視圖被dismissed
時(shí)計(jì)算視圖frame
的棘手任務(wù)。此部分根據(jù)顯示方向設(shè)置frame
的原點(diǎn)树叽,使其位于可見(jiàn)區(qū)域之外舆蝴。 - 4) 確定轉(zhuǎn)場(chǎng)的初始和最終
frames
。當(dāng)呈現(xiàn)視圖控制器時(shí)题诵,它從dismissed frame
移動(dòng)到presented frame
- 反之亦然洁仗。 - 5) 最后,此方法將視圖從初始
frame
動(dòng)畫(huà)到最終frame
仇轻。如果這是dismissal
京痢,則從視圖層次結(jié)構(gòu)中刪除視圖控制器的視圖。請(qǐng)注意篷店,您在transitionContext
上調(diào)用completeTransition(_ :)
以通知轉(zhuǎn)場(chǎng)已完成祭椰。
1. Wiring Up the Animation Controller
您正處于構(gòu)建轉(zhuǎn)場(chǎng)的最后一步:Hooking up the animation controller
臭家!
打開(kāi)SlideInPresentationManager.swift
并將以下兩個(gè)方法添加到UIViewControllerTransitioningDelegate
擴(kuò)展的末尾:
func animationController(
forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return SlideInPresentationAnimator(direction: direction, isPresentation: true)
}
func animationController(
forDismissed dismissed: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return SlideInPresentationAnimator(direction: direction, isPresentation: false)
}
第一種方法返回動(dòng)畫(huà)控制器以展示視圖控制器。 第二個(gè)返回動(dòng)畫(huà)控制器以關(guān)閉視圖控制器方淤。 兩者都是SlideInPresentationAnimator
的實(shí)例钉赁,但它們具有不同的isPresentation
值。
構(gòu)建并運(yùn)行應(yīng)用程序携茂。 看看那些轉(zhuǎn)場(chǎng)你踩! 點(diǎn)擊Summer
時(shí),您應(yīng)該會(huì)從左側(cè)看到平滑的滑入式動(dòng)畫(huà)讳苦。 對(duì)于Winter
带膜,它來(lái)自右側(cè),Medal Count
來(lái)自底部鸳谜。
你的結(jié)果正是你打算做的膝藕!
只要設(shè)備處于縱向,它就可以很好地工作咐扭。 嘗試旋轉(zhuǎn)到橫屏芭挽。
Adaptivity
好消息是你做了最難的部分! 轉(zhuǎn)場(chǎng)很完美蝗肪。 在本節(jié)中袜爪,您將使效果在所有設(shè)備和兩個(gè)方向上都能很好地工作。
再次構(gòu)建并運(yùn)行應(yīng)用程序薛闪,但這次是在iPhone SE
上運(yùn)行它辛馆。 如果您沒(méi)有實(shí)際設(shè)備,可以使用模擬器逛绵。 嘗試打開(kāi)橫向Summer
菜單怀各。 看到有什么不對(duì)嗎倔韭?
上面看著還不錯(cuò)术浪,但是~~~
但是當(dāng)你試圖展示medal count
時(shí)會(huì)發(fā)生什么? 從菜單中選擇一年寿酌,然后點(diǎn)擊Medal Count
胰苏。 您應(yīng)該看到以下屏幕:
SlideInPresentationController
將視圖限制為屏幕的2/3
,留下很小的空間來(lái)顯示medal count view
醇疼。如果你這樣運(yùn)送應(yīng)用程序硕并,你一定會(huì)聽(tīng)到投訴。
幸運(yùn)的是秧荆,可以進(jìn)行適配倔毙。 iPhone具有縱向的.regular
高度尺寸類(lèi)和橫向的.compact
高度尺寸類(lèi)。您所要做的就是對(duì)展示進(jìn)行一些更改以使用此功能乙濒!
UIPresentationController
有一個(gè)符合UIAdaptivePresentationControllerDelegate
的代理陕赃,它定義了幾種支持自適應(yīng)的方法卵蛉。你馬上就會(huì)使用其中的兩個(gè)。
首先么库,您將使SlideInPresentationManager
成為SlideInPresentationController
的委托傻丝。這是最佳選擇,因?yàn)槟x擇提供的控制器確定應(yīng)用程序是否應(yīng)支持緊湊高度诉儒。
例如葡缰,GamesTableViewController
在compact
高度看起來(lái)正確,因此不需要限制其顯示忱反。但是泛释,您確實(shí)要調(diào)整MedalCountViewController
的展示。
打開(kāi)SlideInPresentationManager.swift
并direction
以下添加:
var disableCompactHeight = false
在這里添加disableCompactHeight
以指示展示是否支持compact
高度温算。
接下來(lái)胁澳,添加符合UIAdaptivePresentationControllerDelegate
的擴(kuò)展,并實(shí)現(xiàn)adaptivePresentationStyle(for:traitCollection :)
米者,如下所示:
// MARK: - UIAdaptivePresentationControllerDelegate
extension SlideInPresentationManager: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(
for controller: UIPresentationController,
traitCollection: UITraitCollection
) -> UIModalPresentationStyle {
if traitCollection.verticalSizeClass == .compact && disableCompactHeight {
return .overFullScreen
} else {
return .none
}
}
}
此方法接受UIPresentationController
和UITraitCollection
并返回所需的UIModalPresentationStyle
韭畸。
接下來(lái),它檢查verticalSizeClass
是否等于.compac
t以及是否為此展示禁用了compact
高度蔓搞。
- 如果是胰丁,則返回
.overFullScreen
的展示樣式。 這樣喂分,呈現(xiàn)的視圖將覆蓋整個(gè)屏幕 - 不僅僅是SlideInPresentationController
中定義的2/3锦庸。 - 如果不是,則返回
.none
蒲祈,繼續(xù)執(zhí)行UIPresentationController
甘萧。
查找presentationController(forPresented:presents:source :)
。
通過(guò)在return
語(yǔ)句上方添加以下行梆掸,將SlideInPresentationManager
設(shè)置為展示控制器的委托:
presentationController.delegate = self
最后扬卷,您將告訴SlideInPresentationManager
何時(shí)禁用compact
高度。
打開(kāi)MainViewController.swift
并找到prepare(for:sender :)
酸钦。 找到segue
的目標(biāo)視圖控制器是GamesTableViewController
的位置怪得,然后將以下行添加到if
塊:
slideInTransitioningDelegate.disableCompactHeight = false
找到segue
的目標(biāo)視圖控制器是MedalCountViewController
的位置,并將以下內(nèi)容添加到if
塊:
slideInTransitioningDelegate.disableCompactHeight = true
構(gòu)建并運(yùn)行應(yīng)用程序卑硫,調(diào)出medal count
并將設(shè)備旋轉(zhuǎn)到橫向徒恋。 該視圖現(xiàn)在應(yīng)該占據(jù)整個(gè)屏幕,如下所示:
這就很好欢伏!
1. Overriding the presented controller
對(duì)于用例入挣,您的視圖只能以常規(guī)高度顯示,也許那里的東西太高了硝拧,不適合compact
的高度径筏。 UIAdaptivePresentationControllerDelegate
可以幫助您风皿。
通過(guò)打開(kāi)SlideInPresentationManager.swift
并將以下方法添加到UIAdaptivePresentationControllerDelegate
擴(kuò)展來(lái)設(shè)置它:
func presentationController(
_ controller: UIPresentationController,
viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle
) -> UIViewController? {
guard case(.overFullScreen) = style else { return nil }
return UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "RotateViewController")
}
此方法接受UIPresentationController
和UIModalPresentationStyle
。 它返回一個(gè)視圖控制器匠璧,它覆蓋要顯示的原始控制器桐款,如果應(yīng)該顯示原始視圖控制器,則返回nil
夷恍。
如果展示樣式為.overFullScreen
魔眨,則會(huì)創(chuàng)建并返回不同的視圖控制器。 這個(gè)新控制器只是一個(gè)簡(jiǎn)單的UIViewController
酿雪,其圖像告訴用戶(hù)將屏幕旋轉(zhuǎn)為縱向遏暴。
構(gòu)建并運(yùn)行應(yīng)用程序,調(diào)出medal count
并將設(shè)備旋轉(zhuǎn)到landscape
指黎。 您應(yīng)該看到以下消息:
將設(shè)備旋轉(zhuǎn)回豎屏以再次查看medal count
朋凉。
恭喜!你已經(jīng)建立了一個(gè)自定義UIPresentationController
醋安。
你已經(jīng)學(xué)習(xí)了很多杂彭!您學(xué)習(xí)了如何自定義和重用UIPresentationController
以從任何方向創(chuàng)建整潔的滑入效果。
您還學(xué)習(xí)了如何調(diào)整各種設(shè)備和兩個(gè)方向的展示吓揪,以及如何處理設(shè)備無(wú)法處理橫向方向的情況亲怠。
有關(guān)UIPresentationController
的更多信息,請(qǐng)查看Apple’s documentation柠辞。
后記
本篇主要講述了基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示团秽,感興趣的給個(gè)贊或者關(guān)注~~~