最终效果

最终效果

开始

首先下载并打开一个事先搭好架子的Demo,然后来分析一下。这个Demo包含一个主页和详情页,其中MenuViewController继承自UITableViewController,它主要用于展示左边侧栏,自定义的MenuItemCell中设置了每一个菜单的图标和颜色。DetailViewController为详情页,显示了每个cell点击后,对应的颜色和图标。

Starter Project效果

这个教程将详细的介绍实现步骤,具体步骤如下:

  • 整个教程将使用自动布局来实现,需要将现在的主页push到详情页的这种模式改为横向ScrollView滚动的模式。

  • 需要在左上角添加显示/隐藏菜单栏的按钮。
  • 接下来,需要实现类似于Taasky的菜单栏的3D翻转效果。
  • 最后,需要将菜单栏的翻转效果与按钮的旋转效果结合起来。

首要任务是把菜单栏改成滑出的效果,UIScrollView中需要包含菜单栏和详情页面,就像是Swift Scroll View School Part 13这个视频教程中介绍的SwiftSideNav一样。示意图如下:

视图范围

用户可以通过左右滑动来显示/隐藏菜单栏。在上图中,紫色方框是当菜单栏显示的时候,屏幕所显示的部分,绿色方框是当菜单栏隐藏时,屏幕所显示的内容。当菜单栏打开时,显示的是一个局部侧滑边栏。

如果对UIScrollView不熟悉,请先学习Part 1Part2一系列教程,它们讲介绍UIScrollView的工作流程。

刚才提到的SwiftSideNav是一个使用自动布局完成例子,但是今天的教程将会直接在StoryBoard中嵌入菜单与详情页,完成后的结构如图:

最终结构图

为UIScrollView添加约束

这个部分会使用到控制器约束等知识(在iOS5时推出),如果还不熟悉,可以先学习iOS 5 By Tutorials书中的第18章UIViewController Containment。

菜单栏与详情。整个视图层级应该是这样的:

  • 有一个根控制器,并将UIScrollView添加到根控制器上;
  • 然后添加一个UIView到UIScrollView上,暂且叫它”Content View”;
  • 添加两个子容器到”Content View”上,然后分别把菜单栏和详情页填到到子容器中。

创建一个名为ContainerViewController的控制机,继承自UIViewController,语言选择Swift

创建容器

打开Main.StoryBoard,从Object Library中拖一个View Controller到画布中,并在Identity Inspector(Xcode右边栏的第三个选项)中设置自定义类为ContainerViewController,唯一标识为ContainerVC

绑定类,修改唯一标识

接下来,在 Attributes Inspector(Xcode右边栏的第四个选项)中将ContainerViewController的view背景色设置为黑色。

设置背景色

创建UIScrollView

接着,需要添加UIScrollView到ContainerViewController中,并添加约束。具体操作如下:

  • 拖一个UIScrollView到ContainerViewController中,并让它填充整个ContentViewController
  • 取消横、纵滚动条的显示(Shows Horizontal IndicatorShows Vertical Indicator);
  • 取消Delays Content Touches,让响应事件及时传递给子控件,防止响应延迟;

ContainerViewController设置为UIScrollView的delegate(按住control键,从UIScrollView拖到UIViewController)。

关联代理

接下来是给UIScrollView添加约束的步骤:

  • 找到右下角的Pin按钮,打开添加约束的弹窗;
  • 取消Constrain to margins,防止系统自动加左右边距;

  • 添加上下左右四个约束,并确保约束的值都是0;
  • 点击Add 4 Constraints按钮,将约束加上。

给UIScrollView添加约束

仍然选中UIScrollView,打开Size Inspector选项(Xcode右边栏倒数第二个选项),确认一下约束是否与教程所加的约束相同:

  • Trailing Space to: Superview
  • Leading Space to: Superview
  • Top Space to: Superview

  • Bottom Space to: Bottom Layout Guide

检查约束

如果有类似16这样的数字出现,说明在添加约束的时候,没有取消Constrain to margins。解决方案:删除UIScrollView的约束,重新添加一次,注意记得取消Constrain to margins。

