模拟 UIAlertView

原文:Transcending UIAlertView on iOS 7
作者: Matt Neuburg
译者:kmyhy

在我看来,在 iOS7 提供给开发者中的所有新特性中,最重要的莫过于一个 UI 方面的改进:自定义 view controller 转换,能够在 view controller 呈现时插入自己的动画。这样:

  • 当 tab bar controller 的某个子控制器被选中时,你可以自定义动画。
  • 当 view controller 以 push 方式展现到导航控制器中时,你不再受限于系统的“从右边滑入”的动画。
  • 当一个 view controller 以弹出方式呈现/解散时,不再受限于 4 种 UIModalTransitionStyle 所指定的动画。

对于第三种情况——一个被呈现的 view controller —— iOS 7 引入了一个创举:你可以将被呈现控制器放在任何地方,甚至可以部分遮住原视图上面。

换句话说,被呈现控制器的 view 可以“浮”在或部分覆盖住原视图上方,用户可以同时看到两个视图,一个在前,一个在后。

这对于 iPhone 来说意义重大。以前,在 iPhone 上,被呈现控制器只能全屏覆盖——实际上取代了原视图。在 iOS 7,情况发生了变化,被呈现控制器的视图可以只占据部分屏幕,同时原来的 view controller 的视图也不会被移除。

为了展示这一点,我会介绍如何利用这个特性来代替原来单调的 UIAlertView,利用自定义的小巧的“漂浮式”的 view,可以以 UIAlertView 相同的方式来显示和解散。和 UIAlertView 不同,这个 view 可以任意定制你自己的 UI。

注意:本文摘自作者写的一本叫做《Programming iOS 7》的书,我为本文专门编写了一个 demo 项目:https://github.com/mattneub/custom-alert-view-iOS7

下面的截图显示了我们即要实现的效果:

左边是 app 的主界面,包含一个”Show Custom Alert View” 按钮(顶部)和一个 image view。目的仅仅是为我们的自定义的 alert view 提供一个足够醒目、漂浮于上的 UI。

右边是自定义 alert view 显示的样子,它漂浮在原来的界面上。注意,和 UIAlertView 一样,屏幕的剩余部分将变暗,并使”Show Custom Alert View” 按钮(在背景 view 上)变得模糊。但它显然不是一个普通的 UIAlertView:它包含了一个 image view 和一个 switch 控件!这是用常规的 UIAlertView 所无法做到的。当然,你可以在这个 view 上加入任何东西。我们突破了 UIAlertView 的限制。我们需要做的仅仅是定义一个 view controller 类,我把它命名为 CustomAlertViewController,以及一个同名的 .xib。这个 .xib 的大概样子如下图所示:

有两个地方值得注意:

  • 外面的那个 view,它的大小占满了 iPhone 整个屏幕 ,它是控制器的根视图(即控制器的 view)。它的背景色是一个有点透明的淡灰色。这产生了一个效果,在 alert view 后面产生一个背景加暗的效果。
  • 中间是稍小一些的 subview。它构成了我们的 alert view 的主要部分。CustomAlertViewController 中有一个 IBOutlet 引用了它,名字是 alertView。

这是 viewDidLoad 方法,它负责配置 self.alertView,给它一个蓝色的边框和圆角矩形的外观。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.alertView.layer.borderColor = [UIColor blueColor].CGColor;
    self.alertView.layer.borderWidth = 2;
    self.alertView.layer.cornerRadius = 8;
}

CustomAlertViewController 有一个初始化方法:

-(id)initWithNibName:(NSString *)nibNameOrNil 
              bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil 
                           bundle:nibBundleOrNil];
    if (self) {
        self.modalPresentationStyle = UIModalPresentationCustom;
        self.transitioningDelegate = self;
    }
    return self;
}

这里设置了两个 iOS 7 中新增的 UIViewController 属性。modalPresentationStyle 设置为 Custom,表示我们将为它提供自定义的转换动画。transitioningDelegate 设置为一个对象,由这个对象来负责提供自定义的转换动画。

然后回到主界面,处理 Show Custom Alert View 按钮的触摸。在这里创建 CustomAler letViewControl 实例并呈现它:

- (IBAction) doButton: (id) sender {
    UIViewController* vc = [CustomAlertViewController new];
    [self presentViewController:vc animated:YES completion:nil];
}

我们的 CustomAlertViewController 将被呈现,但是它 modalPresentationStyle 和 transitioningDelegate 还需要进一步处理。transitioningDelegate 被设为了 self, 也就是 CustomAlertViewController 自己,因此它需要采用 UIViewControllerTransitioningDelegate 协议。在 CustomAlertViewController 中需要实现两个方法,每个方法返回一个对象,由该对象来实际操作自定义转换动画。为了遵循对象的“自包含”原则,那个对象也被我们放到了 self 中:

```swift
-(id<UIViewControllerAnimatedTransitioning>)
        animationControllerForPresentedController:
            (UIViewController *)presented 
        presentingController:
            (UIViewController *)presenting 
        sourceController:
            (UIViewController *)source {
    return self;
}

-(id<UIViewControllerAnimatedTransitioning>)
        animationControllerForDismissedController:
            (UIViewController *)dismissed {
    return self;
}




<div class="se-preview-section-delimiter"></div>

两个方法都返回了 self,运行的时候需要 CustomAlertViewController 提供更多的细节来进行自定义转换动画(即实现 UIViewControllerAnimatedTransitioning 协议)。首先必须实现这个方法,以返回一个动画时长:

-(NSTimeInterval)transitionDuration:
        (id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.25;
}




<div class="se-preview-section-delimiter"></div>

然后,真正执行自定义转换动画:

-(void)animateTransition:
        (id<UIViewControllerContextTransitioning>)transitionContext {
    // ...
}





<div class="se-preview-section-delimiter"></div>

我将分多次完成这个方法。首先看一下传入的参数,transitionContext。这个对象会在运行时提供给你,以便获取一些重要的信息比如:

  • 和转换相关的两个 view controller。
  • 主要是 container view 对象,在这个地方,两个 view controller 将被显示。可以将 container view 看做是动画发生的场所。

因此,我们的第一步就是从 transitionContext 中获取这几个信息:

UIViewController* vc1 =
    [transitionContext viewControllerForKey:
        UITransitionContextFromViewControllerKey];
UIViewController* vc2 =
    [transitionContext viewControllerForKey:
        UITransitionContextToViewControllerKey];
UIView* con = [transitionContext containerView];
UIView* v1 = vc1.view;
UIView* v2 = vc2.view;





<div class="se-preview-section-delimiter"></div>

现在我们就拿到了两个 view controller 和它们的 view 以及 containerView。
在 UIViewControllerAnimatedTransitioning 协议中,我们总共返回了两次 self,一次用于呈现,一次用于解散。我们必须区分这两种情况。其实很简单:当呈现时,第二个 view controller(vc2) 将是 self,它的 view (v2) 则是 self.view。

我会分别针对两种情况进行处理。当呈现的时候,在 container view 中已经有第一个 view controller 的 view(v1)存在了,我们的工作仅仅是将第二个 view controller 的 view(v2,也就是 self.view)添加到 container view(con)中去。把它放到我们想放的地方,并按照我们的方式对它进行动画。还有一个强制性的规定:当我们完成动画,我们必须发送一个消息(completeTransition:)给 transitionContext 对象,告诉它动画已经完成。

在我们的例子中,因为我们的 view 是一个背景上的“阴影 view”,它的 frame 应当等于位于它下面的原始 view 的 frame。为了造成一种 UIAlertView 飞进屏幕的效果,我们需要将我们的 view 从屏幕之外移动到屏幕之内。我们可以用一个缩放转换来完成,将我们的 alertView 从一个放大的尺寸变成正常大小,以模仿系统内置的 UIAlertView:

if (vc2 == self) { // 呈现过程
    [con addSubview:v2];
    v2.frame = v1.frame;
    self.alertView.transform = CGAffineTransformMakeScale(1.6,1.6);
    v2.alpha = 0;
    v1.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
    [UIView animateWithDuration:0.25 animations:^{
        v2.alpha = 1;
        self.alertView.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];
} 




<div class="se-preview-section-delimiter"></div>

呈现就讲完了,现在来看解散。当用户点击 OK,我们应该解散我们的 alert view:

- (IBAction) doDismiss: (id) sender {
    [self.presentingViewController 
        dismissViewControllerAnimated:YES completion:nil];
}




<div class="se-preview-section-delimiter"></div>

这同样会调用我们的 animateTransition: 方法。当我们进行解散过程时,view controller 会以不同方式进行:第二个 view controller(vc2)是原始视图,而第一个 view controller(vc1)是 self,他的 view(v1) 是我们的视图(self.view)。我们以动画的方式隐藏我们的 view,但不需要我们从 container view 中移除它,运行的时候会自动移除:

else { // 解散过程
    [UIView animateWithDuration:0.25 animations:^{
        self.alertView.transform = CGAffineTransformMakeScale(0.5,0.5);
        v1.alpha = 0;
    } completion:^(BOOL finished) {
        v2.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
        [transitionContext completeTransition:YES];
    }];
}

注意在两个过程中我们修变了原始视图的 tintAdjustmentMode 属性,以和原生 UIAlertView 保持一致。

这就搞定了!有几个地方需要注意一下:

  • 这个例子不管 iPhone 是横屏还是竖屏都能够正常工作。甚至你可以在 alert view 显示的时候旋转屏幕也不会有什么问题。因为我们的 view controller 同正常的 view controller 一样是支持旋屏的,无论原始 view 还是我们的 view(“阴影 view”,包括 alert view)都能以正常的方式旋转和改变大小。约束(xib 文件中的)保持让 alert view 始终处于“阴影 view”的中央。
  • 还可以在 viewDidLoad 中将一个 UIMotionEffectdGroup 添加到 self.alertView 上。这样它会显示一种平行视差效果,和 UIAlertView 一样。

我希望这个例子能引导你创建自己的自定义 alert view。不需要再和 UIAlertView 较劲了,事实上,你从此不再需要 UIAlertView。你可以自由地创建自己的 view 并让它替代 UIAlertView。你可以随便修改这个例子,加入你自己的代码,让它更像或者更不像原生的 UIAlertView。没有什么事情是不可能的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值