2014年1月25日土曜日

iBeaconを受信する

uuidgenコマンドを使って、proximity IDとして使用するUUIDを事前準備しておく。

$ uuidgen


iBeacon受信処理を行うクラスにおいて、CoreLocationをインポートした上でCLLocationManagerDelegateプロトコルを実装(適合)する。各メソッドの意味等については下記コード内のコメントを要参照。
@implementation BLGViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.delegate = self;

        // identifierについては、アプリ内でどのリージョンであるかを判別するために使用
        NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BLGViewControllerProximityUUID];
        self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:kIdentifier];
        
        // いずれもデフォルト設定値
        self.beaconRegion.notifyOnEntry = YES;
        self.beaconRegion.notifyOnExit = YES;
        self.beaconRegion.notifyEntryStateOnDisplay = NO;
        
        // 領域観測を開始する
        [self.locationManager startMonitoringForRegion:self.beaconRegion];
    }
}

#pragma mark - CLLocationManagerDelegate

// 領域観測が正常に開始されると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
    // 非同期に実行し、CLLocationManagerDelegateに結果を配送する
    // (locationManager:didDetermineState:forRegion:メソッド要実装)
    [self.locationManager requestStateForRegion:self.beaconRegion];
}

// 領域に関する状態を取得する
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    switch (state) {
        case CLRegionStateInside:
            NSLog(@"state is CLRegionStateInside");
            self.stateLabel.text = @"CLRegionStateInside";
            // 領域内にいるので、測距を開始する
            if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
                [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
            }
            break;
        case CLRegionStateOutside:
            NSLog(@"state is CLRegionStateOutside");
            self.stateLabel.text = @"CLRegionStateOutside";
            break;
        case CLRegionStateUnknown:
            NSLog(@"state is CLRegionStateUnknown");
            self.stateLabel.text = @"CLRegionStateUnknown";
            break;
        default:
            NSLog(@"state is UNKNOWN");
            self.stateLabel.text = @"UNKNOWN";
            break;
    }
}

// 領域に入ると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    // 領域に入ったので、測距を開始する
    if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
        [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
    }
}

// 領域から出ると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
        [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
    }
}

// 領域内でビーコンを受信する度に呼ばれる(実機で確認する限りでは約1秒毎)
// ビーコンの状態が変わった時も呼ばれる
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
    if ([beacons count] > 0) {
        // 最も近くにあるビーコンが配列の先頭にあるように、デバイスからの距離によって整列されている
        CLBeacon *nearestBeacon = beacons.firstObject;
        NSString *proximityStr;
        
        switch (nearestBeacon.proximity) {
            case CLProximityImmediate:
                proximityStr = @"CLProximityImmediate";
                break;
            case CLProximityNear:
                proximityStr = @"CLProximityNear";
                break;
            case CLProximityFar:
                proximityStr = @"CLProximityFar";
                break;
            case CLProximityUnknown:
                proximityStr = @"CLProximityUnknown";
                break;
            default:
                proximityStr = @"UNKNOWN";
                break;
        }

        // ビーコン識別情報
        self.uuidLabel.text = nearestBeacon.proximityUUID.UUIDString;
        self.majorLabel.text = [NSString stringWithFormat:@"%@", nearestBeacon.major];
        self.minorLabel.text = [NSString stringWithFormat:@"%@", nearestBeacon.minor];
        
        // アドバタイズしているビーコンまでの距離に関する情報
        self.proximityLabel.text = proximityStr;
        self.rssiLabel.text = [NSString stringWithFormat:@"%ld [dB]", (long)nearestBeacon.rssi];
        self.accuracyLabel.text = [NSString stringWithFormat:@"%.0f [m]", nearestBeacon.accuracy];
    }
}

// アプリのロケーションサービスに関するアクセス許可状態に変更があると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    NSLog(@"%s", __func__);
    switch (status) {
        case kCLAuthorizationStatusAuthorized:
            NSLog(@"kCLAuthorizationStatusAuthorized");
            break;
        case kCLAuthorizationStatusRestricted:
            NSLog(@"kCLAuthorizationStatusRestricted");
            break;
        case kCLAuthorizationStatusDenied:
            NSLog(@"kCLAuthorizationStatusDenied");
            break;
        case kCLAuthorizationStatusNotDetermined:
            NSLog(@"CLAuthorizationStatusNotDetermined");
            break;
        default:
            NSLog(@"UNKNOWN");
            break;
    }
}

