2014년 4월 5일 토요일

[iOS7] UIKit Dynamics 사용법

UIKit Dynamics는 iOS7에서 추가된 것입니다.

iOS의 메시지에서 위로 올리다 보면, 마지막 부분에 튕기듯이 멈추는 부분과, Lock Screen에서 카메라 아이콘을 터치하거나, 위로 들었다가, 아래로 내리면 튕기 듯이 반응 하는 것이 UIKit의 Dynamics에서 지원하는 기능입니다.

 - UIKit Dynamics는 UIKit에 통합된 완전한 물리엔진 입니다. 즉, UIKit의 객체에 gravity, attachments, forces와 같은 동작(behavior)를 추가하여 현실처럼 느낄 수 있는 인터페이스를 만들 수 있게 지원하고 있습니다.
 - 물리특성을 정의하면, dynamics엔진이 알아서 처리를 해주고 있습니다.
 - iOS7 전에는 물리적인 시뮬레이션을 위해서, 다른 라이브러리를 사용해야 했었는데, iOS7에서는 간단한 물리적 시뮬레이션을 UIKit에서 해주는 것입니다.
 - 또한, CollectionView에서 아이콘들의 행동을 규정할 수 있습니다. 뭔가 추가되면, 다른 것들이 옆으로 흔들리게 만들 수 있습니다.

버튼들이 화면에 있다가 특정 순간이 되면, 아래로 떨어지도록 할 수도 있습니다.
이것을 가능하게 하는 것이 iOS7의 UIKit Dynamics입니다.


Dynamics는 UIKit내부에 내장되어 있으므로, 별도의 Framework를 import 할 필요가 없습니다.
위에 그림에서 처럼 Button 두개와, TextField를 움직이도록 해 보겠습니다.
먼저 Single View Application을 만들고, storyboad에서 위와 같이 객체를 등록하고, 각각 Property를 설정합니다.

UIDyanmicsAnimator를 현재 Main으로 등록된 View를 레퍼런스로 만듭니다.
Source Code

@interface DBUViewController ()

//아래로 떨어질 TextField
@property (strong, nonatomic) IBOutlet UITextField *textField; 
//아래로 떨어질 Button
@property (strong, nonatomic) IBOutlet UIButton *fallingButton; 
//스프링처럼 매달려 있을 Button
@property (strong, nonatomic) IBOutlet UIButton *danglingButton; 
//매달려 있을 버튼이 고정되어 있는 곳
@property (strong, nonatomic) IBOutlet UIView *redSquare; 
//버튼이 터치되면 결과를 알려줄 곳
@property (strong, nonatomic) IBOutlet UITextView *resultTextView; 

// 여기에 행동(Behavior)를 등록합니다.
@property (strong, nonatomic) UIDynamicAnimator *animator; 
//아래로 중력 행동을 만들것.
@property (strong, nonatomic) UIGravityBehavior* gravityBehavior; 

//원래 자리로 돌아가는 스냅 행동을 정의 돌아갈 위치가 달라지므로, 두개가 필요합니다.
@property (strong, nonatomic) UISnapBehavior * textFieldSnapBehavior; 
// 버튼 자리로 돌아갈 스냅 행동
@property (strong, nonatomic) UISnapBehavior *fallingButtonSnapBehavior;
@end

@implementation DBUViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    UIAttachmentBehavior *springBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.danglingButton offsetFromCenter:UIOffsetMake(/*20.0f*/0, /*self.danglingButton.frame.size.height/2*/0) attachedToAnchor:CGPointMake(self.redSquare.center.x, self.redSquare.center.y)];
    [springBehavior setFrequency:1.0f];
    [springBehavior setDamping:0.1f];
    
    [self.animator addBehavior:springBehavior];
}

시작버튼을 누르면, Gravity Behavior를 만들고, 등록을 시킨다.
source code
- (IBAction)startButton:(UIButton *)sender
{
    //gravity behavior가 없는 경우 새로 만들고 등록한다.
    if (!self.gravityBeahvior) {
        [sender setTitle:@"Stop" forState:UIControlStateNormal];
        [self.animator removeBehavior:self.textFieldSnapBehavior];
        [self.animator removeBehavior:self.fallingButtonSnapBehavior];
        
        //중력 행동을 만들때, 어느 Item을 행동에 넣을지 넣습니다.
        self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[self.fallingButton, self.textField]];
        //만들고 난 다음에 추가할 수도 있습니다.
        [self.gravityBeahvior addItem:self.danglingButton];
        //Dynamic Animator에 행동을 추가하면, 실제 동작하게 됩니다.
        [self.animator addBehavior:self.gravityBeahvior];
    }else{
        //원래 위치로 돌아가기 위해서, 중력 행동을 빼고, 스냅 행동을 넣습니다.
        [sender setTitle:@"Start" forState:UIControlStateNormal];
        [self.animator removeBehavior:self.gravityBeahvior];
        self.gravityBeahvior = nil;
        [self.animator addBehavior:self.textFieldSnapBehavior];
        [self.animator addBehavior:self.fallingButtonSnapBehavior];
    }
}

스냅은 특정 Item이 어디로 이동할 지 넣는 것입니다.
이 스냅이 호출 될 때는 이미 아이템 2개가 아래로 떨어지고 난 다음입니다. 그래도, 원래의 위치는 그대로 저장되어 있는 상태로, Dynamics 엔진 내부에 위치만 달라진 것이므로, 자시느이 위치로 돌아오도록 center 값을 읽을 수 있습니다.
source code
- (UISnapBehavior *)textFieldSnapBehavior
{
    if (_textFieldSnapBehavior == nil) {
        _textFieldSnapBehavior = [[UISnapBehavior alloc] initWithItem:self.textField 
                                                          snapToPoint:self.textField.center];
    }
    return _textFieldSnapBehavior;
}
- (UISnapBehavior *)fallingButtonSnapBehavior
{
    if (_fallingButtonSnapBehavior == nil) {
        _fallingButtonSnapBehavior = [[UISnapBehavior alloc] initWithItem:self.fallingButton 
                                                   snapToPoint:self.fallingButton.center];
    }
    return _fallingButtonSnapBehavior;
}

감사합니다.

[소스코드: https://github.com/davidbae/iOS7-Dynamics-Test.git ]


참고 :
 - WWDC 2013 : Getting started with UIKit Dynamics
      : #206 세션으로, 다이나믹스에 대한 설명이 있음.
 - UIKit Dynamics and iOS 7: Building UIKit Pong
      : 다이나믹스를 이용해서, 바운싱을 예제로 설명하고 있는 곳.
 - Apple의 Dynamics 예제
 - RayWenderlich의 UIKit Dynamics 강좌
 -