UIKit Dynamics 入門

UIKit Dynamicsフレームワークを使ってみました。
UIKit Dynamicsフレームワークは、iOS 7から導入された新しいAPIです。
これを使うと、UIViewにリアリスティックな動きをつけられます。
今回は基本的な紹介だけですが、応用するといい感じのUIが作れます。


使用するクラス

UIDynamicAnimator

役割 - 全体のコンテキストを提供 - 座標系の決定 - 物理エンジンのコントロール - ビヘイビアの把握


UIDynamicBehavior

Predefined Behaviors

UIGravityBehavior
UICollisionBehavior
UIAttachmentBehavior
UIPushBehavior
UISnapBehavior

DEMO

UIGravityBehavior

  • Sample Code

// Create view
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [UIColor purpleColor];
[self.view addSubview:view];
        
// Create animator
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
// Create gravity behavior
self.gravity = [[UIGravityBehavior alloc] initWithItems:@[view]];;

// Add behavior
[self.animator addBehavior:self.gravity];


UICollisionBehavior + UIGravityBehavior

  • Sample Code

// Create sub views
NSMutableArray* views = [NSMutableArray array];
for (int i = 0; i < 20; i++) {
    UIView* view;
    view = [[UIView alloc] initWithFrame:CGRectZero];
    view.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.0f green:(rand()%255)/255.0f blue:(rand()%255)/255.0f alpha:1.0f];
    [self.view addSubview:view];
    
    …中略…
    
    // Collect views
    [views addObject:view];
}

// Create dynamic animator
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// Create collision behavior
self.collision = [[UICollisionBehavior alloc] initWithItems:views];
self.collision.collisionMode = UICollisionBehaviorModeEverything;
self.collision.translatesReferenceBoundsIntoBoundary = YES;

// Create gravity behavior
self.gravity = [[UIGravityBehavior alloc] initWithItems:views];

// Add behaviors
[self.animator addBehavior:self.collision];
[self.animator addBehavior:self.gravity];


UIAttachmentBehavior + UIGravityBehavior

  • Sample Code

// Create sub view
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [UIColor purpleColor];
[self.view addSubview:view];

…中略...

// Create dynamic animator
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// Create attachment behavior
self.attachment = [[UIAttachmentBehavior alloc] initWithItem:view attachedToAnchor:self.view.center];
self.attachment.damping = 0.1f;

// Create gravity behavior
self.gravity = [[UIGravityBehavior alloc] initWithItems:@[view]];

// Add behaviors
[self.animator addBehavior:self.attachment];
[self.animator addBehavior:self.gravity];


UIPushBehavior + UICollisionBehavior + UIGravityBehavior

  • Sample Code

NSMutableArray* views = [NSMutableArray array];
for (int i = 0; i < 20; i++) {
    // Create sub views
    UIView* view;
    view = [[UIView alloc] initWithFrame:CGRectZero];
    view.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.0f green:(rand()%255)/255.0f blue:(rand()%255)/255.0f alpha:1.0f];
    [self.view addSubview:view];
    
    …中略...
    
    // Collect views
    [views addObject:view];
}

// Create dynamic animator
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// Create push behavior
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:views mode:UIPushBehaviorModeInstantaneous];
self.pushBehavior.magnitude = 0.1f;
self.pushBehavior.pushDirection = CGVectorMake(0, -0.1);

// Create collision behavior
self.collistion = [[UICollisionBehavior alloc] initWithItems:views];
self.collistion.collisionMode = UICollisionBehaviorModeEverything;
self.collistion.translatesReferenceBoundsIntoBoundary = YES;

// Create gravity behavior
self.gravity = [[UIGravityBehavior alloc] initWithItems:views];

// Add behaviors
[self.animator addBehavior:self.collistion];
[self.animator addBehavior:self.pushBehavior];
[self.animator addBehavior:self.gravity];

UISnapBehavior

  • Sample Code

// Create sub view
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [UIColor greenColor];
[self.view addSubview:view];

…中略...

// Create dynamic animator
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// Create snap behavior
self.snap = [[UISnapBehavior alloc] initWithItem:view snapToPoint:self.view.center];
self.snap.damping = 0.1f;

// Add behaviors
[self.animator addBehavior:self.snap];


参考URL
Getting Started with UIKit Dynamics
DynamicsCatalog

iBeaconを試してみた

iBeaconを試すにあたり、デモアプリを作りました。その際、参考にした記事等、調べたことをまとめます。


Bluetooth Low Enerygとは

Bluetooth Low Energy(以下、BLEと省略)は、Bluetooth4で追加された超低消費電力の通信仕様です。

BLEには、セントラルとペリフェラルという概念があります。通常、iPhoneがセントラル、心拍センサーなどの周辺装置がペリフェラルになります。

