이전에 작성했던 UIDynamics 데모를 재작성했다.
- swift3 문법을 적용했고,
- 별도의 Xcode 프로젝트가 아니라 Playground 용으로 만들어서 바로 확인할 수 있게 했다.
UIDynamics를 적용하는 방법은 UIDynamicAnimator 객체를 만들고 여기에 애니메이션에 고려될 물리학적 요소 (중력이나 마찰등)를 behaivor로 설정해주면 된다. 애니메이터는 기준이 되는 레퍼런싱 뷰를 참조하면서 생성되고, 이후 해당 뷰 내의 뷰 계층 구조에 대해서 동역학을 계산하게 된다.
class PlayViewController: UIViewController {
// 손을 끌고 다닐 뷰
lazy var draggableView: UIView = {
let v = UIView(frame: CGRect(x:40, y:40, width:50, height: 50))
v.backgroundColor = UIColor.blue()
return v
}()
// 이동가능한 뷰를 위한 제스쳐 인식
lazy var pan: UIPanGestureRecognizer = { [unowned self] in
let _pan = UIPanGestureRecognizer(target: self,
action: #selector(self.panned(_:)))
return _pan
}()
// UIDynamicAnimator는 느긋하게 초기화한다.
lazy var animator: UIDynamicAnimator = { [unowned self] in
let anim = UIDynamicAnimator(referencingView: self.view)
return anim
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(draggableView)
draggableView.addGestureRecognizer(pan)
// 중력, 충돌 behavior 생성
let gravity: UIGravityBehavior = {
let g = UIGravityBehavior(items: [draggableView])
return g
}()
let collision: UICollisionBehavior = {
let c = UICollisionBehavior(items: [draggableView])
c.translatesReferenceBoundsIntoBoundary = true
c.collisionMode = .boundaries
// 화면 바닥면에
let bottomCoord = view.bounds.size.height
c.addBoundary(withIdentifier:"block",
from: CGPoint(x:0, y:bottomCoord)
to: CGPoint(x:view.bounds.size.width, y:bottomCoord))
return c
}()
animator.addBehavior(gravity)
animator.addBehavior(collision)
}
/// 패닝이 일어날 때마다 호출되는 핸들러
func panned(_ sender: UIPanGestureRecognizer) {
// 터치가 끝날 때 애니메이터가 draggableView를 재계산에 포함하도록 한다.
if (sender.state == .ended) {
animator.updateItem(usingCurrentState: draggableView)
}
// 매 이동 순간의 offset을 계산하여 뷰의 위치를 바꾼다.
let translation = sender.translation(in:view)
if let sview = sender.view {
sview.center = CGPoint(x: sview.center.x + translation.x,
y: sview.center.y + translation.y)
sender.setTranslation(CGPoint.zero)
}
}
}
let vc = PlayViewController()
PlaygroundPage.current.liveView = vc.view
PlaygroundPage.current.needsIndefiniteExecution = true