在添加或修改约束以后,可能需要根据新的约束来展示frame。这个需求可以通过右下角Resolve Auto Layout Issues弹出框(Pin按钮旁边)的Update Frames选项来解决。

创建Content View

接下来需要做的是添加一个Content View到UIScrollView上,并添加响应约束,这些约束对设置UIScrollView的contentSize很重要。在下一节中,要将添加菜单栏与详情两个容器视图添加到Content View上。

拖一个新的View到UIScrollView上,让它自动填满整个父视图,并将背景色设置为Default

设置宽与背景色

译者注:上图中,作者将Width设置为了680,但在文字描述中并未提到。这个宽度可加可不加,因为之后界面上的宽度其实全都要通过约束来表现。

选中刚加上的这个View,打开Identity Inspector,将Document\Label设置为Content View。这个名字将会在document outline(IB左边视图层级栏)显示出来,可以很容易追踪到这个View,并且在之后加与它相关的约束的时候,这个名字也会显示出来。

在给Content View添加约束的过程中,可以看到警告,不过不用惊慌,这些警告在这一节完之前能得到解决。

打开Pin弹出框,给Content View添加相对于父视图的约束。

给Content View添加约束

在Size Inspector中,确保Trailing Space的约束值为0。

将右边距约束改为0

译者注:这里有点不明白作者的操作,直接在添加约束的时候,将右边距设置为0就可以了,但是作者却先将宽设置为680,然后添加约束的时候也是添加的-80,最后又改成0。感觉有点多此一举,还请大神们解答下。

现在之所以有警告是因为StoryBoard需要Content View的高和宽来设置contentSize。添加相对于Scroll View的父容器的这些约束,可以使之适配各种设备与横竖屏。

在document outline中,按住Control键,从Content View拖到View(Scroll View的父容器)。按住Shift键,选择Equal WidthsEqual Heights

添加等宽等高约束

然后将Equal Width约束改为80。

加80像素的约束

将约束改为80的用意是:这样Content View就会比View宽80,就可以刚好装下菜单栏。同时,可以发现之前的警告也不存在了——干得漂亮。

添加菜单栏与详情界面

现在Scroll View上有Content View可以作为菜单栏和详情页的容器,然后将菜单栏与详情页(后文中称为Menu View与Detail View)嵌入Content View,这样便可以创建一个可以滑动显示.隐藏的Menu View。

首先,创建Menu Container View:拖一个Container ViewContent View上,在Size Inspector中,将其宽度设为80,然后在Identity Inspector中将Document\Label设置为Menu Container View

设置宽度,添加描述Label

高度应该是默认的600,不过如果你不确定,也可以设置一下。

译者注:这里作者只提到设置宽度,但根据后文的一些描述,这里应该还需要将x设置为0,y设置为0。

然后,添加Detail Container View:新拖一个Container ViewContent View上,并放置在menu container的右边。打开Size Inspector,并设置以下值:

  • X: 80
  • Y: 0
  • Width: 600
  • Height: 600

接下来,打开Identity Inspector,将Document\Label设置为Detail Container View

设置frame,添加描述Label

这样,Menu ViewDetail View的宽度就和Content View相等了。

容器

当添加完这两个自控制器后(container view),可在在IB中看到系统已经默认添加了contained view controller,但是我们需要用到已存在的Menu View与Detail View,所以需要在document outline或者画布中删除这两个控制器。

删除系统自动关联的ViewController

译者注:这个教程中的操作,都在文章开头下载的那个Demo中进行(称为starter project),所以,已存在的Menu View与Detail View就是指starter project中的控制器视图。

接下来,需要给两个container view添加约束。

Menu Container View添加宽为80,上下左右边距为0,共5个约束。

添加5个约束

Detail Container View添加上、右、下边距为0,共3个约束;注意不要添加左边距约束,否则会和Menu Container View的右边距约束重复。

添加三个约束

给两个Container View添加的约束都要确保Constrain to margins为取消。

嵌入Menu与Detail View Controllers