iOSアプリケーション開発

iOSアプリケーション開発では、クラシックBluetoothBluetooth LEデバイスとで、扱いが違います。

  • ペアリングはない

  • iOSを通さず、アプリケーションから直接、デバイスの発見と接続ができる

  • Bluetooth LE用のバックグラウンド・モードがある

  • iPhoneはセントラルとペリフェラル、どちらにもなれる

Bluetooth LEデバイスには、ペアリングはありません。クラシックBluetoothデバイスは、初めてiPhoneと接続するときに、ペアリングという接続認証が必要でした。Bluetooth LEデバイスでは、接続処理にiOSが一切関与しないため、ペアリングはありません。

Bluetooth LEのデバイスの発見、接続、通信そして切断も、すべてiOSアプリケーションが行います。iOSが関与することはありません。

iOSアプリケーションには、Bluetooth LE専用のバックグラウンド・モードが用意されています。バックグラウンドで、Bluetooth LEデバイスと接続して通信しつづける、あるいはデバイスを発見して接続をすることができます。

Bluetooth LEのネットワーク・トポロジーには、セントラルとペリフェラルという2つの役割があります。前節で例に上げたテレビのリモコンの場合は、テレビがセントラル、リモコンがペリフェラルです。iOS6では、iOSアプリケーションはセントラルにも、ペリフェラルにもなれます。つまり、iOSアプリケーション自体が、テレビのリモコンとして振る舞うことができます。


実装


セントラル app

セントラルの実装は、CoreLocationフレームワークのCLLocationManagerを使い、ペリフェラルを検知します。

  • CoreLocation framework

  • CLLocationManager

  • CLLocationManagerDelegate

CLLocationManagerの作成・モニタリング開始のコード


if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) {
        // Create location manager
        self.locationManager = [CLLocationManager new];
        self.locationManager.delegate = self;
        
        // Create proximity uuid
        self.proximityUUID = [[NSUUID alloc] initWithUUIDString:@"C1B19D33-6EBC-434A-AC9C-39A6A6265C58"];
        
        // Create beacon region
        self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:self.proximityUUID identifier:@"testregion"];
        
        // Start monitoring
        [self.locationManager startMonitoringForRegion:self.beaconRegion];
    }

Beaconを検知した時呼ばれるデリゲートメソッドの引数からCLBeaconオブジェクトを取得し、こやつから、proximityUUIDやrssi情報が取得できます。


- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
    if (beacons.count > 0) {
        // Get nearest object
        CLBeacon *nearestBeacon = beacons.firstObject;
        
        NSString *rangeMessage;
        
        // Decide message distance
        switch (nearestBeacon.proximity) {
            case CLProximityImmediate:
                rangeMessage = @"Range Immediate: ";
                break;
            case CLProximityNear:
                rangeMessage = @"Range Near: ";
                break;
            case CLProximityFar:
                rangeMessage = @"Range Far: ";
                break;
            default:
                rangeMessage = @"Range Unknown: ";
                break;
        }


ペリフェラル app

ペリフェラルの実装は、CoreBluetoothフレームワークのCBPeripheralManagerを使い、アドバタイズします。

  • CoreBluetooth framework

  • CBPeripheralManager

  • CBPeripheralManagerDelegate

アドバタイズ開始コード


// Create proximity uuid
    self.proximityUUID = [[NSUUID alloc] initWithUUIDString:@"C1B19D33-6EBC-434A-AC9C-39A6A6265C58"];
    
    // Create peripheral manager
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
    
    // Start advertise
    if (self.peripheralManager.state == CBPeripheralManagerStatePoweredOn) {
        [self startAdvertising];
    }
    
- (void)startAdvertising
{
    // Create data for advertise
    CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:self.proximityUUID
                                                                           major:1
                                                                           minor:2
                                                                      identifier:@"testregion"];
    NSDictionary *beaconPeripheralData = [beaconRegion peripheralDataWithMeasuredPower:nil];
    
    // Start advertise
    [self.peripheralManager startAdvertising:beaconPeripheralData];
}


所感

アプリ側で取得できるのは、電波強度とデバイス間の大体の距離でした。

正確な距離(何メートル)とかを取得するのは難しそうです。

Beaconの設置場所へ向かって歩いて通り過ぎると検知できませんでした。

設置場所に止まって5秒くらいたつと検知しました。

今後、Bluetooth Low Enerygについて詳しく調べたいと思います。


引用/参考URL

http://reinforce-lab.github.io

http://reinforce-lab.github.io/blog/2013/01/21/ios-ble-introduction/

http://reinforce-lab.github.io/blog/2013/07/26/blebook-ch1/

