本文是我学习《iOS Animations by Tutorials》 笔记中的一篇。 文中详细代码都放在个人Github上 andyRon/LearniOSAnimations。html
自动布局(Auto Layout) 在iOS 6中首次推出,已经存在了一段时间,每次发布新版本的iOS和Xcode都经历了一系列成功的迭代。ios
自动布局背后的核心理念很是简单:它容许您根据布局中的每一个元素之间建立的关系来定义应用程序的UI元素的布局。git
咱们日常开发时已将自动布局用于静态的布局,在本文中将学习使用约束来设置动画。github
本章节是用自动布局完成下一章节须要使用的项目Packing List 。关于自动布局,可参考我以前的文章开始用Swift开发iOS 10 - 3 介绍Auto Layout,这里就不重复了。swift
约束动画(Animating Constraints)并不比属性动画困难; 它只是有点不一样。 一般,只需使用新约束替换现有约束,而后让Auto Layout为两个状态之间的UI设置动画就能够了。数组
开始项目Packing List大概以下:bash
在ViewController
中添加约束接口:闭包
@IBOutlet weak var menuHeightConstraint: NSLayoutConstraint!
复制代码
并让它与导航栏视图的高度约束关联:app
在右上角加号按钮的Action方法actionToggleMenu()
中添加:ide
isMenuOpen = !isMenuOpen
menuHeightConstraint.constant = isMenuOpen ? 200.0 : 60.0
titleLabel.text = isMenuOpen ? "Select Item" : "Packing List”
复制代码
点击加号按钮后导航栏高度变大,而且title变化。
继续在actionToggleMenu()
添加布局变化的弹簧动画:
UIView.animate(withDuration: 1.0, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 10.0, options: .curveEaseIn, animations: {
// 强制更新布局
self.view.layoutIfNeeded()
}, completion: nil)
复制代码
在menuHeightConstraint.constant = isMenuOpen ? 200.0 : 60.0
已经更新了约束值,但iOS尚未机会更新布局。经过从动画闭包中调用layoutIfNeeded()
强制更新布局,能够设置布局中涉及的每一个视图的中心和边界。好比table view也随着Menu的收缩或增大而收缩或增大,这就是约束的效果,如今至关于一次设置两个动画😊。
效果:
让+
旋转45°变成x
在上面的动画闭包中添加:
let angle: CGFloat = self.isMenuOpen ? .pi/4 : 0.0
self.buttonMenu.transform = CGAffineTransform(rotationAngle: angle)
复制代码
直接用可视化的方式为视图约束添加代码接口(outlet)是相对简单的方式。有的时候不方便在Interfa Builder使用Control-drag方式添加接口或者不方便添加有太多outlet,这时能够利用UIView
提供的constraints
属性,它是当前视图全部约束的数组。
好比下面代码:
titleLabel.superview?.constraints.forEach { constraint in
print("-> \(constraint.description)\n")
}
复制代码
打印结果:
-> <NSLayoutConstraint:0x600002d04320 UIView:0x7ff7df530c00.height == 200 (active)>
-> <NSLayoutConstraint:0x600002d02210 UILabel:0x7ff7df525350'Select Item'.centerX == UIView:0x7ff7df530c00.centerX (active)>
-> <NSLayoutConstraint:0x600002d02a30 UILabel:0x7ff7df525350'Select Item'.centerY == UIView:0x7ff7df530c00.centerY + 5 (active)>
-> <NSLayoutConstraint:0x600002d02d00 H:[UIButton:0x7ff7df715d20'+']-(8)-| (active, names: '|':UIView:0x7ff7df530c00 )>
-> <NSLayoutConstraint:0x600002d030c0 UIButton:0x7ff7df715d20'+'.centerY == UILabel:0x7ff7df525350'Select Item'.centerY (active)>
复制代码
看上去有点乱,不过仔细看仍是能看出有五个约束分别对应于:
在 actionToggleMenu()
的isMenuOpen = !isMenuOpen
下添加:
titleLabel.superview?.constraints.forEach { constraint in
if constraint.firstItem === titleLabel && constraint.firstAttribute == .centerX {
constraint.constant = isMenuOpen ? -100.0 : 0.0
return
}
}
复制代码
约束表达式的通用形式以下:
firstItem.firstItemAttribute == secondItem.secondItemAttribute * multiplier + constant
复制代码
对应于 NSLayoutConstraint
的各类属性,名字看着很明显,其中==
对应于属性relation
,固然也能够是<=
、>=
等。
实际例子:
Superview.CenterX = 1.0 * UILabel.CenterX + 0.0
复制代码
这边的效果:
每一个约束能够添加 Identifier
属性,在代码中就能够经过 Identifier
获取这个约束。
继续在上面的约束后添加:
if constraint.identifier == "TitleCenterY" {
constraint.isActive = false
let newConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: titleLabel.superview!, attribute: .centerY, multiplier: isMenuOpen ? 0.67 : 1.0, constant: 5.0)
newConstraint.identifier = "TitleCenterY"
newConstraint.isActive = true
return
}
复制代码
新加的约束能够表示为Title.CenterY = Menu.CenterY * 0.67 + 0.0
,图示:
运行后效果:
在 actionToggleMenu()
中添加:
if isMenuOpen {
slider = HorizontalItemList(inView: view)
slider.didSelectItem = { index in
print("add \(index)")
self.items.append(index)
self.tableView.reloadData()
self.actionToggleMenu(self)
}
self.titleLabel.superview!.addSubview(slider)
} else {
slider.removeFromSuperview()
}
复制代码
HorizontalItemList
是自定义的一个UIScrollView
子类,用于menu中左右滚动的视图,
当点击TableView的cell时,会调用showItem(_:)
,在这个方法中添加:
// 点击后创造图片
let imageView = UIImageView(image: UIImage(named: "summericons_100px_0\(index).png"))
imageView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
imageView.layer.cornerRadius = 5.0
imageView.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
复制代码
添加约束代码:
let conx = imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
复制代码
此方法使用新的NSLayoutAnchor
类,这使得建立常见约束很是容易。 在这里,您将在图像视图的中心x锚点和视图控制器的视图之间建立约束。
添加图片底部约束:
let conBottom = imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: imageView.frame.height)
复制代码
此约束设置图像视图的底部以匹配视图控制器视图的底部,加上图像高度; 这会将图像定位在屏幕底部边缘以外,这将做为动画的起点。
添加图片宽度约束:
let conWidth = imageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.33, constant: -50.0)
复制代码
这将图像宽度设置为屏幕宽度的1/3减去50磅。 目标尺寸是屏幕的1/3; 你将动画50磅的差别,使图像“成长”到位。
最后,添加高度和宽度相等约束,并激活上面全部约束:
let conHeight = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor)
NSLayoutConstraint.activate([conx, conBottom, conWidth, conHeight])
复制代码
此时点击TableView的Cell,只能看到下面:
在showItem(_:)
添加:
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, animations: {
conBottom.constant = -imageView.frame.size.height/2
conWidth.constant = 0.0
self.view.layoutIfNeeded()
}, completion: nil)
复制代码
可是此时的效果是:
**想想:**添加了一个视图,设置了一些约束,而后改变了这些约束并设置了布局变化的动画。 可是,视图从未有机会执行其初始布局,所以图像从其左上角的(0, 0)
的默认位置开始🙄。
要解决此问题,只要在动画开始以前进行初始布局,在动画前添加:
view.layoutIfNeeded()
复制代码
效果变成从下面上来:
上面的弹出图片会重叠在一块儿,下个图片出来以前,须要把上一个图片移出。
在以前的代码下添加:
UIView.animate(withDuration: 0.8, delay: 1.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, animations: {
conBottom.constant = imageView.frame.size.height
conWidth.constant = -50.0
self.view.layoutIfNeeded()
}) { (_) in
imageView.removeFromSuperview()
}
复制代码
效果为:😝
本文在个人我的博客中地址:系统学习iOS动画之二:自动布局动画