廢話不多說勾拉,本教程將教你實(shí)現(xiàn)以上樣式的密碼框輸入視圖炒考。要點(diǎn)如下:
- 該密碼輸入頁面是以自定義模態(tài)視圖的轉(zhuǎn)場(chǎng)動(dòng)畫的方式實(shí)現(xiàn)的宰翅。通過本教程噩死,你會(huì)對(duì)
UIPresentationController
颤难、<UIViewControllerTransitioningDelegate>
、<UIViewControllerAnimatedTransitioning>
等幾個(gè)類有所了解已维; - 受益于站在巨人的肩膀上的優(yōu)勢(shì)行嗤,本教程中使用了
Masonry
框架實(shí)現(xiàn)相關(guān)視圖元素的自動(dòng)布局,還使用了YYKit
框架中的部分工具類方法垛耳。
自定義 UIPresentationController 子類對(duì)象
首先栅屏,你需要自定義一個(gè) UIPresentationController
子類對(duì)象。
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface HQLVerticalPresentationController : UIPresentationController <UIViewControllerTransitioningDelegate>
@end
NS_ASSUME_NONNULL_END
等一下堂鲜,UIPresentationController
是何許人也栈雳?
UIPresentationController
對(duì)象為所呈現(xiàn)的視圖控制器提供高級(jí)視圖轉(zhuǎn)換管理功能,我們通過它實(shí)現(xiàn)視圖控制器之間的轉(zhuǎn)場(chǎng)動(dòng)畫缔莲。
通過模態(tài)方式呈現(xiàn)視圖控制器的常用方法是:
UIViewController *viewControllerA = [[UIViewController alloc] init];
UIViewController *viewControllerB = [[UIViewController alloc] init];
[viewControllerA presentViewController:viewControllerB animated:YES completion:NULL];
當(dāng)通過 presentViewController:animated:completion:
方式讓視圖控制器 A 以模態(tài)方式呈現(xiàn)視圖控制器 B 時(shí)哥纫,兩個(gè)視圖控制器之間的層次結(jié)構(gòu)是這樣的:
視圖控制器 A 被稱之為 presenting View Controller。
視圖控制器 B 被稱之為 presented View Controller痴奏。
而 UIPresentationController
類在其中充當(dāng)著協(xié)調(diào)器的作用蛀骇,它并不會(huì)顯示在視圖層次結(jié)構(gòu)中,借用網(wǎng)友的一張圖表示 UIPresentationController
在其中的位置:
?? 為方便起見读拆,本教程中會(huì)把
UIPresentationController
命名為「呈現(xiàn)控制器」擅憔。
我們需要?jiǎng)?chuàng)建一個(gè)繼承 UIPresentationController
的子類,負(fù)責(zé)「被呈現(xiàn)」及「負(fù)責(zé)呈現(xiàn)」的控制器以外的 controller, 比如顯示帶漸變效果的黑色半透明背景視圖建椰,還可以在其中創(chuàng)建帶有陰影或者圓角的中間層視圖雕欺,本教程中會(huì)教大家如何在密碼輸入框的左上角和右上角設(shè)置圓角。
在此步驟中棉姐,起碼需要重寫以下 5 個(gè)方法:
-
- (void)presentationTransitionWillBegin;
屠列, 跳轉(zhuǎn)將要開始時(shí)執(zhí)行。 -
- (void)presentationTransitionDidEnd:(BOOL)completed;
伞矩,跳轉(zhuǎn)已經(jīng)結(jié)束時(shí)執(zhí)行笛洛。 -
- (void)dismissalTransitionWillBegin;
,返回將要開始時(shí)執(zhí)行乃坤。 -
- (void)dismissalTransitionDidEnd:(BOOL)completed;
苛让,返回已經(jīng)結(jié)束時(shí)執(zhí)行沟蔑。 -
frameOfPresentedViewInContainerView
,跳轉(zhuǎn)完成后狱杰,被呈現(xiàn)視圖在容器視圖中的位置瘦材。
其中,frameOfPresentedViewInContainerView
是一個(gè)只讀屬性仿畸,用于在過渡動(dòng)畫呈現(xiàn)結(jié)束時(shí)食棕,設(shè)置被呈現(xiàn)的視圖在容器視圖中的位置。
@property(nonatomic, readonly) CGRect frameOfPresentedViewInContainerView;
我們會(huì)在子類中實(shí)現(xiàn) Getter 方法错沽,返回被呈現(xiàn)視圖在容器視圖中的位置簿晓。
在該頭文件中,我們還需要聲明該類遵守并實(shí)現(xiàn) <UIViewControllerTransitioningDelegate>
協(xié)議千埃。
遵守 <UIViewControllerTransitioningDelegate>
協(xié)議的作用:告訴控制器憔儿,誰是動(dòng)畫主管 (UIPresentationController
),哪個(gè)類負(fù)責(zé)開始動(dòng)畫的具體細(xì)節(jié)放可、哪個(gè)類負(fù)責(zé)結(jié)束動(dòng)畫的具體細(xì)節(jié)谒臼、是否需要實(shí)現(xiàn)可交互的轉(zhuǎn)場(chǎng)動(dòng)畫。
這個(gè)協(xié)議中一共有 5 個(gè)可選的實(shí)現(xiàn)方法吴侦,大致瀏覽一下:
@protocol UIViewControllerTransitioningDelegate <NSObject>
@optional
// 返回的對(duì)象控制 Presented 時(shí)的動(dòng)畫 (該類負(fù)責(zé)開始動(dòng)畫的具體細(xì)節(jié))
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
// 由返回的控制器控制 dismissed 時(shí)的動(dòng)畫 (該類負(fù)責(zé)結(jié)束動(dòng)畫的具體細(xì)節(jié))
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
// 實(shí)現(xiàn)可交互轉(zhuǎn)換動(dòng)畫屋休,呈現(xiàn)動(dòng)畫
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
// 實(shí)現(xiàn)可交互轉(zhuǎn)換動(dòng)畫,dismiss 動(dòng)畫
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
// 告訴控制器备韧,誰是動(dòng)畫主管(UIPresentationController)劫樟,因?yàn)榇祟惱^承了 UIPresentationController,就返回了 self
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source API_AVAILABLE(ios(8.0));
@end
在呈現(xiàn)密碼輸入框視圖時(shí)织堂,我們需要在密碼輸入框和 presenting View Controller 之間顯示一個(gè)半透明的遮罩層叠艳,這個(gè)遮罩層視圖我們稱之為 dimmingView
,它就可以被添加到我們創(chuàng)建的 UIPresentationController
子類對(duì)象中易阳。因此附较,我們會(huì)在 HQLVerticalPresentationController.m
的類擴(kuò)展中創(chuàng)建一個(gè)屬性:
@interface HQLVerticalPresentationController ()
@property (nonatomic, strong) UIView *dimmingView;
@end
另外,我們還需要通過一個(gè)容器視圖來顯示密碼輸入框左上角和右上角的圓角潦俺,因此再添加一個(gè)帶圓角的包裝視圖:
@interface HQLVerticalPresentationController ()
@property (nonatomic, strong) UIView *dimmingView;
@property (nonatomic, strong) UIView *presentationRoundedCornerWrappingView;
@end
實(shí)現(xiàn) UIPresentationController
子類對(duì)象的指定初始化方法:
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController {
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
if (self) {
presentedViewController.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
對(duì)于使用 UIPresentationController
子類來自定義呈現(xiàn)控制器時(shí)拒课,presented view controller 的 modalPresentationStyle
屬性必須設(shè)置為 UIModalPresentationCustom
。
在實(shí)現(xiàn)文件中事示,需要重載的幾個(gè)父類方法如下早像,幾乎每一處代碼都有注釋說明:
// 這是 presentation controller 在呈現(xiàn)視圖之初時(shí)首先被調(diào)用的方法之一。
// 當(dāng)這個(gè)方法被調(diào)用時(shí)肖爵,containerView 已經(jīng)在視圖層次結(jié)構(gòu)中被創(chuàng)建卢鹦。
// 但是,presentedView 還沒有被檢索到劝堪。
- (void)presentationTransitionWillBegin
{
// presentedView 屬性的 Getter 方法默認(rèn)返回 self.presentedViewController.view
UIView *presentedViewControllerView = [super presentedView];
// 將 presented view controller 的視圖包裹在一個(gè)中間視圖層次結(jié)構(gòu)中冀自。
// 該中間層視圖在左上角和右上角應(yīng)用了陰影和圓角效果揉稚。
// 最終的效果是使用兩個(gè)中間視圖構(gòu)建的。
//
// presentationWrapperView =
// |- presentationRoundedCornerView <- 添加 rounded corners (masksToBounds) 圓角
// |- presentedViewControllerWrapperView
// |- presentedViewControllerView (presentedViewController.view)
//
{
/**
presentationRoundedCornerView 的高度比 presented view controller 的視圖高 CORNER_RADIUS 的值熬粗。
這是因?yàn)?cornerRadius 默認(rèn)被應(yīng)用于視圖的所有角搀玖。
由于該效果只要求對(duì)頂部兩個(gè)角進(jìn)行圓角處理,我們將視圖底部的 CORNER_RADIUS 點(diǎn)調(diào)整為位于
屏幕的底部邊緣以下位置荐糜。
*/
UIView *presentationRoundedCornerView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(self.frameOfPresentedViewInContainerView, UIEdgeInsetsMake(0, 0, -CORNER_RADIUS, 0))];
presentationRoundedCornerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
presentationRoundedCornerView.layer.cornerRadius = CORNER_RADIUS;
presentationRoundedCornerView.layer.masksToBounds = YES;
self.presentationRoundedCornerWrappingView = presentationRoundedCornerView;
/**
為了撤銷加在 presentationRoundedCornerView 上的額外高度巷怜,
presentationViewControllerWrapperView 被 CORNER_RADIUS 點(diǎn)插入.
這也使 presentationViewControllerWrapperView 的邊界大小與 frameOfPresentedViewInContainerView 的大小相匹配。
*/
UIView *presentedViewControllerWrapperView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(presentationRoundedCornerView.bounds, UIEdgeInsetsMake(0, 0, CORNER_RADIUS, 0))];
presentedViewControllerWrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// 將 presentedViewControllerView 添加到 presentedViewControllerWrapperView.
presentedViewControllerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
presentedViewControllerView.frame = presentedViewControllerWrapperView.bounds;
[presentedViewControllerWrapperView addSubview:presentedViewControllerView];
// 將 presentedViewControllerWrapperView 添加到 presentationRoundedCornerWrappingView.
[presentationRoundedCornerView addSubview:presentedViewControllerWrapperView];
}
/**
在 presentationWrapperView 后面添加一個(gè)黑色半透明背景視圖暴氏,
self.presentationView 是稍后添加的(由動(dòng)畫添加),所以這里添加的任何視圖都會(huì)出現(xiàn)在 presentedView 后面绣张。
*/
{
UIView *dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
dimmingView.backgroundColor = [UIColor blackColor];
dimmingView.opaque = NO;
dimmingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[dimmingView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)]];
self.dimmingView = dimmingView;
[self.containerView addSubview:dimmingView];
// 獲取過渡期協(xié)調(diào)器以實(shí)現(xiàn)呈現(xiàn)答渔,這樣我們就可以在呈現(xiàn)動(dòng)畫的同時(shí),淡化 dimmingView侥涵。
id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
self.dimmingView.alpha = 0.f;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 0.5f;
} completion:NULL];
}
}
// 在呈現(xiàn)過渡結(jié)束時(shí)被調(diào)用的沼撕,并且該方法提供一個(gè)布爾變量來判斷過渡效果是否完成
- (void)presentationTransitionDidEnd:(BOOL)completed
{
/**
Completed 參數(shù)的值與動(dòng)畫傳遞給 -completeTransition: 方法的值相同。
在取消交互轉(zhuǎn)換的情況下芜飘,它可能是 NO务豺。
就是說,在呈現(xiàn)動(dòng)畫發(fā)生時(shí)嗦明,異常終止了動(dòng)畫笼沥,這時(shí)我們需要手動(dòng)釋放未正常添加的視圖
*/
if (completed == NO)
{
/**
系統(tǒng)會(huì)將 presented view controller 的視圖從它的 superview 中移除,并同時(shí)處理 containerView娶牌。
這隱含了從視圖層次結(jié)構(gòu)中刪除在 -presentationTransitionWillBegin: 中創(chuàng)建的視圖奔浅。 然而,我們?nèi)匀恍枰艞墝?duì)這些視圖的強(qiáng)引用诗良。
*/
self.presentationRoundedCornerWrappingView = nil;
self.dimmingView = nil;
}
}
// 消失過渡即將開始的時(shí)候被調(diào)用的
- (void)dismissalTransitionWillBegin
{
// 獲取過渡期協(xié)調(diào)器以實(shí)現(xiàn)退出汹桦,這樣我們就可以在退出動(dòng)畫的同時(shí),淡化 dimmingView鉴裹。
id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 0.f;
} completion:NULL];
}
// 消失過渡完成之后調(diào)用舞骆,此時(shí)應(yīng)該將視圖移除,防止強(qiáng)引用
- (void)dismissalTransitionDidEnd:(BOOL)completed
{
// The value of the 'completed' argument is the same value passed to the
// -completeTransition: method by the animator. It may
// be NO in the case of a cancelled interactive transition.
if (completed == YES)
{
// The system removes the presented view controller's view from its
// superview and disposes of the containerView. This implicitly
// removes the views created in -presentationTransitionWillBegin: from
// the view hierarchy. However, we still need to relinquish our strong
// references to those view.
self.presentationRoundedCornerWrappingView = nil;
self.dimmingView = nil;
}
}
你會(huì)注意到径荔,我們?cè)?dimmingView 上添加了一個(gè)手勢(shì)識(shí)別器督禽,當(dāng)點(diǎn)擊密碼輸入框上方的半透明視圖時(shí),可以實(shí)現(xiàn)密碼輸入框的 dismiss 效果猖凛,手勢(shì)識(shí)別器處理程序如下:
// IBAction for the tap gesture recognizer added to the dimmingView.
// Dismisses the presented view controller.
//
- (IBAction)dimmingViewTapped:(UITapGestureRecognizer*)sender
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
}
動(dòng)態(tài)方式實(shí)現(xiàn)四個(gè)布局方法:
// This method is invoked whenever the presentedViewController's
// preferredContentSize property changes. It is also invoked just before the
// presentation transition begins (prior to -presentationTransitionWillBegin).
//
// 當(dāng) presentedViewController 控制器內(nèi)容大小變化時(shí)赂蠢,就會(huì)調(diào)用這個(gè)方法, 比如適配橫豎屏幕時(shí)辨泳,翻轉(zhuǎn)屏幕時(shí)
// 可以使用 UIContentContainer 的方法來調(diào)整任何子視圖控制器的大小或位置虱岂。
- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
{
[super preferredContentSizeDidChangeForChildContentContainer:container];
if (container == self.presentedViewController)
[self.containerView setNeedsLayout];
}
// When the presentation controller receives a
// -viewWillTransitionToSize:withTransitionCoordinator: message it calls this
// method to retrieve the new size for the presentedViewController's view.
// The presentation controller then sends a
// -viewWillTransitionToSize:withTransitionCoordinator: message to the
// presentedViewController with this size as the first argument.
//
// Note that it is up to the presentation controller to adjust the frame
// of the presented view controller's view to match this promised size.
// We do this in -containerViewWillLayoutSubviews.
//
- (CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize
{
if (container == self.presentedViewController)
return ((UIViewController*)container).preferredContentSize;
else
return [super sizeForChildContentContainer:container withParentContainerSize:parentSize];
}
- (CGRect)frameOfPresentedViewInContainerView
{
CGRect containerViewBounds = self.containerView.bounds;
CGSize presentedViewContentSize = [self sizeForChildContentContainer:self.presentedViewController withParentContainerSize:containerViewBounds.size];
// The presented view extends presentedViewContentSize.height points from
// the bottom edge of the screen.
CGRect presentedViewControllerFrame = containerViewBounds;
presentedViewControllerFrame.size.height = presentedViewContentSize.height;
presentedViewControllerFrame.origin.y = CGRectGetMaxY(containerViewBounds) - presentedViewContentSize.height;
return presentedViewControllerFrame;
}
// This method is similar to the -viewWillLayoutSubviews method in
// UIViewController. It allows the presentation controller to alter the
// layout of any custom views it manages.
//
- (void)containerViewWillLayoutSubviews
{
[super containerViewWillLayoutSubviews];
self.dimmingView.frame = self.containerView.bounds;
self.presentationRoundedCornerWrappingView.frame = self.frameOfPresentedViewInContainerView;
}
我們的 UIPresentationController
子類對(duì)象還需要遵守并實(shí)現(xiàn) <UIViewControllerAnimatedTransitioning>
協(xié)議玖院。你當(dāng)然可以把實(shí)現(xiàn)這兩個(gè)協(xié)議的方法通過兩個(gè)不同的類來實(shí)現(xiàn),分別實(shí)現(xiàn) UIViewControllerTransitioningDelegate
協(xié)議和 <UIViewControllerAnimatedTransitioning>
協(xié)議第岖。但這兩個(gè)協(xié)議的方法具有較強(qiáng)的關(guān)聯(lián)性难菌,通常的做法是全都在某一個(gè)類內(nèi)部實(shí)現(xiàn)了。
這里我們?cè)?code>UIPresentationController 子類對(duì)象的擴(kuò)展中設(shè)置讓它遵守<UIViewControllerAnimatedTransitioning>
協(xié)議:
@interface HQLVerticalPresentationController () <UIViewControllerAnimatedTransitioning>
@property (nonatomic, strong) UIView *dimmingView;
@property (nonatomic, strong) UIView *presentationRoundedCornerWrappingView;
@end
實(shí)現(xiàn)兩個(gè)動(dòng)畫轉(zhuǎn)換的核心方法:
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
// 設(shè)置交互動(dòng)畫的百分比時(shí)間蔑滓,容器控制器的動(dòng)畫需要與主動(dòng)畫保持同步
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return [transitionContext isAnimated] ? 0.35 : 0;
}
// The presentation animation is tightly integrated with the overall
// presentation so it makes the most sense to implement
// <UIViewControllerAnimatedTransitioning> in the presentation controller
// rather than in a separate object.
// 核心郊酒,動(dòng)畫效果的實(shí)現(xiàn)
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// 獲取源控制器、目標(biāo)控制器键袱、動(dòng)畫容器 view
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
// 獲取源控制器燎窘、目標(biāo)控制器的 View,但是注意二者在開始動(dòng)畫蹄咖,消失動(dòng)畫褐健,身份是不一樣的:
// 對(duì)于呈現(xiàn)動(dòng)畫來說
// For a Presentation:
// fromView = The presenting view.
// toView = The presented view.
// 對(duì)于返回動(dòng)畫來說
// For a Dismissal:
// fromView = The presented view.
// toView = The presenting view.
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
// If NO is returned from -shouldRemovePresentersView, the view associated
// with UITransitionContextFromViewKey is nil during presentation. This
// intended to be a hint that your animator should NOT be manipulating the
// presenting view controller's view. For a dismissal, the -presentedView
// is returned.
//
// Why not allow the animator manipulate the presenting view controller's
// view at all times? First of all, if the presenting view controller's
// view is going to stay visible after the animation finishes during the
// whole presentation life cycle there is no need to animate it at all — it
// just stays where it is. Second, if the ownership for that view
// controller is transferred to the presentation controller, the
// presentation controller will most likely not know how to layout that
// view controller's view when needed, for example when the orientation
// changes, but the original owner of the presenting view controller does.
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
// 判斷是 present 還是 dismiss 動(dòng)畫
BOOL isPresenting = (fromViewController == self.presentingViewController);
// This will be the current frame of fromViewController.view.
CGRect __unused fromViewInitialFrame = [transitionContext initialFrameForViewController:fromViewController];
// For a presentation which removes the presenter's view, this will be
// CGRectZero. Otherwise, the current frame of fromViewController.view.
CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromViewController];
// This will be CGRectZero.
CGRect toViewInitialFrame = [transitionContext initialFrameForViewController:toViewController];
// For a presentation, this will be the value returned from the
// presentation controller's -frameOfPresentedViewInContainerView method.
CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toViewController];
// We are responsible for adding the incoming view to the containerView
// for the presentation (will have no effect on dismissal because the
// presenting view controller's view was not removed).
// 我們需要負(fù)責(zé)將傳入的視圖添加到容器視圖中進(jìn)行 presentation/dismissal
[containerView addSubview:toView];
if (isPresenting) {
toViewInitialFrame.origin = CGPointMake(CGRectGetMinX(containerView.bounds), CGRectGetMaxY(containerView.bounds));
toViewInitialFrame.size = toViewFinalFrame.size;
toView.frame = toViewInitialFrame;
} else {
// Because our presentation wraps the presented view controller's view
// in an intermediate view hierarchy, it is more accurate to rely
// on the current frame of fromView than fromViewInitialFrame as the
// initial frame (though in this example they will be the same).
fromViewFinalFrame = CGRectOffset(fromView.frame, 0, CGRectGetHeight(fromView.frame));
}
NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:transitionDuration animations:^{
if (isPresenting)
toView.frame = toViewFinalFrame;
else
fromView.frame = fromViewFinalFrame;
} completion:^(BOOL finished) {
/**
當(dāng)我們的動(dòng)畫執(zhí)行完成后,需要給 transition context 傳遞一個(gè) BOOL 值
以表示動(dòng)畫是否執(zhí)行完成
*/
BOOL wasCancelled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:!wasCancelled];
}];
}
創(chuàng)建 UIViewController 子類對(duì)象作為 presented View Controller
因?yàn)閯?chuàng)建的被呈現(xiàn)視圖控制器并不是全屏顯示的澜汤,所以需要用一個(gè)指定初始化方法來傳遞當(dāng)前視圖控制器視圖的 frame
屬性蚜迅。
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface HQLVerticalPresentedViewController : UIViewController
- (instancetype)initWithFrame:(CGRect)frame;
@end
NS_ASSUME_NONNULL_END
在實(shí)現(xiàn)文件中,核心代碼就是通過 frame
屬性的值更新被呈現(xiàn)視圖控制器的尺寸俊抵。
#import "HQLVerticalPresentedViewController.h"
#import <YYKit/YYCGUtilities.h>
#import "HQLVerticalPresentationController.h"
@interface HQLVerticalPresentedViewController ()
@property (nonatomic, assign, readwrite) CGRect frame;
@end
@implementation HQLVerticalPresentedViewController
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_frame = frame;
}
return self;
}
#pragma mark - View life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self updatePreferredContentSizeWithTraitCollection:self.traitCollection];
self.preferredContentSize = _frame.size;
}
#pragma mark - Private
//| ----------------------------------------------------------------------------
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
// When the current trait collection changes (e.g. the device rotates),
// update the preferredContentSize.
[self updatePreferredContentSizeWithTraitCollection:newCollection];
}
//| ----------------------------------------------------------------------------
//! Updates the receiver's preferredContentSize based on the verticalSizeClass
//! of the provided traitCollection.
//
- (void)updatePreferredContentSizeWithTraitCollection:(UITraitCollection *)traitCollection
{
self.preferredContentSize = CGSizeMake(self.view.bounds.size.width, traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact ? 270 : 420);
}
@end
其實(shí)谁不,創(chuàng)建完以上兩個(gè)視圖控制器之后,我們就可以以半模態(tài)視圖的樣式來呈現(xiàn)視圖了徽诲,具體使用步驟:
-
初始化
HQLVerticalPresentedViewController
或其子類實(shí)例:HQLVerticalPresentedViewController *presentationViewController = [[HQLVerticalPresentedViewController alloc] init];
-
初始化
HQLPresentationController
實(shí)例:HQLVerticalPresentationController *presentationController NS_VALID_UNTIL_END_OF_SCOPE; presentationController = [[HQLVerticalPresentationController alloc] initWithPresentedViewController:presentationViewController presentingViewController:self];
-
設(shè)置
UIViewControllerTransitioningDelegate
:presentationViewController.transitioningDelegate = presentationController;
-
模態(tài)呈現(xiàn):
[self presentViewController:presentationViewController animated:YES completion:NULL];
在 HQLVerticalPresentedViewController
子類對(duì)象中添加你想要呈現(xiàn)的視圖
我們可以通過子類化 HQLVerticalPresentedViewController
的方式刹帕,在該視圖控制器中添加密碼輸入框視圖,當(dāng)中包括了一些密碼輸入框相關(guān)的視圖元素馏段。
密碼輸入框視圖元素的實(shí)現(xiàn)我大概參考了之前的實(shí)現(xiàn)轩拨,當(dāng)然也略有優(yōu)化和修改。
之前的實(shí)現(xiàn)文章:密碼輸入框:HQLPasswordViewDemo
所以院喜,有了密碼輸入框視圖 HQLPasswordsView
之后亡蓉,我們直接創(chuàng)建一個(gè)HQLVerticalPresentedViewController
的子類對(duì)象,暫且命名為 HQLPasswordViewController
好了:
#import "HQLVerticalPresentedViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface HQLPasswordViewController : HQLVerticalPresentedViewController
@end
NS_ASSUME_NONNULL_END
實(shí)現(xiàn)代碼中喷舀,把密碼輸入框視圖 HQLPasswordsView
作為該視圖控制器的屬性引入即可砍濒,然后通過 Block 方式實(shí)現(xiàn)了交互回調(diào):
#import "HQLPasswordViewController.h"
#import "HQLPasswordsView.h"
@interface HQLPasswordViewController ()
@property (nonatomic, strong) HQLPasswordsView *passwordView;
@end
@implementation HQLPasswordViewController
#pragma mark - View life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.passwordView];
[self configurePasswordView];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 自動(dòng)彈出鍵盤
[self.passwordView.pwdTextField becomeFirstResponder];
}
#pragma mark - Custom Accessors
- (HQLPasswordsView *)passwordView {
if (!_passwordView) {
_passwordView = [[HQLPasswordsView alloc] initWithFrame:self.view.bounds];
}
return _passwordView;
}
#pragma mark - Private
- (void)configurePasswordView {
__weak __typeof(self)weakSelf = self;
// MARK: 關(guān)閉按鈕
self.passwordView.closeBlock = ^{
[weakSelf dismissViewControllerAnimated:YES completion:NULL];
};
// MARK: 忘記密碼
self.passwordView.forgetPasswordBlock = ^{
[weakSelf dismissViewControllerAnimated:YES completion:NULL];
// 執(zhí)行找回密碼流程
};
// MARK: 輸入所有密碼,發(fā)起網(wǎng)絡(luò)請(qǐng)求硫麻,校驗(yàn)密碼
self.passwordView.finishBlock = ^(NSString *inputPassword) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
NSLog(@"password = %@",inputPassword);
// 通過 GCD 模擬網(wǎng)絡(luò)請(qǐng)求
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
[strongSelf.passwordView requestComplete:YES message:@"支付成功"];
});
};
}
@end
當(dāng)處理密碼輸入這種一次性交互時(shí)爸邢,通過 Block 塊的方式處理 presenting view controller 和 presented view controller 之間的數(shù)據(jù)交互是可用的方式之一。
如果要處理列表視圖或者集合視圖中某個(gè) cell 元素的點(diǎn)擊處理事件拿愧,你也可以通過 Delegate 的方式實(shí)現(xiàn)這兩者之間的交互杠河。
將密碼輸入框視圖控制器作為半模態(tài)視圖控制器呈現(xiàn)
在你所需要的業(yè)務(wù)場(chǎng)景中,呈現(xiàn)密碼輸入框視圖控制器:
- (IBAction)presentPasswordViewController:(id)sender {
// 1.初始化 HQLPresentationViewController 實(shí)例
HQLPasswordViewController *passwordViewController = [[HQLPasswordViewController alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 455)];
// 2.初始化 HQLPresentationController 實(shí)例
HQLVerticalPresentationController *presentationController NS_VALID_UNTIL_END_OF_SCOPE;
presentationController = [[HQLVerticalPresentationController alloc] initWithPresentedViewController:passwordViewController presentingViewController:self];
// 3.設(shè)置 UIViewControllerTransitioningDelegate
passwordViewController.transitioningDelegate = presentationController;
// 4.模態(tài)呈現(xiàn)
[self presentViewController:passwordViewController animated:YES completion:NULL];
}
?????? 效果就是你在文章最開始看見的樣子。
千呼萬喚始出來券敌,源碼也不能少唾戚,該示例代碼可以在 iOS-Samples/UIPresentationController相關(guān)示例/UIPresentationController/HQLPasswordView 中找到〈纾可能是藏得比較深了叹坦,為了避免 iOS Demo 過多,并且分散在 GitHub 的多個(gè)倉庫下卑雁,使得 repositories 變得臃腫募书,而且也造成了項(xiàng)目污染,所以我把它們都?xì)w檔在一個(gè)倉庫下了测蹲。
Anyway莹捡,Have Fun!