http://dev.classmethod.jp/references/ios7-ibeacon-api/

About Motion Activity

Motion data stored by a device

モーションデータの取得方法


モーションデータを活用すると、ユーザーの状態(歩いている、走っている、乗り物に乗っている、静止している)をアプリ側で知ることができます。

ユーザーの状態によって、画面の表示を変える等、なんらかのかたちで活用できないか考えています。

CMMotionActivityManager

デバイスが記録したモーションデータへのアクセスを提供する

CMMotionActivity

このクラスは、モーションが更新された際のデータを保持している


Code example


// Check available
if ([CMMotionActivityManager isActivityAvailable]) {
        
    // Create manager
        self.motionActivityManager = [[CMMotionActivityManager alloc] init];
        
    // Register handler
        [self.motionActivityManager startActivityUpdatesToQueue:[NSOperationQueue mainQueue]
                                                    withHandler:^(CMMotionActivity *activity) {
                                                         [self _updateLabels:activity];
                                                     }];
    }

期間を指定してモーションデータを取得する(過去7日間まで)


if ([CMMotionActivityManager isActivityAvailable]) {
        self.motionActivityManager = [[CMMotionActivityManager alloc] init];
        
        NSDate *now = [NSDate date];
        NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents *comps = [gregorian components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour fromDate:now];
        [comps setHour:0];
        NSDate *today = [gregorian dateFromComponents:comps];
        
        __weak typeof(self) weakSelf = self;
        [self.motionActivityManager queryActivityStartingFromDate:today
                                                           toDate:now
                                                          toQueue:[NSOperationQueue mainQueue]
                                                      withHandler:^(NSArray *activities, NSError *error) {
                                                          if (error) {
                                                              UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil)
                                                                                                              message:[error description]
                                                                                                             delegate:nil
                                                                                                    cancelButtonTitle:NSLocalizedString(@"OK", nil)
                                                                                                    otherButtonTitles:nil, nil];
                                                              [alert show];
                                                              return ;
                                                          }
                                                          weakSelf.activities = [activities mutableCopy];
                                                         [self _updateCircles];
                                                      }];
    }

How to use CMStepCounter

CMStepCounter

CMStepCounterクラスは歩数へのアクセスを提供する


歩数の計測を開始する

- (void)startStepCountingUpdatesToQueue:(NSOperationQueue *)queue updateOn:(NSInteger)stepCounts withHandler:(CMStepUpdateHandler)handler

Code example


// Check step counting available
if ([CMStepCounter isStepCountingAvailable]) {
        __weak typeof(self) weakSelf = self;
        
    // Create step counter
        self.stepCounter = [[CMStepCounter alloc] init];

    // Start step counting
        [self.stepCounter startStepCountingUpdatesToQueue:[NSOperationQueue mainQueue]
                                                 updateOn:1
                                              withHandler:^(NSInteger numberOfSteps, NSDate *timestamp, NSError *error) {   
                                                  weakSelf.stepCountLabel.text = [@(numberOfSteps) stringValue];
                                              }];
    }

歩数の計測を停止する

- (void)stopStepCountingUpdates

期間を指定して歩数を取得する(過去7日間まで取得可能)

- (void)queryStepCountStartingFrom:(NSDate )start to:(NSDate )end toQueue:(NSOperationQueue *)queue withHandler:(CMStepQueryHandler)handler

Code Example


// Check step counting available
if ([CMStepCounter isStepCountingAvailable]) {
        __weak typeof(self) weakSelf = self;
        
        NSDate *now = [NSDate date];
        NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents *comps = [gregorian components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour fromDate:now];
        [comps setHour:0];
        NSDate *today = [gregorian dateFromComponents:comps];
        
        [self.stepCounter queryStepCountStartingFrom:today
                                                  to:now
                                             toQueue:[NSOperationQueue mainQueue]
                                         withHandler:^(NSInteger numberOfSteps, NSError *error) {
                                             NSLog(@"------------------------------------------");
                                             NSLog(@"%s : %d", __PRETTY_FUNCTION__, __LINE__);
                                             NSLog(@"numberOfSteps : %d", (int)numberOfSteps);
                                             NSLog(@"error : %@", error);
                                             
                                             weakSelf.totalStepsLabel.text = [@(numberOfSteps) stringValue];
                                         }];
    }

How to iOS 7 Intaraction Transtion - NavigationController

How to iOS 7 Intaraction Transtion (NavigationController)


1.Return animator object in UINavigationControllerDelegate method.


- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    NKRBaseAnimator*    animator;
    animator = [[NKRBackAnimator alloc] init];
    animator.reverse = (operation == UINavigationControllerOperationPop);
    animator.duration = 0.3f;
    return animator;
}

