Wireframe

UIDynamics 예제

예전에 관련된 내용을 작성한 적이 있는데, 여기서는 내용과 예제를 좀 더 보강한 버전이다. 또한 해당 예제들은 모두 Swift3 버전으로 작성되었다.

UIDynamicAnimator

다이내믹 애니메이터는 물리연산과 관련된 계산이나 애니메이션을 다이내믹 아이템에 적용하고, 그 결과로 계산된 애니메이션 컨텍스트를 제공한다.

개요

다이내믹 아이템UIDynamicItem 프로토콜을 따르는 임의의 클래스 인스턴스로, UIView, UICollectionViewLayoutAttributes 클래스는 이 프로토콜을 따르고 있다. (iOS7+) 커스텀 클래스를 디자인할 때 이 프로토콜을 따르도록하면, 회전이나 위치 변경에 대해서 애니메이터가 계산한 결과에 따라 반응하는 객체를 만들 수 있다.
동역학의 적용을 위해서는 최소 하나의 역학적 동작(dynamic behavior)을 구성해야 하며, 그것을 애니메이터에 연결해야 한다. iOS가 제공하는 동작의 종류로는 다음과 같은 것들이 있다.

암튼 큰 그림에서는 아래의 과정으로 동역학 애니메이션을 구성할 수 있다.

  1. UIDynamicItem 프로토콜을 따르는 객체가 있고
  2. 이 객체에 UIDynamicItemBehavior를 구성해준다음
  3. 다시 동작(behavior)을 애니메이터에 추가한다.

애니메이터가 개별 아이템과 상호작용하는 방식은 아래와 같다.

  1. 아이템에 동작을 추가하기전에 반드시 시작 위치, 회전값, 바운드를 설정해야 한다.
  2. 동작(behavior)을 애니메이터에 추가하면, 각 아이템들의 이러한 속성들은 애니메이터가 관리하게 되며, 애니메이션이 진행됨에 따라 각각의 속성값을 업데이트한다.
  3. 애니메이터가 아이템의 상태에 대한 제어를 되돌려주면, 그러한 속성값을 수동으로 변경할 수 있다. (updateItem(usingcurrentState:))

두 개 이상의 동작은 UIDynamicBehavior 객체의 자식 동작으로 붙여서 조합할 수 있다. 다시 이러한 조합세트는 다른 조합이나 다른 동작과 함께 재조합되는 것도 가능하다.

애니메이터

동역학 애니메이터를 고용하기 위해서는 먼저 애니메이트될 다이내믹 아이템들의 타입을 명시해야 한다. 이 선택은 어떤 초기화 메소드를 호출할 것인가를 결정하게되며, 다음으로는 좌표계가 어떻게 설정되어야 할 것인가를 결정한다.

애니메이터는 타입에 상관없이 다음의 특징을 공유한다.

상황

잠금화면의 카메라 아이콘을 탭하면, 잠금화면이 살짝 위로 들렸다가 아래로 떨어지며 콩 하고 되튀는 모습이 관찰된다. 이 구현은 UIKit Dynamics로 구현되어 있는데 다음과 같은 매커니즘으로 돌아가게끔 되어 있다.

  1. 탭하는 순간 배경화면 뷰에 UIPushBehavior가 적용되면서 순간적으로 뷰를 위로 밀어 올린다.
  2. 배경화면 뷰에는 UIGravityBehavior가 적용되어 있어서 올라간 뷰는 자연스럽게 아래로 다시 내려온다.
  3. 배경화면 뷰는 스크린 가장자리와 충돌하게끔 되어 있어서 충분한 속도를 가지고 내려온 뷰는 다시 위로 튀어오른다.
  4. 2~3을 반복하면서 뷰는 점점 속도를 잃고 최종적으로 멈춘다.

중력과 충돌


class GravityView: UIView { lazy var squareView: UIView = { [unowned self] in let sv = UIView(frame: CGRect(x:100, y:100, width:100, height:100)) self.view.addSubView(sv) return sv }() lazy var gravity = { [unowned self] in return UIGravityBehavior(items: [self.squareView]) }() lazy var animator: UIDynamicAnimator = { [unowned self] in return UIDynamicAnimator(referenceView: self.view) }() override init(frame: CGRect) { super.init(frame:frame) squareView.backgroundColor = UIColor.blueColor() animator.addBehavior(gravity) } }

여기까지 코딩하면 뷰가 로드되면 파란색 서브뷰는 중력의 영향으로 아래로 떨어지게 된다. 다음 코드는 뷰의 테두리에 아이템들이 충돌하게끔하는 장치를 더한다.


// in class GravityAndCollisionViewController lazy var collision: UICollisionBehavior = { [unowned self] in let cl = UICollisionBehavior(items:[self.squareView]) return cl } override init(frame: CGRect) { //... collisioncl.translatesReferenceBoundsIntoBoundary = true animator.addBehavior(collision) }

만약 콩콩 튀는 효과를 주고싶다면, 별도의 커스텀 Behavior를 더해준다.

    override init(frame: CGRect) {
        // ...
        let itemBehavior = UIDynamicItemBehavior(items: [squareView])
        itemBehavior.elasticity = 0.8
        animator.addBehavior(itemBehavior)
    }

스냅

UISnapBehavior는 특정 지점에 용수철이 달린 것처럼 날아가서 고정되는 동작을 의미한다. 여기서는 탭한 위치에 뷰가 날아가서 붙는 동작을 만들어보자.


import UIKit class SnapView: UIView { required init?(coder aDecoder: NSCoder){ super.init(coder:aDecoder) } lazy var animator: UIDynamicAnimator = { [unowned self] in let anim = UIDynamicAnimator(referenceView: self) return anim }() var snap: UISnapBehavior? lazy var squareView: UIView = { let s = UIView(frame: CGRect(x:100, y:100, width:100, height:100)) s.backgroundColor = UIColor.blue() return s }() override init(frame: CGRect){ super.init(frame:frame) addSubView(squareView) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { let tapPoint = touch.location(in: self) if snap != nil { animator.removeBehavior(snap!) } snap = UISnapBehavior(item: squareView, snapTo: tapPoint) animator.addBehavior(snap!) } } }

이를 응용하면 특정 뷰를 잡아서 이동했다가, 손을 떼는 시점에 원 위치로 띠용하고 돌아가게 하는것도 가능하겠다.

Attatch

UIAttachmentBehavior는 두개의 아이템 혹은 하나의 아이템과 다른 한 점이 딱딱한 막대기로 연결되어 있는 것처럼 행동하게 한다. 만약 고정점이 있고, 뷰가 고정점에 연결돼 있다면 고정점을 움직이면 해당 뷰가 같이 움직이게 된다. 뷰에 중력을 적용시켜 놓으면 마치 진자가 된 것 같이 뷰가 움직이게도 할 수 있다.


import UIKit class AttachView: UIView { required init?(coder aDecoder: NSCoder){ super.init(coder:aDecoder) } lazy var animator: UIDynamicAnimator = { [unowned self] in let anim = UIDynamicAnimator(referenceView: self) return anim }() lazy var squareView: UIView = { let s = UIView(frame: CGRect(x:100, y:100, width:100, height:100)) s.backgroundColor = UIColor.blue() s.layer.cornerRadius = 28.0 return s }() lazy var gravity = { [unowned self] in return UIGravityBehavior(items: [self.squareView]) }() lazy var attach: UIAttatchmentBehavior = { [unowned self] in let a = UIAttatchmentBehavior(item: self.squareView, toAnchorPoint: CGPoint(x:300, y:0)) return a }() override init(frame: CGRect){ super.init(frame:frame) addSubView(squareView) animator.addBehavior(attach) animator.addBehavior(gravity) } }

밀기

UIPushBehavior는 두 가지 모드를 가지고 있는데 하나는 지속적으로 힘을 가하는 것이고 다른 하나는 한순간에 힘만 주는 것이다. 전자의 경우에는 주어진 힘의 방향으로 가속되는 동작을 만들게 된다.


class PushView: UIView { var redView: UIVIew = { let v = UIView(frame: CGRect(x:40, y:100, width:50, height:50)) v.backgroundColor = UIColor.red() return v }() var blueView: UIView = { let v = UIView(frame: CGRect(x:220, y:100, width:50, height: 50)) v.backgroundColor = UIColor.blue() return v }() let continuousPush: UIPushBehavior = UIPushBehavior(items:[redView], mode:.Continuous) let instantaneousPush: UIPushBehavior = UIPushBehavior(items:[blueView], mode:.instantaneous) override init(frame: CGRect){ super.init(frame:frame) addSubView(redView) addSubView(blueView) animator( }
Exit mobile version