ラベル iOS の投稿を表示しています。 すべての投稿を表示
ラベル iOS の投稿を表示しています。 すべての投稿を表示

2015年5月23日土曜日

GCDについて情報整理する

歴史的なことも含めてGCDに関する情報を整理しておく。


GCDの歴史

  • Mac OS X 10.6(Snow Leopard)以降、iOS 4.0以降で利用可能
  • OS X 10.8(Mountain Lion)、iOS 6.0以降では、GCDオブジェクト(Dispatch Queue)もARCの管轄下に入るようになった(実際にはGCDだけでなくXPCも)
  • OS X 10.10(Yosemite)、iOS8.0 からQoS(Quality of Service)の概念が導入された


ディスパッチキューの利用


ディスパッチキューの利用には大きく分けて2つの方法がある

  1. dispatch_queue_create関数を使ってディスパッチキューを自分で生成する
  2. システムが標準で提供しているディスパッチキューを取得する

1. dispatch_queue_create関数を使ってディスパッチキューを自分で生成する

  • 開発者はシリアルキューとコンカレントキューを生成できる
  • コンカレントキューを自分で生成するケースは稀
  • OS X v10.7、iOS 4.3以降から第2引数にNULL以外を指定できるようになった

1.1. シリアルキューを生成する場合
dispatch_queue_t mySerialDispatchQueue 
= dispatch_queue_create("com.example.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
// または、下記の通り、第2引数にNULLを指定してもよい。
// ただし、個人的には明示的に定数指定している上記の書き方の方がいいと思う。
//dispatch_queue_t mySerialDispatchQueue 
//= dispatch_queue_create("com.example.MySerialDispatchQueue", NULL);

1.2. コンカレントキューを生成する場合
dispatch_queue_t myConcurrentDispatchQueue 
= dispatch_queue_create("com.example.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);


2. システムが標準で提供しているディスパッチキューを取得する

  • システムが標準で提供しているディスパッチキューは2種類ある(Main Dispatch QueueとGlobal Dispatch Queue)
  • アプリのどこからでも利用できる

2.1. Main Dispatch Queue
  • メインスレッドで実行されるシリアルキュー(メインスレッドは1つしかないため)
  • 実際にはメインスレッドで実行されるデフォルトのRunLoopで処理される
  • Main queue is access queue for UI
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

2.2. Global Dispatch Queue
  • コンカレントキュー
  • 実行優先度別に4種類(従来方式)または5種類(QoS方式)ある
  • 2015年5月現在においてバックワードコンパチを考慮(例えばiOS7.xをサポート)する場合は必然的に従来方式を採用することになる
// OS X 10.10(Yosemite)、iOS8.0以前でも以降でも利用できる
// 第2引数は0固定

dispatch_queue_t globalDispatchQueuePriorityHigh 
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_queue_t globalDispatchQueuePriorityDefault 
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_queue_t globalDispatchQueuePriorityLow 
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

dispatch_queue_t globalDispatchQueuePriorityBackground 
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

// OS X 10.10(Yosemite)、iOS8.0以降でしか利用できない
// 第2引数は0固定

// 基本的にはQoSに関する明確な意思を決めて
// QOS_CLASS_DEFAULT以外を選択するのが良さげ。
// また、QOS_CLASS_DEFAULTについては下記の通り。
// Ordered between UI and non-UI QoS
// Not intented as a work classification

dispatch_queue_t globalDispatchQueueQosInteractive 
= dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);

dispatch_queue_t globalDispatchQueueQosInitiated 
= dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);

dispatch_queue_t globalDispatchQueueQosDefault 
= dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

dispatch_queue_t globalDispatchQueueQosUtility 
= dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);

dispatch_queue_t globalDispatchQueueQosBackground 
= dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);



コード例


ワーカスレッドがシリアルの場合
dispatch_queue_t mySerialDispatchQueue 
= dispatch_queue_create("com.example.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
    
dispatch_async(mySerialDispatchQueue, ^{
    // ワーカスレッド(シリアル)
    // 重い処理をここに書く
    
    dispatch_async(dispatch_get_main_queue(), ^{
        // メインスレッド(シリアル)
        // UI処理をここに書く
    });
    
});

ワーカスレッドがコンカレントの場合
//dispatch_queue_t globalDispatchQueuePriorityDefault 
//= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(globalDispatchQueuePriorityDefault, ^{
    // ワーカスレッド(コンカレント)
    // 「コンカレント実行されても問題ない」重い処理をここに書く
    
    dispatch_async(dispatch_get_main_queue(), ^{
        // メインスレッド(シリアル)
        // UI処理をここに書く
    });
    
});

2015年5月17日日曜日

CocoaPodsを使ってAFNetworkingを導入する

導入対象プロジェクト(今回の場合はPodsSample.xcodeproj)は閉じておく。
プロジェクトファイルがあるディレクトリまで移動後、Podfileを生成する。
$ cd /path/to/PodsSample
$ pod init

Podfileを編集する。
$ open -a Xcode Podfile

Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '6.0'
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

target 'PodsSample' do
  pod 'AFNetworking', '~>2.5.3'
end

target 'PodsSampleTests' do

end


Podfileを保存して閉じた後、pod installを実行する。
$ rm Podfile.lock
$ pod install --verbose

以降は常にプロジェクトではなく、ワークスペースを開いて使う。
$ ls -l
(下記は抜粋表示)
Podfile
Podfile.lock
Pods
PodsSample
PodsSample.xcodeproj
PodsSample.xcworkspace
PodsSampleTests

$ open PodsSample.xcworkspace


CocoaPods管理のライブラリを更新する際は下記を実行する。
$ pod update


2015年5月16日土曜日

CocoaPodsをインストールする

使用中のRuby及びgemのバージョンを確認する。
$ rbenv version
2.2.0 (set by /Users/hogeuser/.rbenv/version)
$ gem -v
2.4.7

CocoaPodsをインストールする。
$ gem install cocoapods
$ rbenv rehash
$ pod setup --verbose


補足


「gem install cocoapods」により"追加で"インストールされたgem
activesupport (4.2.1)

claide (0.8.1)
cocoapods (0.37.1)
cocoapods-core (0.37.1)
cocoapods-downloader (0.9.0)
cocoapods-plugins (0.4.2)
cocoapods-trunk (0.6.0)
cocoapods-try (0.4.4)
colored (1.2)
escape (0.0.4)
fuzzy_match (2.0.4)
i18n (0.7.0)

molinillo (0.2.3)
nap (0.8.0)
netrc (0.7.8)

thread_safe (0.3.5)
tzinfo (1.2.2)
xcodeproj (0.24.1)

インストールするCocoaPodsのバージョンを指定する場合(RubyGemsの仕様通り)
$ gem install cocoapods --version "=0.36.4"

「pod setup」ではそれなりに時間がかかるので根気よく待つ。

2014年11月26日水曜日

NSURLSessionを使ってHTTPリクエストする(Swift)

SwiftでNSURLSessionDataTaskを使って、HTTP-GET、JSONデータをHTTP-POSTする。

環境
  • Xcode 6.1
  • iOS 8.1
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    // HTTP-GET
    @IBAction func getAsync(sender: AnyObject) {
        
        // create the url-request
        let urlString = "http://httpbin.org/get"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        
        // set the method(HTTP-GET)
        request.HTTPMethod = "GET"
        
        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()
        
    }


    // HTTP-POST
    @IBAction func postAsync(sender: AnyObject) {
        
        // create the url-request
        let urlString = "http://httpbin.org/post"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        
        // set the method(HTTP-POST)
        request.HTTPMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // set the request-body(JSON)
        var params: [String: AnyObject] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: nil)
        
        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()
        
    }
        
}