这一节中,将会把starter project中的menu和detail view controllers拆开,然后分别嵌入Menu Container ViewDetail Container View

首先,将Container View Controller设为入口控制器:将原本指向Navigation Controller的入口箭头指向Container View Controller

设置入口控制器

接下来,按住Control键,从Menu Container View拖到Navigation Controller,在弹出框中选择embed

关联菜单视图

Menu Container View嵌入Navigation Controller之后,相关的视图的宽度就都缩小到了80:

关联以后,控制器宽度变为80

现在来调整一下menu与detail:首先,将table view cell中imageView的宽度调整为

调整cell宽度

然后,删除menu与detail之间的push连线。选中Detail View Controller,然后在Xcode菜单中选择Editor\Embed In\Navigation Controller

给详情页添加导航栏

现在detail应该加在了Navigation Controller上,同时拥有了黑色的导航栏。

选中这个新的Navigation Controller的导航栏,打开Attributes Inspector,选择StyleBlank,取消Translucent,并将Bar Tint设置为Black Color

设置导航栏样式与背景色

译者注:如果不好选中导航栏,可以在document outline中进行选择,注意不要选错了。

这个设置完以后,新加的导航栏就和第一个导航栏的样式一致了。

接着,打开Detail View Controller控制器的Attributes Inspector,确保View Controller\Layout\Adjust Scroll View Insets是处于选中状态。

选中Adjust Scroll View Insets

Adjust Scroll View Insets的选中是为了将视图从Navigation Bar下面开始显示,而不会被Navigation Bar盖住

最后,将Detail View Controller嵌入Detail Container View:按住Control键,从Detail Container View拖到Detail View ControllerNavigation Controller,在弹出框中选择embed

嵌入详情页

运行程序,左右拖动视图,就可以显示/隐藏菜单栏了。你是否注意到,在拖动Scroll View是,可以超出左右边界,而且还可以停止滚动,显示部分菜单这个问题?

当前效果图

为了修复这个问题,需要在Scroll View的Attributes Inspector中修改一下属性:

  • 选中Scrolling\Paging Enabled属性,这样就可以…(译者注:原谅我找不到好的词语来表达原词了,原文中用到的是“snaps”,大概就是有一种惯性、巧劲的感觉,具体Paging Enabled的效果相信大家都懂的,就不说了);
  • 取消Bounce\Bounces属性,防止左右滚出边距(译者注:即没有回弹效果)。

译者注:原文的动图没有截导航栏部分,我在跟着做的时候,发现菜单的导航栏上有白边,原因是Menu Container View与Detail Container View的背景色为默认色的原因,设置为黑色即可。

这时,在运行程序,就不会出现左右滑出边界的情况了,并且,也不会出现菜单栏可以显示一部分的情况。但是,在向左滑动,试图隐藏菜单栏时,又出现了新问题。

Paging Enable问题

本来滚动隐藏的菜单栏又弹了出来。这个问题在StackOverflow上讨论过,我们将在关联Scroll View的时候再去解决它。

现在我们需要先解决另外一个问题:详情页是空白的,并且在点击菜单栏的时候,没有任何事件触发。

详情页没有切换

这完全在意料之中,因为还没有代码对container views进行关联。

对容器进行编码

在开始之前,先将MenuViewController.swift中的viewDidLoad()复制到DetailViewController.swift中,如下:

override func viewDidLoad() {
super.viewDidLoad()
// Remove the drop shadow from the navigation bar
navigationController!.navigationBar.clipsToBounds = true
}

这句代码的目的是不显示导航栏下面的那条阴影,虽然是个小细节,但就是这些细节使我们的App更加优雅。

当用户选中某个单元格时, MenuViewController必须要设置DetailViewController中的menuItem属性,但是这两个类已经没有直接联系了。所以,这两个类之间的通信将有ContainerViewController来代替。

ContainerViewController.swift的顶部添加DetailViewController属性:

private var detailViewController: DetailViewController?

ContainerViewController.swift中实现prepareForSegue(_:sender:)这个方法,并添加以下代码:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// 译者注:这里的"DetailViewSegue"设置接下来的描述中讲解(就是这段代码下面的这段话)
if segue.identifier == "DetailViewSegue" {
let navigationController = segue.destinationViewController as! UINavigationController
detailViewController = navigationController.topViewController as? DetailViewController
}
}

segue.identifier是啥?在将DetailViewController嵌入到容器时,需要在Attributes inspector中将Storyboard Embed SegueIdentifier设置为DetailViewSegue

设置Segue唯一标识

然后,声明menuItem属性,并在didSet中,在它进行赋值时,也给DetailViewController中的menuItem属性进行赋值。

var menuItem: NSDictionary? {
didSet {
if let detailViewController = detailViewController {
detailViewController.menuItem = menuItem
}
}
}

这已不再是table view cell与content view之间的交互了,而是用户选择菜单时,MenuViewController需要做出响应。

译者注:上面这句话纠结好久,翻译出来还是不通顺,原话是这样的:There’s no longer a segue from a table view cell to the content view, but MenuViewController needs to respond when the user selects an item.

删除MenuViewControllerprepareForSegue(:sender:)部分的代码,并且添加下面这段代码,注意,在选择方法的时候,不要选成tableView(:didDeselectRowAtIndexPath:)而造成麻烦。

// MARK: UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// 译者注:取消单元格选中
tableView.deselectRowAtIndexPath(indexPath, animated: true)
// 译者注:从menuItems数组中取出menuItem字典,里面的值是根据MenuItems.plist生成的,包含小图,大图,背景色
let menuItem = menuItems[indexPath.row] as! NSDictionary
// 译者注:当前的navigationController!.parentViewController就是ContainerViewController,因为navigationController的视图是作为80像素的Container View添加在ContainerViewController中的
(navigationController!.parentViewController as! ContainerViewController).menuItem = menuItem
}

这里,只是简单将基于选择的单元行设置了ContainerViewControllermenuItem属性。这将使属性的didSet方法触发(就是上面将menuItem赋值给detailViewController.menuItem的那段代码)。

最后,在MenuViewController.swiftviewDidLoad()方法中,添加以下代码:

(navigationController!.parentViewController as! ContainerViewController).menuItem =
(menuItems[0] as! NSDictionary)

这句代码将在App第一次加载的时候给detail view添加对应的图片。

运行程序,可以看到程序加载以后,详情页已经有了图片,菜单栏正常显示,并且在切换菜单的时候,详情页也能显示对应的图片:

运行效果

显示/隐藏菜单

为了达到菜单栏点击以后应该自动隐藏的目的,应该在单元行点击以后,设置Scroll View的水平位移为菜单栏的宽度,这样就可以完全展示出详情页。

首先,需要将Scroll ViewContainer View关联。

建立scrollView与ContainerViewController.swift的关联:在Storyboard的document outline中,选中Scroll View,打开Assistant Editor,按住Control键,从Scroll View拖到ContainerViewController.swift。然后在弹出框的Name一栏中将Scroll View命名为scrollView

关联Scroll View

这里,我设置了View\Assistant Editor\Assistant Editors on Bottom,所以Assistant Editor就会在Xcode窗口的底部(译者注:默认是在右边),这样我就不用在拖动关联时,跨越整个画布了。

同样的步骤,按住Control键,从Menu Container View拖到ContainerViewController.swift,创建一个menuContainerView

关联菜单视图

然后,给ContainerViewController.swift添加hideOrShowMenu(_:animated:)这个方法:

// MARK: ContainerViewController
func hideOrShowMenu(show: Bool, animated: Bool) {
let menuOffset = CGRectGetWidth(menuContainerView.bounds)
scrollView.setContentOffset(show ? CGPointZero : CGPoint(x: menuOffset, y: 0), animated: animated)
}

menuOffset的值就是Menu Container View宽度为80,如果showtrue,scrollView的偏移量就为0,这是菜单栏就会显示,同样,scrollView的偏移量为80,菜单栏就会隐藏。

现在,在menuItem的didSet中调用hideOrShowMenu(_:animated:)方法:

var menuItem: NSDictionary? {
didSet {
hideOrShowMenu(false, animated: true)
// ...

在用户点击单元行之后,应该关闭菜单栏,所以show置为false

同样,在viewDidLoad() 中调用hideOrShowMenu(_:animated:)方法,让程序启动时,菜单栏处于隐藏状态:

override func viewDidLoad() {
super.viewDidLoad()
hideOrShowMenu(false, animated: false)
}

运行程序,这时详情页显示的是笑脸那张图,并且菜单栏处于隐藏状态。滑出菜单,选择某个单元行,可以看到菜单栏会隐藏,并且详情页能显示出对应的图片与背景色。

当前效果

但是,之前因为Paging Enable出现的问题依然存在:如果滑出菜单栏,不选择单元行,然后滑动隐藏菜单栏时,菜单栏又会弹出来。这个问题可以通过实现UIScrollViewDelegate中的一个方法来解决。

ContainerViewController类声明的地方,遵守UIScrollViewDelegate协议:

class ContainerViewController: UIViewController, UIScrollViewDelegate {

然后,在ContainerViewController添加UIScrollViewDelegate的代理方法:

// MARK: - UIScrollViewDelegate
func scrollViewDidScroll(scrollView: UIScrollView) {
/*
Fix for the UIScrollView paging-related issue mentioned here:
http://stackoverflow.com/questions/4480512/uiscrollview-single-tap-scrolls-it-to-top
*/
scrollView.pagingEnabled = scrollView.contentOffset.x < (scrollView.contentSize.width - CGRectGetWidth(scrollView.frame))
}

这将在Scroll View的偏移量与菜单栏宽度相等的时候,禁用Paging Enable;这样,当菜单栏完全隐藏的时候,就会保持隐藏状态。当用户滑出菜单时,Paging Enable又会启用,从而菜单能一次性滑出。

运行程序,可以发现,这个问题已经解决了。

当前效果

看起来不错,但依然缺少一些东西。详情也的导航栏上少了汉堡按钮(The hamburger menu button,即有三条横线的菜单按钮),它能控制菜单的开关,并且还能随着菜单的显示而旋转。

添加菜单按钮

我们需要菜单按钮是自定义视图,这样才可以在菜单栏显示/隐藏的时候进行相应的旋转动画。

创建一个HamburgerView.swift,继承自UIView

在这个类中添加以下代码:

 class HamburgerView: UIView {

  let imageView: UIImageView! = UIImageView(image: UIImage(named: “Hamburger”))

  required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
} required override init(frame: CGRect) {
super.init(frame: frame)
configure()
} // MARK: Private private func configure() {
imageView.contentMode = UIViewContentMode.Center
addSubview(imageView)
} }

上面的代码重写了两个初始化方法,并调用了configure()来将imageView加载到父视图上。

DetailViewController.swift中添加hamburgerView属性:

var hamburgerView: HamburgerView?

viewDidLoad()中,给hamburgerView创建实例,并将它作为Navigation Barleft bar button,并添加一个点击手势。

let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: “hamburgerViewTapped”)
hamburgerView = HamburgerView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
hamburgerView!.addGestureRecognizer(tapGestureRecognizer)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: hamburgerView!)

hamburgerViewTapped() 方法需要触发ContainerViewController中的hideOrShowMenu(_:animated:)方法,但是问题是show应该传什么参数?所以ContainerViewController需要一个Bool值来记录菜单显示/隐藏的状态。

ContainerViewController.swift中添加以下属性:

var showingMenu = false

初始时菜单的显示状态是false,重写viewDidLayoutSubviews()方法,在bounds改变的时候来显示/隐藏菜单。

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
hideOrShowMenu(showingMenu, animated: false)
}

你将不再需要viewDidLoad()方法,所以将它从ContainerViewController.swift中删除。

打开DetailViewController.swift,添加以下代码:

func hamburgerViewTapped() {
let navigationController = parentViewController as! UINavigationController
let containerViewController = navigationController.parentViewController as! ContainerViewController
containerViewController.hideOrShowMenu(!containerViewController.showingMenu, animated: true)
}

如果showingMenu的值为false,菜单栏为隐藏状态,这是,当用户点击按钮,hideOrShowMenu(:animated:)方法就会被调用,show参数传入true,然后菜单显示。相反,当菜单处于显示状态时,即showingMenu值为true,这时用户点击按钮,菜单栏就会隐藏。因此,需要更新ContainerViewController.swifthideOrShowMenu(:animated:)方法的showingMenu属性。

hideOrShowMenu(_:animated:):中添加以下代码:

showingMenu = show

运行程序,尝试去滚动视图、点击菜单按钮、点击单元行这些操作。

当前运行效果

这存在一个问题:如果是采用滑出/滑入菜单栏的操作,那么菜单栏按钮要点两次,菜单栏才有响应。这是为什么?

出现的问题

这个问题出现的原因为:在滚动的后,showingMenu没有更改,菜单滑出的时候showingMenu还是false。当第一次点击的时候,showingMenu的值设置为true,所以菜单还是显示状态。第二次点击的时候,才能将showingMenu置为false,从而隐藏菜单。

要解决这个问题,需要在ContainerViewControllerUIScrollViewDelegate代理方法中将showingMenu设置一下,具体如下:

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let menuOffset = CGRectGetWidth(menuContainerView.bounds)
showingMenu = !CGPointEqualToPoint(CGPoint(x: menuOffset, y: 0), scrollView.contentOffset)
println(“didEndDecelerating showingMenu \(showingMenu)”)
}

当滚动结束以后,如果Scroll View的偏移量与菜单栏的宽度相同(即,菜单栏处于隐藏状态),那就将showingMenu设置为false。相反,则置为true。

运行程序,向右滑动,当滚动停止时,查看控制台打印信息。它的调用取决于Scroll View的滚动速度。当我在模拟器上运行时,只有慢慢的滚动,才会调用这个方法,但是当我在真机上运行时,它又需要快速滚动才能调用。

所以,将这段代码放入到scrollViewDidScroll(_:)方法中,这样可以更好的响应滚动。这个方法在用户滚动时就会调用,这也更加的可靠。

接下来就可以给菜单按钮加旋转动画了,但在此之前,我们先给菜单栏加3D翻转动画。

给菜单栏加立体效果

这个超级炫酷的动画看起来就像开门和关门。与此同时,菜单按钮也应该跟随菜单的状态,有一个平滑的旋转效果。

为了达到这个效果,我们将计算菜单滑出的比例(后文称为fraction),从而得到按钮应该旋转的角度。

ContainerViewController.swift中,添加一个私有方法来通过比例进行3D旋转动画:

func transformForFraction(fraction:CGFloat) -> CATransform3D {
var identity = CATransform3DIdentity
identity.m34 = -1.0 / 1000.0;
let angle = Double(1.0 - fraction) * -M_PI_2
let xOffset = CGRectGetWidth(menuContainerView.bounds) * 0.5
let rotateTransform = CATransform3DRotate(identity, CGFloat(angle), 0.0, 1.0, 0.0)
let translateTransform = CATransform3DMakeTranslation(xOffset, 0.0, 0.0)
return CATransform3DConcat(rotateTransform, translateTransform)
}

下面是transformForFraction(_:)这个方法的实现步骤:

  • 在菜单完全隐藏时,fraction = 0,完全显示时,fraction = 1;

  • CATransform3DIdentity是一个4*4的矩阵,对角线是1,其他是0;
  • CATransform3DIdentitym34属性是这个矩阵中的第三列第四行,它控制着变换中的立体程度;
  • CATransform3DRotate通过angle这个变量来控制y轴的旋转量:-90度呈现的是菜单栏垂直于屏幕的状态,0度呈现的是与xy轴平行的状态;
  • rotateTransform的旋转变换,是根据m34矩阵与y轴旋转量来的;
  • translateTransform设置了x轴的偏移量为菜单栏的一般;
  • CATransform3DConcatrotateTransformtranslateTransform联系起来,使得在做翻转动画时,也有偏移动画。

接下来,在scrollViewDidScroll(_:):中添加下面代码:

let multiplier = 1.0 / CGRectGetWidth(menuContainerView.bounds)
let offset = scrollView.contentOffset.x * multiplier
let fraction = 1.0 - offset
menuContainerView.layer.transform = transformForFraction(fraction)
menuContainerView.alpha = fraction

offset的值在0到1之间。当offset的值为0的时候,菜单栏处于显示状态;当offset为1的时候,菜单栏处于隐藏状态。

fraction是菜单显示部分所占的比例,范围在0到1之间,0是菜单栏完全隐藏,1是显示状态。

同时,fraction也用来调整菜单栏的alpha值,从暗到明,从隐藏到显示。

运行程序,滑动看一下这个3D效果,但是…菜单栏的变换关系有点问题(译者注:原文用的”hinge”这个词,这里的问题是菜单栏沿着中心轴在旋转),原因是菜单栏的锚点在中心点。

当前效果

为了让菜单栏沿着右边距旋转,需要在ContainerViewController.swiftviewDidLayoutSubviews()方法中添加以下方法:

menuContainerView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)

将锚点的x设为1,y设为0,这样就可以沿右边距旋转了。

运行程序,可以看到完美的3D旋转效果。

当前效果

最后一件事:给菜单按钮加旋转动画

菜单按钮动画将是整个App的点睛之笔。

当菜单栏隐藏的时候,按钮处于原有状态,当菜单栏显示的时候,按钮旋转90度。

技术上来讲,是按钮的图片在旋转,但是实际看起来的就效果,就像是按钮在旋转。

HamburgerView.swift中添加以下代码:

func rotate(fraction: CGFloat) {
let angle = Double(fraction) * M_PI_2
imageView.transform = CGAffineTransformMakeRotation(CGFloat(angle))
}

现在菜单按钮可以平滑的旋转了。通过fraction来计算应该旋转的角度,其中M_PI_2常量是定义在math.h头文件中的,表示pi/2。

添加以下代码到scrollViewDidScroll(_:)方法中,这样旋转角度可以和滚动偏移量结合起来:

if let detailViewController = detailViewController {
if let rotatingView = detailViewController.hamburgerView {
rotatingView.rotate(fraction)
}
}

运行程序,可以看到动画已经能很好的结合起来了。

最终效果

最后

可以在这里下载这个项目的最终版。

本文简单的实验了以下m34的3D旋转效果,如果想要了解更多3D变换,可以看Richard Turton的Visual Tool for CATransform3D

维基百科上也有一些文章,使用图片很好的解释了立体视觉这个概念。

同时,你也可以思考一下,还有那些3D效果可以用在你的App中,来提高用户交互体验。就像是菜单按钮这样的微妙的动画,细节的处理能很好的提高用户体验。