@end

[参考URL]
[iOS 7] 新たな領域観測サービス iBeacon を使ってみる
iBeaconの解説

2014年1月13日月曜日

Objective-Cで定数を定義する

ヘッダーファイルにおいて、#define(マクロ)を使って、(公開/非公開に関わらず)全ての定数にプレフィックスkをつけて定義しているコードを見かけることがあるが、下記理由から個人的にはあまり良くないのではないかと思っている。
  • 型情報がない
  • ヘッダーファイルインポート条件によっては、名前衝突により開発者が意図しないファイルにおいても定数置換が行われてしまう危険性がある

実際のところ、私は下記のように定数定義を行っている。

外部に公開する定数

  • extern constを付与
  • 定数にはプレフィックスとしてクラス名をつける(名前衝突を避けるため)

内部でのみ使用する定数

  • static constを付与
  • 定数にはプレフィックスとしてkをつける(よく見かけるパターン)

コーディング例

以下は、BLGClassAクラスにおいて外部に公開する定数を定義、BLGClassBクラスにおいて内部で使用する定数を定義、して利用する場合の例。


動作確認

コンパイル/リンクして実行。


2014年1月11日土曜日

iOSのシステムサウンドを再生する

iOSにシステムとして予め用意されているサウンドを再生する。
プロジェクトにAudioToolbox.frameworkを追加した上で、SystemSoundIDを引数に持つAudioServicesPlaySystemSound関数を呼び出す。
#import "ViewController.h"
@import AudioToolbox;

@interface ViewController ()
@end

- (IBAction)playSystemSound:(id)sender {
    // ベル音(SystemSoundIDは1009)を鳴らす
    AudioServicesPlaySystemSound(1009); 
}

[参考URL]
iOSのシステムサウンドを確認する
AudioServices - iPhone Development Wiki

2014年1月5日日曜日

即座にローカル通知する

fireDateを指定する方法とpresentLocalNotificationNow:メソッドを使う方法がある。いずれの場合についても、前回と同様、AppDelegateクラスにおいてイベントメソッド処理できる。


1. fireDateを指定する
 fireDateにnilまたは過去の時間を指定すると、即座にローカル通知が送信される。
- (IBAction)sendNotification:(id)sender {
  
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = nil;
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];

}

2. presentLocalNotificationNow:メソッドを使う
 fireDateの値に関わらず、即座にローカル通知が送信される。
- (IBAction)sendNotification:(id)sender {
  
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = [[NSDate date] dateByAddingTimeInterval:60];
    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];

}

2014年1月4日土曜日

一定時間後にローカル通知する


UILocalNotificationを使って一定時間後(例では10秒後)にローカル通知する。

- (IBAction)sendNotification:(id)sender {
    NSLog(@"%s", __func__);
    
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    
    //    // iOS4未満用の処理?
    //    if (notification == nil)
    //        return;
    
    notification.fireDate = [[NSDate date] dateByAddingTimeInterval:10];
    notification.timeZone = [NSTimeZone defaultTimeZone];
    notification.alertBody = @"Hello, LocalNotification!";
    notification.hasAction = YES;
    notification.alertAction = @"Launch";
    notification.soundName = UILocalNotificationDefaultSoundName;
    notification.applicationIconBadgeNumber = 1;
    
    NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"Recevied." forKey:@"kExtra"];
    notification.userInfo = infoDict;
    
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ 
    // notificationの有無に関わらず、アプリ起動時に呼ばれる。
    NSLog(@"%s", __func__);
    
    UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    
    if (notification) {
        NSDictionary *infoDict = [notification userInfo];
        NSString *extra = [infoDict objectForKey:@"kExtra"];
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSStringFromSelector(_cmd) message:extra delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alertView show];
    }
    
    return YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"%s", __func__);
    // アプリ強制終了の場合も考慮して、ここにバッジクリア処理を入れるのが良さそうか?
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    // アプリがフォアグランドまたはバックグラウンドにある場合に呼ばれる。
    NSLog(@"%s", __func__);
    NSDictionary *infoDict = [notification userInfo];
    NSString *extra = [infoDict objectForKey:@"kExtra"];
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSStringFromSelector(_cmd) message:extra delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    [alertView show];
}


