交互式滑动侧边栏(下)

2,370 阅读6分钟

接着可交互滑动侧边栏(上)的内容,今次我们将在之前的基础上,做出个完整可用的侧边栏。Previously on我的教程,我们通过自定义View Controller Transition以及快照的巧妙使用,成功实现了打开和关闭菜单的动画(如下图所示),但并没有添加任何交互效果。如果能通过左右滑动的方式打开/关闭侧边栏,想必是极好的。

closingAnimation

打开上次的半成品,我们接着开码。

添加Dismiss动作

我们通过滑动手势识别器来驱动过渡动画,当用户水平滑动屏幕时,关闭动画会随你手势而动。

打开MenuViewController.swift并添加下面的代码:

importUIKit

classMenuViewController: UIViewController{

// 1

varinteractor:Interactor?=nil

// 2

@IBAction funchandleGesture(sender:UIPanGestureRecognizer){

// 3

lettranslation=sender.translationInView(view)

// 4

letprogress=MenuHelper.calculateProgress(

translation,

viewBounds:view.bounds,

direction:.Left

// 5

MenuHelper.mapGestureStateToInteractor(

sender.state,

progress:progress,

interactor:interactor){

// 6

self.dismissViewControllerAnimated(true,completion:nil)

@IBAction funccloseMenu(sender:AnyObject){

dismissViewControllerAnimated(true,completion:nil)

解释一下上面的代码:

  • 注释1:MainViewController把interactor对象传递给了MenuViewController,用于同步状态机的状态
  • 注释2:给滑动手势创建对应的@IBAction方法,随后我们会将它与Storyboard中的手势相连
  • 注释3:通过translationView()方法获得手势当前坐标
  • 注释4:调用MenuHelper的calculateProgress()方法,把手势坐标转化为特定方向(这里是.Left)上的滑动进度(滑动距离/屏幕距离)
  • 注释5:把所需信息传入MenuHelper的mapGestureStateToInteractor()方法,进行手势状态和过渡动画之间的同步
  • 注释6:注意这里的闭包并不是Completion Handler(方法完成后调用的闭包)。我们需要把启动动画的代码放在这里,即dismissViewControllerAnimated()

每一次触发滑动手势都会调用上面的代码,更新过渡动画的进度。假如上面的代码弄懵了你,不要悲伤,不要心急!忧郁的日子里需要回顾上一篇教程中的添加辅助文件,相信吧,看懂代码的日子终会来临!

添加滑动手势识别器

还记得我们刚刚创建的@IBAction吗?是时候把它和Storyboard相连接了。

  • 从Object Library拖出一只手势识别器,扔到之前创建的Close按钮上
  • 按住Control不放,把手势识别器图标拖到Menu View Controller的Scene图标上
  • 在弹出菜单中选中handleGesture:

wirePanGestureToCloseButton

多亏一位叫做Aly的读者告诉我,把手势识别器放到Close按钮上的效果要比放到背景视图上好。这会让快照更像一个真实存在的对象(好悲伤的一句话,默哀一秒钟)。此外,如果你准备在菜单栏里使用Table View并给Cell添加滑动手势的话(好反人类的设计),这种方法还可以避免手势冲突。

连接Dismiss动作

在手势起作用之前还差一步:我们需要告诉MainViewController,关闭菜单时过渡动画的交互被我们承包了。

MainViewController.swift的内容替换成下面的代码:

importUIKit

classMainViewController: UIViewController{

// 1

letinteractor=Interactor()

@IBAction funcopenMenu(sender:AnyObject){

performSegueWithIdentifier("openMenu",sender:nil)

overridefuncprepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){

ifletdestinationViewController=segue.destinationViewControlleras?MenuViewController{

destinationViewController.transitioningDelegate=self

// 2

destinationViewController.interactor=interactor

extensionMainViewController: UIViewControllerTransitioningDelegate{

funcanimationControllerForPresentedController(presented:UIViewController,presentingController presenting:UIViewController,sourceController source:UIViewController)->UIViewControllerAnimatedTransitioning?{

returnPresentMenuAnimator()

funcanimationControllerForDismissedController(dismissed:UIViewController)->UIViewControllerAnimatedTransitioning?{

returnDismissMenuAnimator()

// 3

funcinteractionControllerForDismissal(animator:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{

returninteractor.hasStarted?interactor:nil

老规矩,解释一下其中几处:

  • 注释1:还记得我们传来传去的interactor对象吗?这里就是最初创建它的地方
  • 注释2:把interactor传给prepareForSegue()方法
  • 注释3:只有当用户滑动时(之前的代码中,我们在手势开始时把hasStarted设置为了true,这里正好用上)才会返回interactor,表示动画将以交互的形式进行

目前为止的代码结构:

  • MainViewController.swift
  • MenuViewController.swift
  • Interactor.swift(未修改)
  • MenuHelper.swift(未修改)
  • PresentMenuAnimator.swift(未修改)
  • DismissMenuAnimator.swift(未修改)

编译运行,现在应该能通过拖拽快照关闭菜单了。

closingInteraction

添加Present动作

就快完成了!其实我们可以就此打住,这已经是一个非常实用的滑动侧边栏了。但如果打开菜单也能通过手势交互完成多好?

译者:Apple不提倡滑动侧边栏的原因正在于此。原生软件里的手势都严格遵循统一的设计哲学,从左边缘向右滑动代表着“返回”,无论是Safari里的返回上一页面,还是无处不在的返回上级内容,这与侧边栏的设计相悖。其实不单是Apple,就连重度使用侧边栏的Google,为了保证手势意义的统一,也没有添加滑动展开菜单的手势。但另一方面,如果添加了侧边栏却不添加手势,那么用户就不得不去按左上角的菜单图标,没有个13厘米长的大拇指根本无法正常玩耍。

个人原则是,不常用的设置性内容可以放到侧边栏里,尽量不使用滑动展开的手势;否则用Tab Bar Controller代替,避免过度设计。

这里我们不使用普通的手势识别器,而是用Screen Edge Pan Gesture Recognizer(边缘滑动手势识别器),以此把它对主内容区手势的影响降到最低。

和其他手势一样,你需要引导用户,让他们知道你的App具有这样的手势特性。好在对重度依赖滑动手势的用户来说,这样的手势并不陌生。如果想要了解如何制作手势引导动画,可以参考这里

打开MainViewController.swift并添加下面的代码:

	
@IBAction funcedgePanGesture(sender:UIScreenEdgePanGestureRecognizer){

lettranslation=sender.translationInView(view)

letprogress=MenuHelper.calculateProgress(translation,viewBounds:view.bounds,direction:.Right)

MenuHelper.mapGestureStateToInteractor(

sender.state,

progress:progress,

interactor:interactor){

self.performSegueWithIdentifier("openMenu",sender:nil)

和之前我们在MenuViewController里添加的@IBAction方法非常相似。edgePanGesture调用了MenuHelper的方法,负责手势状态和过渡动画间的同步。

添加边缘滑动手势识别器

  • 从Object Library里拽一个边缘手势识别器并拖到MainViewController上(蓝色)
  • 按住Control不放,把手势图标拽到MainViewController的Scene图标上
  • 在弹出菜单中选择edgePanGesture:动作
  • 在手势的Attributes Inspector里勾选Left

edgePanGesture

操作和之前差不多,唯一的区别是,这次需要设定手势仅使用屏幕左边缘。

连接Present动作

最后的最后,我们同样需要告诉过渡动画代理,我们将接管打开菜单时的所有交互。

打开MainViewController.swift,在UIViewControllerAnimatedTransitioning扩展里添加下面的代码:

	
funcinteractionControllerForPresentation(animator:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{

returninteractor.hasStarted?interactor:nil

完成收工!
最后检查一下代码结构:

  • MainViewController.swift
  • MenuViewController.swift(未修改)
  • Interactor.swift(未修改)
  • MenuHelper.swift(未修改)
  • PresentMenuAnimator.swift(未修改)
  • DismissMenuAnimator.swift(未修改)

编译运行,应该能通过滑动手势打开菜单了。

leftEdgePanToOpen

总结

你可以在这里查看完成的项目代码。此外,在示例项目的菜单视图里,我们还添加里一些Table View Cell,用于打开相应详细视图。

私以为,自定义View Controller Transition不失为一种优雅创建交互侧边栏的方法。通过快照的巧妙使用,营造出了屏幕上同时存在两个View Controller的错觉。除此之外,我们还可以自由定义过渡动画效果,满足各种变态需求。

原文链接:https://www.thorntech.com/2016/03/ios-tutorial-make-interactive-slide-menu-swift/

第一时间获取iOS优质中文教程,扫描下方的二维码订阅程序猴猴猴,或订阅我的博客:www.jiarui-blog.com。

qrcode

交互式滑动侧边栏(下)