跟着斯坦福白胡子老头学UIDynamic动画的技巧
leonx918
8年前
<p>UIDynamic是从iOS 7开始引入的一种新技术,隶属于UIKit框架可以认为是一种物理引擎,能模拟和仿真现实生活中的物理重力、弹性碰撞、附着行为、捕捉行为、推动行为和动力元素等现象。</p> <p><img src="https://simg.open-open.com/show/da47edc2f468b5ed3c6dead3b90a6b1d.gif"></p> <p>DropIt.gif</p> <p>首先要在stroyboard里添加一个UIView, 并设置constraint跟父View边界相同(点击Reset to Suggested Constraints), 即充满屏幕。</p> <p>我从这个Demo里总结了一些实际编码中可以用到的技巧, 供大家参数。</p> <p>技巧1: 如果需要实现extension, 可以新建一个Swift文件, 一个类/枚举/结构体的所有扩展都放在同一个文件里(例如在A文件里扩展UIView,在B文件也扩展UIView, 这个语法不好维护,对同一个数据结构的扩展要放在一起!); 扩展在整个程序内都有效;示例代码:</p> <pre> <code class="language-objectivec">extension UIView { //根据坐标判断对应的UIView func hitTest(p: CGPoint) -> UIView? { <strong>return</strong> hitTest(p, with: nil) } } extension UIBezierPath { //静态函数, 画直线 <strong>class</strong> func lineFrom(from: CGPoint, to: CGPoint) -> UIBezierPath { let path = UIBezierPath() path.move(to: from) path.addLine(to: to) <strong>return</strong> path } }</code></pre> <p>技巧2:得到随机数, 在Swift语言里使用arc4random()方法,实际编码中建议使用扩展语法实现。 示例代码是CGFloat, 还可以是Int、Double等等。</p> <pre> <code class="language-objectivec">extension CGFloat { /<em>* </em> 返回范围内的随机数 <em> @param max, 最大值 </em>/ static func random(max: Int) -> CGFloat { return CGFloat(arc4random() % UInt32(max)) } }</code></pre> <p>技巧3: UIView是所有界面空间的基类, 能添加子UIView(这点跟Andriod的View不同); 注意父UIView可以添加子UIView, 但是需要使用子UIView的方法才能移除(这点跟Android的ViewGroup不同)。</p> <pre> <code class="language-objectivec">let drop = UIView(frame: frame) //创建一个UIView addSubview(drop) //添加drop到当前UIView里 drop.removeFromSuperview() //drop从父UIView中移除, 考虑一下drop执行了哪些生命周期函数?</code></pre> <p>技巧4: 使用闭包语法为变量赋初值, lazy关键字为懒加载,即运行时调用了animator变量后才会执行闭包代码。</p> <pre> <code class="language-objectivec"><strong>private</strong> lazy var animator: UIDynamicAnimator = { let animator = UIDynamicAnimator(referenceView: self) animator.delegate = self <strong>return</strong> animator }()</code></pre> <p>技巧5: 监听属性变化didSet/WillSet事件( <strong>观察者模式</strong> )并添加对应逻辑。 还记得前面博文提到的两个类相互引用的问题么,使用weak关键字解开闭环;注意下面代码, 如果在闭包里使用了self, 那么外部类实例和闭包之间形成了相互引用的关系, 这时需要 <strong>使用[unowned self]避免内存泄漏。</strong></p> <pre> <code class="language-objectivec"><strong>private</strong> var attachment: UIAttachmentBehavior? { willSet { <strong>if</strong> attachment != nil { ... //attachment值变化前,做逻辑 } } didSet { <strong>if</strong> attachment != nil { ... //attachment值变化后,做逻辑 attachment!.action = {[unowned self] in <strong>if</strong> let attachedrop = self.attachment!.items.first as? UIView { self.bezierPaths["line"] = UIBezierPath.lineFrom(from: (self.attachment?.anchorPoint)!, to: attachedrop.center) } } } } }<</code></pre> <p>技巧6: if逻辑判断需要where关键字的功能, 这时要使用逗号。 下面示例代码的意思是dropToAttachTo不是nil时才执行后面的语句dropToAttachTo.superview != nil , 如果条件都满足则进入代码块。</p> <pre> <code class="language-objectivec"><strong>if</strong> let dropToAttachTo = lastDrop, dropToAttachTo.superview != nil { attachment = UIAttachmentBehavior(item: dropToAttachTo, attachedToAnchor: gesturePoint) }</code></pre> <p>技巧7:对应Optional参数类型,即值可能为nil。 在 <strong>Java</strong> 语法里要写一堆的判空,语句间使用&&连接; Swift3.0省略了判空操作,再也不用写蛋疼的判空语句了。</p> <p>请问下面的语句会崩溃吗?</p> <pre> <code class="language-objectivec">attachment = nil attachment?.anchorPoint = gesturePoint //attatchment后面是问号,说明他是Optional类型</code></pre> <p>答: 不会!</p> <p>翻译一下: 如果attachment等于nil则不调用.后面的参数, 如果attachment有值则调用后面的参数。扩展一下可以是这样: attachment?param1?param2?param3?.someValue , 如果用Java写这条语句要被累死!!!</p> <p>技巧8: 自定义UIView绘制若干个图形时,可以使用Dictionary数组。 注意setNeedsDisplay函数类似于 <strong>Android</strong> 的invalidate,相当于设置个逻辑判断参数为true,UIView会在 <strong>下个绘制周期</strong> 时调用drawRect函数; 自定义UIView定义一个数组, 在drawRect函数里遍历并绘制。</p> <pre> <code class="language-objectivec">var bezierPaths = <a href="">String:UIBezierPath</a> { didSet { setNeedsDisplay() //触发刷新 } } override func draw(<em> rect: CGRect) { <strong>for</strong> (</em>, path) in bezierPaths { path.stroke() //画线 } }</code></pre> <p>代码下载地址:</p> <p><a href="/misc/goto?guid=4959732835648936153" rel="nofollow,noindex">https://github.com/brycegao/DropIt#dropit</a></p> <p> </p> <p>来自:http://www.jianshu.com/p/d115c941c2ab</p> <p> </p>