[参考URL]
LocalおよびPush Notification プログラミングガイド

2014年1月3日金曜日

ストーリーボードも使わずに実現する

前回の内容をストリーボードも使わずに実現する。ただし、AppDelegateクラスについてはUINavigationControllerをプロパティとして使い、ViewControllerクラスについてはUITableViewControllerを継承する形式で実現してみる。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;

@end
#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    ViewController *viewController = [[ViewController alloc] init];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    self.window.rootViewController = self.navigationController;

    [self.window makeKeyAndVisible];
    return YES;
}

@end
#import <UIKit/UIKit.h>

@interface ViewController : UITableViewController

@end
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    
    DetailViewController *detailViewController = [[DetailViewController alloc] init];
    [detailViewController setTitle:teams[indexPath.row]];
    [detailViewController setDetailItem:teamPrefixes[indexPath.row]];
    [self.navigationController pushViewController:detailViewController animated:YES];
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    // I don't care orientation this time!
    UILabel *detailLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    detailLabel.textAlignment = NSTextAlignmentCenter;
    detailLabel.backgroundColor = [UIColor clearColor];
    detailLabel.text = self.detailItem;
    [self.view addSubview:detailLabel];
    NSLog(@"self.detailItem is %@", self.detailItem);
}

2014年1月2日木曜日

セグエを使わずに実現する

前回の内容をセグエを使わずに実現する。 また、ビューコントローラについてはView Controllerのみを使い、ストーリボードについては2つ使ってみる。つまり、iOS View Controllerプログラミングガイドのリスト 2-3のような形式をとってみる。


新しいストーリーボードからビューコントローラのインスタンスを生成する。
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIViewController *viewController;

@end
#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:NSStringFromClass([ViewController class]) bundle:nil];
    ViewController *viewController = [storyboard instantiateInitialViewController];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    self.viewController = navigationController;
    self.window.rootViewController = navigationController;
    
    [self.window makeKeyAndVisible];
    return YES;
}

@end


選択行をスムーズに消すために、viewWillAppear:イベントメソッド内でdeselectRowAtIndexPath: animated:メソッドを呼び出す。
セルを再利用のためにdequeueReusableCellWithIdentifier:メソッドを使う。
画面遷移については、didSelectRowAtIndexPath:イベントメソッド内で実行する。
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

@end
#import "ViewController.h"
#import "DetailViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController {
    @private
    NSArray *_leagueKinds;
    NSArray *_leagues;
    NSArray *_leaguePrefixes;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view.
    _leagueKinds = @[@"セントラル・リーグ", @"パシフィック・リーグ"];
    
    _leagues = @[
                 @[@"ジャイアンツ", @"タイガース", @"ドラゴンズ", @"ベイスターズ", @"カープ", @"スワローズ"],
                 @[@"バッファローズ", @"ホークス", @"ファイターズ", @"マリーンズ", @"ライオンズ", @"ゴールデンイーグルス"]
                 ];
    
    _leaguePrefixes = @[
                        @[@"読売", @"阪神", @"中日", @"横浜DeNA", @"広島東洋", @"東京ヤクルト"],
                        @[@"オリックス", @"福岡ソフトバンク", @"北海道日本ハム", @"千葉ロッテ", @"埼玉西武", @"東北楽天"]
                        ];
    
    self.title = @"日本プロ野球チーム";
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
}

- (void)viewWillAppear:(BOOL)animated
{
    [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return [_leagueKinds count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [_leagues[section] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    cell.textLabel.text = teams[indexPath.row];
    cell.detailTextLabel.text = teamPrefixes[indexPath.row];
    
    return cell;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *HeaderIdentifier = @"Header";
    UITableViewHeaderFooterView *view = [tableview dequeueReusableHeaderFooterViewWithIdentifier:HeaderIdentifier];
    
    if (!view) {
        view = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:HeaderIdentifier];
    }
    
    view.textLabel.text = _leagueKinds[section];
    
    return view;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:NSStringFromClass([DetailViewController class]) bundle:nil];
    DetailViewController *detailViewController = [storyboard instantiateInitialViewController];
    [detailViewController setTitle:teams[indexPath.row]];
    [detailViewController setDetailItem:teamPrefixes[indexPath.row]];
    [self.navigationController pushViewController:detailViewController animated:YES];
}

@end

[参考URL]
iOS View Controllerプログラミングガイド