2014년 4월 8일 화요일

[iOS7] Dynamic : Attachment, Push, DynamicItem Behavior

Dynamics에 대한 기본적인 구성과 사용에 대해서는 이전 블로그에서 정리를 하였고, 이번은 Dynamics를 가지고, UI를 좀더 사용자 친화적으로 꾸밀 수 있을 지 알아보겠습니다.

모든 UIKit에 물리적 성질을 추가 할 수 있는 것이 iOS7의 Dynamics 입니다.
그것을 이용해서, 초기 화면의 UILabel 들에 Dynamics를 적용해 보도록 하겠습니다.
이전에는 UIView Animation을 이용해서 하던 것을 Dynamics를 활용해서 만드는 것입니다.

버튼 밑에 설명 Label을 붙이고, 이 Label들이 모션에 의해서 흔들리도록 해보겠습니다.
아래에 사용된 이미지는 구글 이미지에서 찾은 것들로, 저작권은 전혀 저에게 있지 않습니다.( 원본은 아래에 링크를 넣었습니다.)

위 이미지에서 Travel 라벨이 그 위의 아이콘의 center와 UIAttachmentBehavior로 연결을 하고, CoreMotion으로 X,Y 값의 변화에 따라서 좌/우로 흔들리도록 UIPushBehavior를 이용했습니다.

각 컨트롤에 대해서, Property 연결 합니다.
source code
@property (strong, nonatomic) IBOutlet UIImageView *travelImageView;
@property (strong, nonatomic) IBOutlet UIImageView *bookImageView;
@property (strong, nonatomic) IBOutlet UIImageView *workoutImageView;
@property (strong, nonatomic) IBOutlet UIImageView *foodImageView;

@property (strong, nonatomic) IBOutlet UILabel *travelLogLabel;
@property (strong, nonatomic) IBOutlet UILabel *bookLogLabel;
@property (strong, nonatomic) IBOutlet UILabel *foodLogLabel;
@property (strong, nonatomic) IBOutlet UILabel *workoutLogLabel;


@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) UIGravityBehavior *gravityBehavior;
@property (strong, nonatomic) UICollisionBehavior *collisionBehavior;
@property (strong, nonatomic) UIPushBehavior *pushBehavior;

그리고, UIDynamicsAnimator, UIGravityBehavior 로 설정합니다.
- (void) setupDynamics
{
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.travelLogLabel, self.bookLogLabel, self.foodLogLabel, self.workoutLogLabel]];    
    [self.animator addBehavior:self.gravityBehavior];
    ...
}

각 UILabel에 대해서 UIAttachmentBehavior를 연결하고, Damping과 Frequency를 설정합니다.

#pragma mark - Dynamics Setting
- (void) setupDynamics
{
    ...
    
    //Label들에 대해서, 마찰저항을 설정하고, 회전하지 않도록 설정
    UIDynamicItemBehavior *labelItemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.travelLogLabel, self.bookLogLabel, self.foodLogLabel, self.workoutLogLabel]];
    labelItemBehavior.resistance = 1.0f;
    labelItemBehavior.allowsRotation = NO;
    [self.animator addBehavior:labelItemBehavior];

    // Travel Label에 대해서 AttchmentBehavior를 연결함.
    [self addAttachmentBehavior:self.travelLogLabel
                        atPoint:CGPointMake(self.travelImageView.center.x, self.travelImageView.center.y)
                     toAnimator:self.animator];
    
    // Book Label에 대해서 AttachmentBehavior를 연결
    [self addAttachmentBehavior:self.bookLogLabel
                        atPoint:CGPointMake(self.bookImageView.center.x, self.bookImageView.center.y )
                     toAnimator:self.animator];
    
    // Food Label에 대해서 AttachmentBehavior 연결
    [self addAttachmentBehavior:self.foodLogLabel
                        atPoint:CGPointMake(self.foodImageView.center.x, self.foodImageView.center.y )
                     toAnimator:self.animator];
    
    // Workout Label에 대해서 AttachmentBehavior 연결
    [self addAttachmentBehavior:self.workoutLogLabel
                        atPoint:CGPointMake(self.workoutImageView.center.x, self.workoutImageView.center.y )
                     toAnimator:self.animator];
    
    ....
}
- (void) addAttachmentBehavior:(UILabel *)label atPoint:(CGPoint)point toAnimator:(UIDynamicAnimator *)animator
{
    UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:label attachedToAnchor:point];
    attachment.damping = 1.0f;
    attachment.frequency = 100.0f;
    [animator addBehavior:attachment];
}

이 두 값을 설정하더라도 적절하게 멈추는 것이 아니라, 자주 흔들리게 됩니다.
아래의 왼쪽이 Attachment만 적용한 것이고, 오른쪽이 UIDynamicItemBehavior로 마찰저항을 추가한 것입니다.

 


이제, CoreMotion을 추가해서, 기기가 좌우로 흔들릴 때 마다, PushBehavior를 추가하여, Label이 흔들리도록 하겠습니다.

먼저 CoreMotion을 추가합니다.

@import CoreMotion;
...
- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    
    _motionManager = [[CMMotionManager alloc] init];
    [self startMonitoringAcceleration];
}
...

#pragma mark - Dynamics Setting
- (void) setupDynamics
{
    ...
    
    self.pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.travelLogLabel, self.bookLogLabel, self.foodLogLabel, self.workoutLogLabel] mode:UIPushBehaviorModeInstantaneous]; //Instantaneous로 설정
    self.pushBehavior.active = NO;
    float angle = arc4random() % 360;
    self.pushBehavior.angle = (angle*M_PI/180.0);
    self.pushBehavior.magnitude = 0.4f;
    [self.animator addBehavior:self.pushBehavior];
}
#pragma mark - CoreMotion
- (void)startMonitoringAcceleration
{
    if (_motionManager.accelerometerAvailable) {
        //[_motionManager startAccelerometerUpdates];
        NSLog(@"accelerometer updates on...");
        [_motionManager setAccelerometerUpdateInterval:0.1f];
        [_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
                                             withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
                                                 float angle = atan2f(-accelerometerData.acceleration.y, accelerometerData.acceleration.x);
                                                 //NSLog(@"accelerometer:angle:%2.02f, x:%.02f, y:%.02f, z:%.02f", 180.0+angle*180.0/M_PI, accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z);
                                                 
                                                 NSLog(@"angle: %0.2f, %02.2f", angle, angle / M_PI * 180.0);
                                                 if (angle > 100*M_PI/180.0) {
                                                     angle = 100*M_PI/180.0;
                                                 }
                                                 if(angle < 80*M_PI/180.0){
                                                     angle = 80*M_PI/180.0;
                                                 }
                                                 self.gravityBehavior.angle = angle;// - 180.0 * M_PI / 180.0;
                                             }
         ];
    }else{
        NSLog(@"accelerometer Unavailable");
    }
}


- (void)stopMonitoringAcceleration
{
    if (_motionManager.accelerometerAvailable && _motionManager.accelerometerActive) {
        [_motionManager stopAccelerometerUpdates];
        NSLog(@"accelerometer updates off...");
    }
}
@end

위에서, accelerometerData의 acceleration의 x,y 좌표 값을 가지고, Angle을 구하고, 그것을 바탕으로 PushBehavior의 Active를 YES로 입력합니다.

실제 Device에서 실행을 하면, 회전을 살짝하면, Label 들이 움직이게 됩니다.


[이미지 출처]
배경화면, 비행기, , 음식, 운동

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