2014年8月16日土曜日

NSURLSessionを使ってHTTPリクエストする

NSURLSessionDataTaskを使って、HTTP-GET、JSONデータをHTTP-POSTする。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *requestUrl;
@property (weak, nonatomic) IBOutlet UITextField *requestBody;
@property (weak, nonatomic) IBOutlet UITextView *requestResult;
- (IBAction)getAsync:(id)sender;
- (IBAction)postAsync:(id)sender;

@end
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

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

// HTTP-GET
- (IBAction)getAsync:(id)sender {
    NSLog(@"%s", __func__);
    
    // create the url-request
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    // set the method(HTTP-GET)
    [request setHTTPMethod:@"GET"];
    
    // use NSURLSessionDataTask
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        
        if (!error) {
            NSHTTPURLResponse *httpRes = (NSHTTPURLResponse *)response;
            NSLog(@"statusCode: %ld", (long)httpRes.statusCode);
            NSLog(@"allHeaderFields: %@", httpRes.allHeaderFields);
            
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.requestResult.text = result;
            });
            
        } else {
            NSLog(@"error: %@", [error localizedDescription]);
        }
        
    }];
    
    [task resume];
}

// HTTP-POST
- (IBAction)postAsync:(id)sender {
    NSLog(@"%s", __func__);
    
    // create the url-request
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // set the headers(s)
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
    // set the method(HTTP-POST)
    [request setHTTPMethod:@"POST"];

    // set the request-body
    NSString *reqBody = self.requestBody.text;
    NSLog(@"%@", reqBody);

    [request setHTTPBody:[reqBody dataUsingEncoding:NSUTF8StringEncoding]];
    
    // use NSURLSessionDataTask
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        
        if (!error) {
            NSHTTPURLResponse *httpRes = (NSHTTPURLResponse *)response;
            NSLog(@"statusCode: %ld", (long)httpRes.statusCode);
            NSLog(@"allHeaderFields: %@", httpRes.allHeaderFields);
            
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.requestResult.text = result;
            });
            
        } else {
            NSLog(@"error: %@", [error localizedDescription]);
        }
        
    }];
    
    [task resume];
}

@end

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プログラミングガイド

2013年12月28日土曜日

iOSで詳細ビューを追加する


前回作成したテーブルビューから遷移する詳細ビューを追加する。
詳細ビューとして、ストーリーボードにView Controller(紐づけるクラス名はDetailViewControllerとする)を追加する。

Table View Controller(クラス名:ViewController)からView Controller(クラス名:DetailViewController)へのセグエを設定する。
Storyboard Segue: 「identifier」に"getDetail"と入力、「Style」に"push"を選択する。


Table View Controller(クラス名:ViewController)のバックボタン表示名を変更する
Navigation Item: 「Back Button」に"プロ野球"と入力する。


詳細ビュー(クラス名:DetailViewController)をコーディングする。
#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *detailLabel;
@property (strong, nonatomic) NSString *detailItem;

@end
#import "DetailViewController.h"

@implementation DetailViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.detailLabel.text = self.detailItem;
    NSLog(@"self.detailItem is %@", self.detailItem);
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

@end


テーブルビュー(クラス名:ViewController)をコーディングする。
ViewController.mにおいてDetailViewController.hをインポートし、prepareForSegue: sender:メソッドを追加する。
#import "ViewController.h"
#import "DetailViewController.h"

@interface ViewController ()

@end

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

#pragma mark - Navigation

// In a story board-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    if ([[segue identifier] isEqualToString:@"getDetail"]) {

        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        NSArray *teams = _leagues[indexPath.section];
        NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
        
        [[segue destinationViewController] setTitle:teams[indexPath.row]];
        [[segue destinationViewController] setDetailItem:teamPrefixes[indexPath.row]];
    }   
}
@end

2013年12月23日月曜日

iOSでテーブルビューを使う