1.Return UIViewControllerInteractiveTansitioning object in UINavigationControllerDelegate method.


- (id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController
{
    return self.interactiveTransition;
}

NKRBaseInteraction.h


#import 
@interface NKRBaseInteraction : UIPercentDrivenInteractiveTransition
@end

NKRBaseInteraction.m


#import "NKRBaseInteraction.h"

@implementation NKRBaseInteraction

// This is important! if not override this method, animateTransition: was executed twice.
- (CGFloat)completionSpeed
{
    return 1 - self.percentComplete;
}

@end

2.Push or Pop view controller in UIGestureRecognizerStateBegan.


if (recognizer.state == UIGestureRecognizerStateBegan) {
        // Create a interactive transition
        self.interactiveTransition = [[NKRBaseInteraction alloc] init];
        
        if (translation.y > 0) {
            // Push view controller
            ViewController*  viewController;
            viewController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ViewController"];
            viewController.view.backgroundColor = [UIColor colorWithRed:(rand()%255)/255.0f green:(rand()%255)/255.0f blue:(rand()%255/255.0f) alpha:1.0f];
            [self.navigationController pushViewController:viewController animated:YES];
        }
        else {
            // Pop view controller
            [self.navigationController popViewControllerAnimated:YES];
        }
    }

3.Do updateInteractiveTransition: in UIGestureRecognizerStateChanged.


else if (recognizer.state == UIGestureRecognizerStateChanged) {
        // if an interactive transitions is 100% completed via the user interaction, for some reason
        // the animation completion block is not called, and hence the transition is not completed.
        // This glorious hack makes sure that this doesn't happen.
        // see: https://github.com/ColinEberhardt/VCTransitionsLibrary/issues/4
        if (fraction >= 1.0)
            fraction = 0.99;
        
        // Update the interactive transition's progress
        [self.interactiveTransition updateInteractiveTransition:fraction];
    }

4.Do finishInteractiveTransition and cancelInteractiveTransition in UIGestureRecognizerStateEnded and UIGestureRecognizerStateCancelled


else if (recognizer.state == UIGestureRecognizerStateEnded ||
             recognizer.state == UIGestureRecognizerStateCancelled) {
        // Finish or cancel the interactive transition
        if (fraction > 0.5) {
            [self.interactiveTransition finishInteractiveTransition];
        }
        else {
            [self.interactiveTransition cancelInteractiveTransition];
        }
        
        // Clear interaction
        self.interactiveTransition = nil;
    }

iOS7 テキストの回り込み

iOS 7からUITextViewでテキストの回り込みが指定できるようになった。 NSTextContainerのexclusionPathsプロパティに、回り込みさせたい領域のパスを指定すると、文字がパスに沿って表示される。


- (void)_updateTextView
{
    UIBezierPath*   bezierPath;
    bezierPath = [UIBezierPath bezierPathWithRect:self.imageView.frame];
    
    UIBezierPath*   bezierPath2;
    bezierPath2 = [UIBezierPath bezierPathWithOvalInRect:_circleView.frame];
    
    self.textView.textContainer.exclusionPaths = @[bezierPath, bezierPath2];
}

f:id:nkmrh:20131220072626p:plain

参考サイト

Alberto Pasca

頭と尻尾はくれてやる!

double

iOS 7のカスタムトランジション

AnimationController

<UIViewControllerAnimatedTransitioning>プロトコルを採用したクラスを用意し、下記メソッドにアニメーションを記述する。


- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext


- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext


モーダル

モーダル表示のアニメーションをカスタマイズする方法

  1. 遷移元ViewControllreにUIViewControllerTransitioningDelegateメソッドを実装し、AnimationControllerを返す

    – animationControllerForPresentedController:presentingController:sourceController:
    – animationControllerForDismissedController:

  2. 遷移先ViewControllreの「transitioningDelegate」プロパティにデリゲートオブジェクトを設定する

    
    toVC.transitioningDelegate = self;
    
    

  3. 遷移先ViewControllreのmodalPresentationStyleプロパティにUIModalPresentationCustomを指定する

    
    toVC.modalPresentationStyle = UIModalPresentationCustom;
    
    

  4. presentViewController:animated:completion:を呼ぶ


TabBarController

タブ切り替え時の実装方法

  1. TabBarControllerDlegateの下記メソッドを実装し、AnimationControllerを返す
    
    tabBarController:animationControllerForTransitionFromViewController:toViewController:
    
    

UINavigationController

UINavigationControllerのPushやPopの時のアニメーションをカスタマイズする方法

  1. UINavigationControllerDelegateの下記メソッドを実装し、AnimationControllerを返す
    
     navigationController:animationControllerForOperation:fromViewController:toViewController: