iOS 7 : UIDynamic의 이해

iOS 7에서 UIKit 에는 UIDynamic이라는 새로운 기술이 도입되었다. 이는 뷰에 대해 강체 물리(rigid-object physics)를 적용한 애니메이션을 구현할 수 있도록 한다. 뷰가 중력의 영향으로 떨어지거나 다른 뷰에 충돌하는 효과를 표현할 수 있다. 이는 이전에는 매우 복잡한 알고리듬을 적용하여 표현할 수 있었으나, UIDynamicItem은 매우 간단히 구현할 수 있다.

내용을 재작성한 별도의 포스팅이 있으니, 본 글보다는 새 글을 참고해주세요.

[원글보기]

사각형의 뷰를 만들고 여기에 패닝인식기(UIPanGestureRecognizer)를 붙여, 끌고 다닐 수 있도록 하며, 뷰에서 손을 땠을 때 아래로 떨어지는 효과를 구현해보도록 하자.

UIDynamicAnimator

UIDynamic은 네 가지 요소의 조합으로 구성되는데,

  • **reference view** – 애니메이터가 좌표를 표시하는 기준뷰. 보통은 부모뷰를 쓴다.
  • **dynamic animator** – 실제 좌표 계산을 담당함
  • **dynamic behavior** – 좌표 계산 방식(동작방식)과 영향을 받은 아이템들
  • **dynamic item** – 애니메이팅에 참가하는 아이템들. 서브 뷰의 부분집합이다.**

이 중에서 가장 핵심이 되는 객체가 바로 dynamic animator(UIDynamicAnimator, 이하 다이내믹 애니메이터)이다. 다이내믹 애니메이터는 참조뷰를 기준으로 생성되는데, 참조뷰는 특별한 역할을 한다기 보다는 애니메이터가 연산을 수행하여 애니메이션을 표현할 때 기준이 되는 좌표계를 제공할 뿐이다.

애니메이터가 어떤 식으로 애니메이션을 구현할 것인가는 애니메이터에 어떤 행동양식(behavior)이 추가되었는지에 따라 다르다. 만약 물체가 중력의 영향으로 아래로 떨어지는 애니메이션을 만들고자 한다면 UIGravityBehavior를 추가하면 되고, 물체들의 충돌을 시뮬레이팅하려면 UICollisonBehavior를 추가해주면 된다.

행동양식(behavior, UIDynamicBehaivor)은 물리적 특성 중 각각의 일면을 고려한 애니메이팅 조건이라 보면 된다. 행동양식은 해당 행동 양식이 적용될 UIDynamicItem 들을 가지고 생성할 수 있다. 이 아이템들은 UIDynamicItem 프로토콜을 따라야 하는데, 기본적으로 UIView에 이 프로토콜이 구현되어 있다.

최종적으로 행동양식을 애니메이터에 추가하면 애니메이터는 자동으로 애니메이션을 시작한다.

구현 절차

  • 뷰를 중심으로 애니메이터 생성
  • 애니메이션될 객체들을 사용해 행동양식 생성
  • 행동 양식을 애니메이터에 추가

이렇게만 해주면 된다.

아래 코드에서 서브 뷰는 인터페이스빌더를 통해 스토리보드상에 만들어둔 것으로 가정한다.

#import "MYViewController.h"

@interface MYViewController ()
@property (weak, nonatomic) IBOutlet UIView *draggableView;
@property (strong, nonatomic) UIPanGestureRecognizer *panRecognizer;
// 애니메이터 및 행동양식 객체를 프로퍼티에 추가
@property (strong, nonatomic) UIDynamicAnimator  *animator;
@property (strong, nonatomic) UIGravityBehavior *gravity;
@property (strong, nonatomic) UICollisionBehavior *collision;
-(void)pan:(UIGestureRecognizer *)sender;
@end

@implementation MYViewController
- (void)viewDidLoad {
    self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.draggableView addGestureRecognizer:self.panRecognizer];

    // 애니메이터 및 행동양식 인스턴스 초기화    
    // 1
    self.animator = [[UIDynamicAnimator alloc] initWithReferencingVIew:self.view];
    // 2
    self.gravity = [[UIGravityBehavior alloc] initWithItems:@[self.draggableView]];
    // 3
    self.collision = [[UICollisionBehavior alloc] initWithItems:@[self.draggableView]];
    // 애니메이터 적용
    // 4
    [self.animator addBehavior:self.gravity];
    [self.animator addBehavior:self.collision];

}

-(void)pan:(UIGestureRecognizer *)sender {
    CGPoint translation = [sender translationInView:self.view];
    sender.view.center = CGPointMake(sender.view.center.x + translation.x, sender.view.center.y + translation.y);
    [sender setTranslation:CGPontMake(0, 0) inView:self.view];
}
  1. animator 는 뷰 컨트롤러의 메인 뷰를 기준으로 생성했다.
  2. 서브뷰인 draggableView 는 중력의 영향을 받아 아래로 떨어진다.
  3. 그와 동시에 draggableView는 메인뷰의 바닥에 충돌하기도 할 것이다.
  4. 2, 3에서 만든 behavior들을 애니메이터에 적용해준다.

이렇게 하고 빌드해보면 뷰가 자연스럽게 낙하하는 것을 볼 수 있다. 문제는 바닥에 충돌할 물체가 없는 관계로, 그냥 쑥하고 빠져버린다는 것인데, 충돌의 경계선이 없어서 그렇다. 충돌(UICollisionBehavior)은 다이내믹 아이템 사이의 충돌을 인식하는데, 눈에 보이지 않는 경계선을 만들어서 거기에도 충돌하도록 할 수 있다. 바닥에 충돌선을 하나 깔아서 물체가 빠지지 않도록 해보자.

위의 “// 3” 부분 아래에 다음 코드를 추가한다.

[self.collision addBoundaryWithIdentifier;@"bottom" 
        fromPoint:CGPointMake(0, self.view.frame.size.height)
        toPoint:CGPointMake(self.view.frame.size.width, self.view.frame.size.height)];

그리고 위에 붙어 있는 뷰에 부딪친 경우에도 충돌이 일어나도록 설정하자.

이제 다시 빌드해보면 떨어진 물체가 바닥에 탕하고 부딪치는 걸 볼 수 있다. 그리고 이 벽돌(?)은 드래그가 가능한데, 집어서 위로 들어올리면 더 이상 애니메이션이 일어나지 않는다.

이는 애니메이터가 추적하던 위치 및 상태 값이 유실되기 때문인데, 따라서 패닝이 끝나는 시점에 다시 위치 정보를 업데이트하여야 한다.

-(void)pan:(UIGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateEnded) {
        [self.animator updateItemsUsingCurrentState:self.draggableView];
    }
    CGPoint translation = [sender translationInView:self.view];
    sender.view.center = CGPointMake(sender.view.center.x + translation.x, sender.view.center.y + translation.y);
    [sender setTranslation:CGPontMake(0, 0) inView:self.view];
}

Wrap Up

다시 한 번 요약하자.

  1. UIDynamics의 애니메이션을 위한 모든 좌표 계산을 담당하는 실제 엔진은 UIDynamicAnimator이다.
  2. animator는 계산의 대상이 될 behavior들이 필요하다. 이 behaviorUIDynamicItemBehavior의 서브 클래스들을 사용해서 만든다.
  3. UIDynamicItemBehavior는 해당 동작을 수행할 아이템(UIView)들이 필요한데, 이것은 behavior를 만들 때 같이 지정해버린다.

화려한 것까지는 아니지만, UI요소에 현실적인 물리 애니메이션을 덧붙여서 디테일한 효과를 노리고 싶을때 사용하는 것이 좋으며, 본격적인 화려한 효과는 SpriteKit을 쓰는 편이 낫다.

아래는 중간에 장애물을 하나 더 붙여서 구현한 예제이다.