環境
  • Xcode 5.0.2
  • ARC ON
  • iOSシミュレータ 7.0

ストーリーボードにNavigation ControllerとTable View Controllerを追加する。

Table View Cell: 「Style」から"Subtitle"を選択、「Identifier」に"Cell"と入力する。

この紐付けをしておかないと実行時に下記エラーが吐かれる。
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'


Navigation Item: 「Title」に"日本プロ野球チーム"と入力する。


UITableViewControllerを継承したクラス(今回の例ではViewController)を作成する。

tableView: cellForRowAtIndexPath:メソッドとtableView: viewForHeaderInSection:メソッドはテーブルビュー初回描画時と画面スクロールが発生するたびに呼ばれる。
#import <UIKit/UIKit.h>

@interface ViewController : UITableViewController

@end
#import "ViewController.h"

@interface ViewController ()

@end

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

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
 
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
    
    _leagueKinds = @[@"セントラル・リーグ", @"パシフィック・リーグ"];
    
    _leagues = @[
                @[@"ジャイアンツ", @"タイガース", @"ドラゴンズ", @"ベイスターズ", @"カープ", @"スワローズ"],
                @[@"バッファローズ", @"ホークス", @"ファイターズ", @"マリーンズ", @"ライオンズ", @"ゴールデンイーグルス"]
                ];
    
    _leaguePrefixes = @[
                       @[@"読売", @"阪神", @"中日", @"横浜DeNA", @"広島東洋", @"東京ヤクルト"],
                       @[@"オリックス", @"福岡ソフトバンク", @"北海道日本ハム", @"千葉ロッテ", @"埼玉西武", @"東北楽天"]
                       ];
}

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

#pragma mark - Table view data source

- (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 forIndexPath:indexPath];

//    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 = _leagueNames[section];

    return view;
}

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

@end

2013年6月23日日曜日

iOSでHTTPリクエストする

比較的単純なHTTPリクエストを実行する際は、非同期リクエストであるNSURLConnectionクラスのsendAsynchronousRequest:queue:completionHandler:クラスメソッドを利用する。
本APIはiOS5.0から利用可能。

第3引数completionHandlerにはデータ取得時に実行するBlock文を記述する。このBlock文は第2引数queueで指定したNSOperationQueue上で実行される。以下のコード例ではBlock文はメインスレッドで実行されることになる。

環境
  • Xcode 4.6.3
  • ARC ON
  • iPhone 6.1 Simulator
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *requestUrl;
@property (weak, nonatomic) IBOutlet UITextField *requestBody;
@property (weak, nonatomic) IBOutlet UITextView *requestResult;
- (IBAction)getAsync:(id)sender;
- (IBAction)postAsync:(id)sender;

@end
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

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

// HTTP-GET
- (IBAction)getAsync:(id)sender {
    NSLog(@"%@#%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));

    // Create the url-request.
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // Set the method(HTTP-GET)
    [request setHTTPMethod:@"GET"];

    // Send the url-request.
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {   
        if (data) {
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            self.requestResult.text = result;
        } else {
            NSLog(@"error: %@", error);
            self.requestResult.text = [NSString stringWithFormat:@"error: %@", error];
        }
    }];
    
}

// HTTP-POST
- (IBAction)postAsync:(id)sender {
    NSLog(@"%@#%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));

    // Create the url-request.
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // Set the header(s).
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    // Set the method(HTTP-POST)
    [request setHTTPMethod:@"POST"];

    // Set the request-body.
    NSString *reqBody = self.requestBody.text;
    NSLog(@"reqBody: %@", reqBody);
    [request setHTTPBody:[reqBody dataUsingEncoding:NSUTF8StringEncoding]];

    // Send the url-request.   
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        
        if (data) {
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            self.requestResult.text = result;
        } else {
            NSLog(@"error: %@", error);
            self.requestResult.text = [NSString stringWithFormat:@"error: %@", error];
        }
    }];
  
}

@end