【译】仿Taasky的3D翻转菜单动画实现的更多相关文章

  1. 纯css3响应式3d翻转菜单

    前端开发whqet,csdn,王海庆,whqet,前端开发专家 周末快乐哈,今天来看一个纯CSS3实现的3d翻转菜单.3d响应式菜单,希望对大家有所帮助. 在线赞赏效果.在线编辑代码,或者下载收藏. ...

  2. WPF实现3D翻转的动画效果

    1.前端代码实现 1.1 原理见代码注析 <Grid MouseDown="Grid_MouseDown"> <Viewport3D> <Viewpo ...

  3. 纯CSS 3D翻转一个面(翻转导航菜单 立方体)

    在做练习的时候学到css的翻转导航菜单,原代码有点让人头疼,通过对其css的参数一点点研究了其实现过程. 这里推荐大家研究这个3D翻转动画的代码. 我的github:swarz,欢迎给老弟我++星星 ...

  4. 两个activity的3D翻转动画.md

    一.业务需求 这里在公司项目设计时,用到了一个小的需求,就是点击一个按钮然后整个activity的页面进行3d翻转; 二.设计思路 由于是2个activity的之间的翻转动画,就意味着前90度是A页面 ...

  5. 简单几步用纯CSS3实现3D翻转效果

    作为前端开发人员的必修课,CSS3翻转能带我们完成许多基本动效,本期我们将用CSS3实现hover翻转效果~ 第一步非常简单,我们简单画1个演示方块,为其 添加transition和transform ...

  6. 如何创建一个非常酷的3D效果菜单

    http://www.cocoachina.com/ios/20150603/11992.html 原文地址在这里.原文 去年,读者们投票选出了Top5的iOS7最佳动画,当然也很想看到有关这些动画如 ...

  7. CSS3——3D翻转相册

    transform属性和transition过渡属性,结合jQuery代码实现翻转功能. <!DOCTYPE html> <html lang="en"> ...

  8. CSS3之图片3D翻转效果(网页效果--每日一更)

    今天,带来的是纯CSS3的效果--图片3D翻转. 请看效果:亲,请点击这里 这个效果主要还是运用了CSS3的transform变形属性,与上个效果不同的是,这次并不是动画,所以没有采用animatio ...

  9. jquery仿天猫商城左侧导航菜单

    之前看到有博友写了一个仿天猫商城左侧导航菜单,可惜不提供免费下载,也没有代码.以前自己也写过类似的效果,只是都是一小块一小块的,现在重新拼凑.我将一步一步的实现拼凑过程,希望对你有所帮助. Demo在 ...

随机推荐

  1. 我的游戏蜗牛web前端面试经历

    蜗牛在江苏苏州地区应该算是比较大的互联网公司了,可以称得上中国游戏的鼻祖,之前一直很想进蜗牛,但作为一个应届毕业生却没有看到蜗牛发布任何关于招聘实习生的职位,无奈之下于是就毛遂自荐了,主动以邮件的形式 ...

  2. visual studio 2015.3 downloads

    https://www.visualstudio.com/zh-hans/downloads/ visual studio 2015.3 downloads http://download.micro ...

  3. 【转载】Lucene.Net入门教程及示例

    本人看到这篇非常不错的Lucene.Net入门基础教程,就转载分享一下给大家来学习,希望大家在工作实践中可以用到. 一.简单的例子 //索引Private void Index(){    Index ...

  4. 【转载】8天学通MongoDB——第八天 驱动实践

    作为系列的最后一篇,得要说说C#驱动对mongodb的操作,目前驱动有两种:官方驱动和samus驱动,不过我个人还是喜欢后者, 因为提供了丰富的linq操作,相当方便. 官方驱动:https://gi ...

  5. 基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面

    最近花了不少时间在重构和进一步提炼我的Web开发框架上,力求在用户体验和界面设计方面,和Winform开发框架保持一致,而在Web上,我主要采用EasyUI的前端界面处理技术,走MVC的技术路线,在重 ...

  6. .NET Core HtmlAgilityPack HTML解析利器

    最近学习.NET Core ,想把自己之前的一个项目升级到 .NET Core. 发现HtmlAgilityPack 没法进行引用,遂自己做了些修改,可以运行在 .NET Core 中.现在分享出来, ...

  7. How Will Java Technology Change My Life?

    How Will Java Technology Change My Life? We can't promise you fame, fortune, or even a job if you le ...

  8. WPF的一些总是记不住的Tips

    Margin(Left,Top,Right,Bottom); HorizontalAlignment(Height的Opposite,水平对齐方式):VerticalAlignment(Width的反 ...

  9. java.lang.NullPointerException org.apache.struts2.impl.StrutsActionProxy.getErrorMessage(StrutsActionProxy.java:69)

    采用SSH框架时出现了 java.lang.NullPointerException org.apache.struts2.impl.StrutsActionProxy.getErrorMessage ...

  10. luogg_java学习_03_流程控制及循环结构

    本文为博主辛苦总结,希望自己以后返回来看的时候理解更深刻,也希望可以起到帮助初学者的作用. 转载请注明 出自 : luogg的博客园 谢谢配合! 程序流程控制 顺序结构 分支结构:if-else,sw ...