[iPhone]サーバとHTTP通信する(同期通信・非同期通信/GET・POST)

はじめに

以前AndroidでサーバとHTTP通信する方法を書きましたが、その実装になぞらえてiPhoneでも同様のサンプルを作ろうと思います。汎用的な基底クラスを作成することで開発効率が向上することを切に願います。
[Android]サーバーと通信する方法

作成するクラスとその説明

リクエストパラメータクラス RequestParam abstract
⇒サーバに送信するデータを保持したクラス。業務や処理によって送信したいパラメータは異なってくるので、基本的には当該クラスのサブクラスを作成し、送信したいパラメータのカスタマイズを行って利用します
レスポンスパラメータクラス ResponseParam abstract
⇒サーバから受けたデータを保持したクラス。業務や処理によって受信したいパラメータは異なってくるので、基本的には当該クラスのサブクラスを作成し、受信したいパラメータのカスタマイズを行って利用します
リクエスト送信クラス Request
⇒リクエストパラメータをサーバに送信し、レスポンスパラメータをサーバから受けるためのクラス

RequestParam

RequestParam.h

#import <Foundation/Foundation.h>
#import "ResponseParam.h"

@interface RequestParam : NSObject {
    /** デバイスID */
    NSMutableString *deviceId;
    /** サーバURL */
    @public
    NSString *serverUrl;
    /** メッセージタイプ */
    @public
    NSString *messageType;
    /** yyyymmddhhmmss */
    @public
    NSString *dateTime;
    /** 接続タイムアウト */
    int connectionTimeout;
    /** データ取得のタイムアウト */
    int soTimeout;
}

/**
  * レスポンスパラメータを返す。
  */
- (ResponseParam *) getResponseParam;

/**
  * サーバに送信するパラメータを取得する。
  */
- (NSMutableDictionary *) getSendParameter;

@end

deviceIdとしてはUDIDではなくUUIDを利用するようにしましょう。
AppleにRejectされてしまします。

    NSMutableString *deviceId;

これはRequestParamをオーバーライドしたサブクラスに対応するレスポンスパラメータを返すためのメソッドです。あくまで、RequestParamとResponseParamを処理を共通化しただけの基底クラスなので個別の実装はサブに任せます。

- (ResponseParam *) getResponseParam;

Request.m内に実装されていますが、サーバに送信するパラメータをGenerateする実装をサブクラスでこちらに行います。
サーバに送信したい情報をキー バリューとしてNSMutableDictorinaryにセットする実装を書けばよいです。

- (NSMutableDictionary *) getSendParameter;

次に実装に移ります。(あくまでRequestParam.hの実装で前述しているサブクラスとは異なる)
RequestParam.m

#import "RequestParam.h"
#import "FrameworkConstants.h"

@implementation RequestParam

- (id) init {
    self = [super init];
    connectionTimeout = 60;
    soTimeout = 60;
    // ここでは適当な時間をセットしています。
    dateTime = @"yyyymmdd";
    return self;
}

- (ResponseParam *) getResponseParam {
    // サブクラスでオーバーライドしてください。
    return nil;
}

- (NSMutableDictionary *) getSendParameter {
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    [params setObject:messageType forKey:MESSAGE_TYPE];
    [params setObject:dateTime forKey:DATE_TIME];
    return params;
}

@end

ResponseParam

ResponseParam.h

#import <Foundation/Foundation.h>
#import "FrameworkConstants.h"

@interface ResponseParam : NSObject {
    /** HTTPステータスコード */
    NSInteger *httpStatusCode;
    /** エラーメッセージ */
    NSString *errorMessage;
    /** 生の電文 */
    NSMutableDictionary *rawData;
}

/**
  * 生の電文データを設定する。
  */
- (void) setRawData:(NSString *) str;

/**
  * データを解析してフィールドに設定する。
  */
- (void) setData;

/**
  * 通信終了後の処理。
  */
- (void) afterResponse;

@end

この実装は基本的に戻り電文の形式がかわらなければ同一の実装でOKです。
私が想定した電文の形式は「key1=value1&key2=value2&….&END」です。

- (void) setRawData:(NSString *) str;

これはサブクラスでの実装となります。さすがにマップに詰まっている状態だと実装が煩雑になるのでサブクラスにフィールドを定義し、そこに設定する実装を追加します。

- (void) setData;

通信終了後の個別の実装はここに記述します。

- (void) afterResponse;

ResponseParam.m

#import "ResponseParam.h"

@implementation ResponseParam

- (void) setRawData:(NSString *) str {
    NSArray *values = [str componentsSeparatedByString:AMPARSAND];
    rawData = [[NSMutableDictionary alloc] init];
    for (NSString *s in values) {
        if ([s isEqualToString:END]) {
            break;
        }
        NSArray *arr = [s componentsSeparatedByString:EQUAL];
        [rawData setObject:[arr objectAtIndex:1] forKey:[arr objectAtIndex:0]];
    }
}

- (void) setData {
    
}

- (void) afterResponse {
    // should not be use.
}

@end

Request

Request.h

#import <Foundation/Foundation.h>
#import "HattrickConstants.h"
#import "RequestParam.h"
#import "ResponseParam.h"

@protocol HttpMethodFinishDelegate;
@interface Request : NSObject {
    /** レスポンスデータバッファ */
    NSMutableData *buffer;
    /** リクエストパラメータ */
    RequestParam *requestParam;
    /** レスポンスパラメータ */
    ResponseParam *responseParam;
}

@property (nonatomic, assign) id <HttpMethodFinishDelegate> httpMethodFinishDelegate;

/**
  * POSTメソッドで非同期リクエストを送信する。
  */
-(void)sendAsyncPostRequest;

/**
  * POSTメソッドで同期リクエストを送信する。
  */
-(void)sendSyncPostRequest;

/**
  * GETメソッドで非同期リクエストを送信する。
  */
-(void)sendAsyncGetRequest;

/**
  * GETメソッドえ同期リクエストを送信する。
  */
-(void)sendSyncGetRequest;

/**
  * リクエストパラメータを設定する。
  */
-(void)setRequestParam:(RequestParam *) _requestParam;

/**
  * リクエストパラメータに対応するレスポンスパラメータを返す。
  */
-(ResponseParam *)getResponseParam;

/**
  * 同期リクエストを送信する。
  */
-(void)sendSyncRequest:(BOOL)post;

/**
  * 非同期リクエストを送信する。
  */
-(void)sendAsyncRequest:(BOOL)post;

@end

@protocol HttpMethodFinishDelegate <NSObject>

@optional
-(void)onHttpSyncMethodSuccess:(Request *)request;
-(void)onHttpSyncMethodFail:(Request *)request;
-(void)onHttpAsyncMethodSuccess:(Request *)request;
-(void)onHttpAsyncMethodFail:(Request *)request;
@end

非同期でPOST/GETリクエストを送信します。戻りはメソッド内ではなくコールバック(delegate)メソッドで受けてあげます。

-(void)sendAsyncPostRequest;
-(void)sendAsyncGetRequest;

同期でリクエストを送信します。戻りはメソッド内でうけるので画面が固まってしまうというユーザビリティの悪さでデメリットがありますが、アプリが起動する上で必要なものに関しては同期処理で受ける必要があるかと思います。非同期処理ほど使うことは少ないと思いますが。

-(void)sendSyncPostRequest;
-(void)sendSyncGetRequest;

上記のリクエスト送信メソッドはすべて以下の二つのメソッドに処理を委ねます。

-(void)sendSyncRequest:(BOOL)post;
-(void)sendAsyncRequest:(BOOL)post;

リクエスト送信前にリクエストパラメータを設定します。

-(void)setRequestParam:(RequestParam *) _requestParam;

リクエスト送信後(レスポンス受信後)にそのレスポンスの実態を使用したクラスから参照したい場合に利用します。

-(ResponseParam *)getResponseParam;

最後にプロトコルの宣言をしています。これは呼び出し元上位クラスに通信正常終了・異常終了を通知するためのコールバックです。

@protocol HttpMethodFinishDelegate <NSObject>
 @optional
-(void)onHttpSyncMethodSuccess:(Request *)request;
-(void)onHttpSyncMethodFail:(Request *)request;
-(void)onHttpAsyncMethodSuccess:(Request *)request;
-(void)onHttpAsyncMethodFail:(Request *)request;
@end

Request.mでこれらを実装してやることで、Javaでいうコールバックの実装が可能となります。

Request.m

#import "Request.h"

@implementation Request

@synthesize httpMethodFinishDelegate;

- (id) init {
    return self;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (void) sendAsyncPostRequest {
    [self sendAsyncRequest:YES];
} 

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendSyncPostRequest {
    [self sendSyncRequest:YES];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendAsyncGetRequest {
    [self sendAsyncRequest:NO];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendSyncGetRequest {
    [self sendSyncRequest:NO];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (void) setRequestParam:(RequestParam *)_requestParam {
    requestParam = _requestParam;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (ResponseParam *) getResponseParam {
    if (responseParam) {
        return responseParam;
    } else {
        return [requestParam getResponseParam];
    }
}

/**
  * サーバからレスポンスがかえってきたときのコールバック。
  */
- (void) connection:(NSURLConnection *)conn didReceiveResponse:(NSURLResponse *)response {
    NSLog( @"size = %lld", [response expectedContentLength] );
    NSLog( @"%@", [response MIMEType]); 
    NSLog( @"%@", [response textEncodingName]);
    responseParam = [self getResponseParam];
}

/**
  * サーバからデータがかえってきたときのコールバック。
  */
- (void) connection:(NSURLConnection *)conn didReceiveData:(NSData *)receiveData {
    [buffer appendData:receiveData];
    NSString *message = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
    [responseParam setRawData:message];
    
}

/**
  * サーバと接続できなかった場合のコールバック。
  */
- (void) connection:(NSURLConnection *)conn didFailWithError:(NSError *)error {
    NSLog(@"Connection failed: %@", [error localizedDescription]);
    [httpMethodFinishDelegate onHttpAsyncMethodFail:self];
}

/**
  * データのロードが完了したときのコールバック。
  */
- (void) connectionDidFinishLoading:(NSURLConnection *)conn {
    NSLog(@"Succeed! Received %d bytes of data.", [buffer length]);
    NSString *contents = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
    NSLog(@"%@", contents);
    [responseParam afterResponse];
    [httpMethodFinishDelegate onHttpAsyncMethodSuccess:self];
}

-(void)sendAsyncRequest:(BOOL)post {
    NSString *httpMethod;
    if (post == YES) {
        httpMethod = @"POST";
    } else {
        httpMethod = @"GET";
    }
    NSURL *url = [NSURL URLWithString: requestParam->serverUrl];
    NSMutableString *value = [[NSMutableString alloc] init];
    NSMutableDictionary *values = [requestParam getSendParameter];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:httpMethod];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"];
    
    if (values) {
        for (id key in values) {
            [value appendString:key];
            [value appendString:EQUAL];
            [value appendString:[values objectForKey:key]];
            [value appendString:AMPARSAND];
        }
        NSData *requestData = [value dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody: requestData];
    }else {
        NSLog(@"%@", requestParam->serverUrl);
    }
    
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    if (conn) {
        buffer = [[NSMutableData alloc] init];
    } else {
        UIAlertView *alert = [
                              [UIAlertView alloc]
                              initWithTitle : @"ConnectionError"
                              message : @"ConnectionError"
                              delegate : nil
                              cancelButtonTitle : @"OK"
                              otherButtonTitles : nil
                              ];
		[alert show];
    }
}

-(void)sendSyncRequest:(BOOL)post {
    NSString *httpMethod;
    if (post == YES) {
        httpMethod = @"POST";
    } else {
        httpMethod = @"GET";
    }
    NSURL *url = [NSURL URLWithString:requestParam->serverUrl];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSURLResponse *response = nil;
    NSError *error = nil;
    
    NSMutableString *value = [[NSMutableString alloc] init];
    NSMutableDictionary *values = [requestParam getSendParameter];
    [request setHTTPMethod:httpMethod];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"];
    if (values) {
        for (id key in values) {
            [value appendString:key];
            [value appendString:EQUAL];
            [value appendString:[values objectForKey:key]];
            [value appendString:AMPARSAND];
        }
        NSData *requestData = [value dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody: requestData];
    }else {
        NSLog(@"%@", requestParam->serverUrl);
    }
    
    NSData *data = [
                    NSURLConnection
                    sendSynchronousRequest : request
                    returningResponse : &response
                    error : &error
                    ];
    // error
	NSString *error_str = [error localizedDescription];
	if (0 < [error_str length]) {
        [httpMethodFinishDelegate onHttpSyncMethodFail:self];
		return;
	}
    // response
    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    responseParam = [self getResponseParam];
	[responseParam setRawData:result];
    [responseParam afterResponse];
    [httpMethodFinishDelegate onHttpSyncMethodSuccess:self];
}

@end

最後に

次回はサンプルでも作成してみようかと思います。

[iPhone]iPhone版Facevote

はじめに

Android版・Web版に続き、近々iPhone版もリリースする予定です。
Android版
Web版

投票画面

画面上に二人の女性が表示され、Hotだと思う方をタップすると投票されます。投票後は詳細画面へと遷移し、WEBブラウザ経由で直接メッセージ送信やその子の友達の検索が可能です。

ランキング画面

投票結果をもとにもっともHotな女の子を表示するランキング画面です。インターフェースは親しみが深いCoverFlow形式にし、横方向にスクロールすることで1位から25位までのランキングの表示が可能となっています。

履歴画面

投票画面で投票した結果を新しい順に左上からならべます、直観的に入れ替え・削除ができるようにドラッグによる移動をサポートしています。また、個別の画像をタップすることで詳細画面への遷移が可能となります。

設定画面

アプリケーション内での挙動の制御や各種インフォメーションを発信する画面です。

まとめ

Android版での反省(とくにUX)を考え、直観的な操作・普段したしんでいるUIをベースにした画面にしました。

[Objective-c]CGPointをNSArrayに入れる

CGRectをNSArrayに直接入れる

複数の画像を移動させるようなUIを作成しているときに、デフォルトの位置を覚えさせたいと思いNSArrayにCGPointを入れようとしてみた。

CGRect rect;
[defaultPosition addObject:rect];

NSArrayは引数にid(オブジェクト)しかとらないからCGPoint(構造体)は引数にとれないよと言われてしまいコンパイルできない。(下の通り)

対応方法

①CGPointをメンバ変数にもつラッパーオブジェクトの作成
②NSValueを用いてエンコーディングしたCGPointをオブジェクトを作成

恐らく②が正攻法なので、実装してみる。ちなみにCGPointでなくても利用可。

・CGPointを配列に入れる

CGRect rect = self.view.frame;
NSArray *array = [[NSArray alloc] init];
[array addObject:[NSValue value:&rect withObjCType:@encode(CGRect)]];

キーは以下、Objective-cを日本語にする。

[NSValue value:&rect withObjCType:@encode(CGRect)]

構造体rectのアドレスを取り出し(&rect)、CGRectでエンコードしてNSValueのオブジェクトを生成する。
@encodeは「指定された構造体をエンコードして文字列を返す」コンパイラディレクティブ。

・CGPointを配列から取り出す

CGRect rect;
[(NSValue *)[array objectAtIndex:0] getValue:&rect];

JavaでもArrayListは引数に基本型のintをとらずに、そのラッパーのIntegerをとるのと一緒。
そもそもなぜコレクションがプリミティブ型をとらないかはどうしてだろう。オブジェクトはメモリにアドレス情報を保持しているのに対してプリミティブはメモリに値自体を保持しているというおおきな構造上の違いがあるから、コレクションの実装上オブジェクトしかとれない、と理由付けしてみる。

[iPhone]UIViewを並べてスワイプしPageControlと連携する

概要

iPhoneのHOME画面を想像していただければ。

スワイプして2ページ目、3ページ目へと進むタイプです。

xibファイルにコンポーネントを配置

以下の三つのファイルでiPhoneのHOMEのような動きを実装していきます。
SwipeSampleViewController.xib
SwipeSampleViewController.h
SwipeSampleViewController.m

まず、xibファイルにinterfaceb builderを利用してPage ControlとUIScrollViewを配置します。

次にSwipeSampleViewController.hにアウトレット接続をします。

# SwipeSampleViewController.h

#import <UIKit/UIKit.h>

@interface SwipeSampleViewController {
}

@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
# 1
- (IBAction)pagingAction:(id)sender;
@end

# 1ではPageControlがタップされたときにUIScrollViewをスクロールさせるためのActionです。

# 1
- (IBAction)pagingAction:(id)sender;

フリックしたときにPageControlを同期させる必要があるので、SwipeSampleViewController.hに以下を追加します。

@interface SwipeSampleViewController : UIViewController <UIScrollViewDelegate> 

これは、スクロールイベントがキャッチされたときのコールバックを受け取るためのDelegateです。
SwipeSampleViewController.hの実装クラスで指定のメソッドをオーバーライドすることで、スワイプイベントをキャッチすることが可能となります。

次にSwipeSampleViewController.mを実装していきます。

# SwipeSampleViewController.m

#import "SwipeSampleViewcontroller.h"

@implementation SwipeSampleViewController
@synthesize scrollView;
@synthesize pageControl;

(omission)

#pragma mark - View lifecycle

- (void)viewDidLoad
{
omission
}

# 2
- (IBAction)pagingAction:(id)sender {
    NSLog(@"paging");
    CGRect frame = scrollView.frame;  
    frame.origin.x = frame.size.width * pageControl.currentPage;
    frame.origin.y = 0;  
    [scrollView scrollRectToVisible:frame animated:YES];
}

# 3
- (void)scrollViewDidScroll:(UIScrollView *)sender {  
    NSLog(@"swipe %f, %f" ,scrollView.contentOffset.x, scrollView.frame.size.width);
    CGFloat pageWidth = scrollView.frame.size.width / 2;  
    pageControl.currentPage = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;  
}

@end

# 2ではscrollViewのフレームを取得し、pageControlの現在のページ番号からscrollViewをスクロールさせている実装です。
X座標の視点をscrollViewのサイズとpageControlの現在のページを乗じたものとしています。最後にscrollRectToVisibileメソッドを呼び出して画面遷移させています。

# 2
- (IBAction)pagingAction:(id)sender {
    NSLog(@"paging");
    CGRect frame = scrollView.frame;  
    frame.origin.x = frame.size.width * pageControl.currentPage;
    frame.origin.y = 0;  
    [scrollView scrollRectToVisible:frame animated:YES];
}

# 3がUIScrollViewDelegateのメソッドをオーバーライトした実装です。

# 3
- (void)scrollViewDidScroll:(UIScrollView *)sender {  
    NSLog(@"swipe %f, %f" ,scrollView.contentOffset.x, scrollView.frame.size.width);
    CGFloat pageWidth = scrollView.frame.size.width / 2;  
    pageControl.currentPage = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;  
}

以上でUIScrollViewとPageControlを連携させるための実装は終了となります。

UIScrollViewにUIViewをはめていく

SwipeSampleViewController.mでプログラマティックにUIViewを追加していきます。二ページ遷移を考えて、左側のViewをleftView、右側のViewをrightViewとします。

まず、PageControlのPagesプロパティを2にしておきます。
※xibから設定可能。

# SwipeSampleViewController.h
# SwipeSampleViewController.h
 
#import <UIKit/UIKit.h>
 
@interface SwipeSampleViewController : UIViewController <UIScrollViewDelegate> {
    UIView *leftView;
    UIView *rightView;
}

(omission)

SwipeSampleViewController.mのviewDidLoadメソッドでleftViewとrightViewをインスタンス化します。

# SwipeSampleViewController.m
- (void)viewDidLoad
{
    [super viewDidLoad];
    // leftViewとrightViewの幅は320
    leftView = [[UIView alloc] initWithFrame:CGRectMake(x, y, width, height)];
    rightView = [[UIView alloc] initWithFrame:CGRectMake(x, y, width, height)];
    // 右の画面に配置されるビューは左のビューの幅だけ右にずらしてあげる必要がある。
    CGRect rect = rightView.frame;
    rect.origin.x = leftView.frame.size.width;
    rect.origin.y = 0;
    rightView.frame = rect;
    [self.scrollView addSubview:leftView.view];
    [self.scrollView addSubview:rightView.view];
}

右の見えていない箇所に配置されるViewを左の画面の幅分だけずらしてあげるのがミソです。

[iPhone]カスタムビューの作り方

カスタムビューとは

よく使うがデフォルトで用意されたいないビューを独自に実装して利用するビューのことである。このカスタムビューを整備することでビューを開発する効率が格段にあがることは言うまでもない。大きくカスタムビューを作成する方法として以下の二つがある。

① UIViewのサブクラスを作成し(void)drawRect:(CGRect)rectをオーバーライドしてビューを実装、それを他のControllerから呼び出す形で利用する。
② Controllerで通常のように実装して、それを他のControllerから呼び出す形で利用する。

①に関するサンプルコードはインターネット上にたくさんおちていたが、②に関するサンプルコードは意外と落ちていなかったので書いてみる。

作成するもの

アイコンとアイコンタイトルをセットにしたコンポーネントを作成しようと思います。
イメージ的にはiPhoneのHOME画面の一つ一つのアイコンです。
※ここでは「いいものを作る」というよりは「作り方を理解する」にフォーカスをあてて記事を書いていきますので、なんでアイコンやねんはとりあえずなしで。

IconSampleViewControllerの作成

適当なサンプルプロジェクトをたててください。ここでは、ヘッダーの作成部分とそれをメインビューに取り込む方法を中心に記事を書いていきます。
まず、UIViewControllerのサブクラスとしてIconSampleViewControllerを作成していきます。

xibファイルの編集

まず、デフォルトのxibファイルでは下の図の通り、Viewがぽつんと配置されているので削除し、カスタムレイアウトを作成していきます。

ベースViewとなるUIViewを配置します。

この状態だとクラスファイルとの紐付け情報が欠落してしまっているので、関連づけを行います。

アイコン(UIImageView)とアイコン名称(UILabel)を配置します。

これでxibの編集はいったん終了です。各コンポーネントとのアウトレット接続は忘れずに行っておきましょう。
アウトレット接続の方法については以下の記事を参照してください。
[iPhone]UIButtonを使ってみる

カスタムビューをロードする

次に今回作成したカスタムビューをコンポーネントとしてビューにロードする方法を見ていきます。6つのコンポーネントとしてメインのビューコントローラに表示させる実装にします。レイアウトファイルは以下のキャプチャーを参照してください。

各ビューをアウトレット接続し、それに対応するカスタムビュー(IconSampleViewController)を宣言します。

#import <UIKit/UIKit.h>
#import "IconSampleViewController.h"

@interface ViewController : UIViewController {
    IconSampleViewController *cont1;
    IconSampleViewController *cont2;
    IconSampleViewController *cont3;
    IconSampleViewController *cont4;
    IconSampleViewController *cont5;
    IconSampleViewController *cont6;
}
@property (strong, nonatomic) IBOutlet UIView *view1;
@property (strong, nonatomic) IBOutlet UIView *view2;
@property (strong, nonatomic) IBOutlet UIView *view3;
@property (strong, nonatomic) IBOutlet UIView *view4;
@property (strong, nonatomic) IBOutlet UIView *view5;
@property (strong, nonatomic) IBOutlet UIView *view6;

@end

実装クラスのViewDidLoadメソッドでアウトレット接続したビューとカスタムビューの紐付けを行います。

#import "ViewController.h"

@implementation ViewController
@synthesize view1;
@synthesize view2;
@synthesize view3;
@synthesize view4;
@synthesize view5;
@synthesize view6;

(omission)

- (void)viewDidLoad
{
    [super viewDidLoad];
	
    // 各view1,... ,view6にマッピングしていく
    cont1 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view1 addSubview:cont1.view];
    
    cont2 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view2 addSubview:cont2.view];
    
    cont3 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view3 addSubview:cont3.view];
    
    cont4 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view4 addSubview:cont4.view];
    
    cont5 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view5 addSubview:cont5.view];
    
    cont6 = [[IconSampleViewController alloc] initWithNibName:@"IconSampleViewController" bundle:nil];
    [self.view6 addSubview:cont6.view];
    
    // 画像ファイルは用意していないのでいったんブランクとします。
    [cont1 setProperty:@"" :@"VIEW1"];
    [cont2 setProperty:@"" :@"VIEW2"];
    [cont3 setProperty:@"" :@"VIEW3"];
    [cont4 setProperty:@"" :@"VIEW4"];
    [cont5 setProperty:@"" :@"VIEW5"];
    [cont6 setProperty:@"" :@"VIEW6"];
}

(omission)

@end

補足
IconSampleViewController.h

#import <UIKit/UIKit.h>

@interface IconSampleViewController : UIViewController

@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *label;

- (void) setProperty:(NSString *)imageName:(NSString *)labelName;

@end

IconSampleViewController.m

#import "IconSampleViewController.h"

@implementation IconSampleViewController
@synthesize imageView;
@synthesize label;

(omission)
- (void) setProperty:(NSString *)imageName:(NSString *)labelName {
    self.label.text = labelName;
    // 画像の読み込みは未実装
}

(omission)

@end

実行結果

エミュレータで実行します。

これで、IconSampleViewControllerをコンポーネントとしたレイアウトの完成となります。同じ原理で、実装していけばよく使われるレイアウトパターンをコンポーネント化して開発効率の向上が見込めそうです。

[iPhone]Siriが使いたい

ぜんぜん進まない

新しいiOS 5.1のソフトウェアをダウンロードしようとするがまったく終わらないので、ググってみるとDNSの設定をしたらダウンロードが始まるとのこと。

この二つのDNSサーバは西洋海岸にあるOpenDNSのDNSサーバとのこと。OpenDNSはドメイン名のキャッシュがでかいため高速で名前の逆引きが可能、フィッシング機能が強力らしい。プロバイダのDNSだと、iOSのソフトウェアがおいてあるサーバの名前がもしかしてまだ高速で引けないのかもとも思いつつ(遅すぎるから違う原因な気もするが)。。。
OpenDNS

1時間強でダウンロードは終了。iPhoneにインストール後、Siriの言語設定を変更して利用しようとすると。。。

現状解決せずで、道のりは遠い。。。「もうすぐの定義が知りたい」

#Update 2012/3/12 09:34
2012年3月11日未明に使用可能になりました。
※使えるようになるまでに24時間以上かかるなんて。。。

[iPhone]このソフトウェア・アプリはよく使うシリーズ(2)

iPhone / iPod touch

iPhone / iPod touchにはこんなアプリがはっています。

GoodReader

いろんな形式に対応したビューアとなり、Wifi経由・USB経由ででPCとのファイル転送が可能、Zip形式のファイルの解凍などかなりいろんなことができるアプリです。お値段は有料で115円となっていますが、それを払うだけの価値はあるほどマルチに活躍してくれます。
Go to Wiki -> GoodRader
Go to Site -> GoodRader

Pocket Informant

Google Calendar、Google Tasks、Toodledo(後述)を一元管理できるアプリである。Google Calendarのスケジュール機能で予定を入れ、Toodledoでタスク管理をしながらその達成度合いや進捗を把握するという使い方をしている。もちろん双方向同期が可能、ただし有料(高め)です。
Go to Site -> Pocket Informant

Saisuke

Google Calendarと双方向同期が可能で見た目もよいアプリです。金額も少々張ります。欠点を一つ言うならば通信(同期)スピードが遅いでしょうか。私はそんなにスピードを求めていないので気になりませんが、気になりそうな方は無料版で試してみてください。
Go to Site -> Saisuke

TED

あらゆる分野の人物が講演する動画を無料で見ることができるアプリ。アプリ内へのダウンロードも可能なので、Wifi配下で動画をダウンロードしておき、外出先でサクサク視聴なんて使い方がお勧めです。
Go to Wiki -> TED
Go to Site -> TED

Twitter

140文字以内の単文を投稿できるサービスのiPhone版クライアント。複数アカウントを持っている場合の切り替えも簡単で、そのほかの機能についてはWeb経由でアクセスする場合と同等と思われる。TwitterのAPIも公開されているので、誰でもカスタマイズしたTwitterクライアントを作成可能。ただし、アクセス制限があったはず。(個人で使う場合には問題にならない程度ではあるが)Twitterで友達以外にフォローされるにはこつがあるらしい、うちわの事象ばかりをツイートのネタにしているとNG(電車乗ったなう)で、見られる人に対して有益な情報+アルファを与えられることが好まれる。

Twitter

Facebook

Twitterよりも友達同士のつながりをもつことができるソーシャルネットワーク。全世界に8億人のユーザがおり、APIも公開されているので誰しもがFacebookを介したアプリケーションの作成が可能である。いまではFacebookのアカウントを使ってログインしサードパーティのサービスを使うなどWebサービスの基盤になりつつある。携帯電話->スマートフォンに次ぐイノベーションだと思う。最近でいうとソーシャルギフトという分野でいろいろなサービスが生まれている。

Go to Wiki -> Facebook

Skype

P2Pの通話やチャットが可能なアプリケーション。

Go to Wiki -> Skype

LINE

Skypeのようなアプリケーション。通話の品質には定評あり。
Go to Wiki -> LINE

foursquare

位置情報を利用し、その場所で何をしているのかを発信できるアプリケーション。ユーザがある場所でCheck inすることで、さまざまな得点が与えられるとともに、TwitterやFacebookのソーシャルネットワークを介して情報を発信することが可能。
Go to Wiki -> foursquare

LinkedIn

ビジネスライクなソーシャルネットワークサービス。自分の学歴やこれまでの経歴、どんな会社でどんな職位についているかを登録可能。履歴書のようなものを書く欄があり、LinkedInを介して求人に対するエントリーも可能。登録人数は全世界で1億人を超える。
Go to Wiki -> LinkedIn

Evernote

名前を通りクラウド上にノートを保存しておけるWebサービス。
Go to Wiki -> Evernote

Mobile Mouse

iPhoneやiPod touchに(クライアントアプリとして)インストールすることでパソコン(サーバアプリをインストールした)を操作できる、マウス的な機能はもちろん、日本語入力にも対応している。ちょっとベッドの上からパソコンを操作したい場合や、Mac miniをテレビにつないで操作したいときに便利。Macのランチャーとかもエミュレートてくれる。

Go to iTunes -> Mobile Mouse

Mocha VNC Lite

リモートデスクトップクライアント。Mobile Mouseとの違いはアプリ上にパソコンのディスプレイが表示可能なこと。無料版では機能制限があるが、私的に利用する分には十分。外出先からVPNを利用して自宅LAN内に接続し、このアプリを使って操作、なんてことも可能。(設定は以下のリンク参照)

Google+

Google+のiPhone / iPod touchクライアントアプリ。

機能詳細

Sleep Cycle

睡眠を管理してくれるアプリ。枕元にアプリをおいて、睡眠することで、振動から眠りの浅さや深さを記録するとともに、日付毎に履歴管理もしてくれるので睡眠管理にはばっちり。このアプリのナイスなポイントは目覚まし機能がついており、起床時間の前後30分程度で眠りの浅いタイミングを判断し、目覚ましをならしてくれるところ。

iCompta

家計簿アプリのiPhone / iPod touchクライアントアプリ。Wifi経由やDropbox経由でパソコンと同期可能。

[iPhone]ジェスチャーイベントを検出する(タップ)

UIGestureRecognizer

iPhoneのアプリケーションに多用されるドラッグ・パン・フリック・ピンチアウト/インといったジェスチャーを簡単に検出可能としたクラスです。このクラスはジェスチャー処理の基本となるクラスとなり、タップやパンといった個別のジェスチャーの処理に特化したクラスがUIGestureRecognizerクラスのサブクラスとして使用されます。

ジェスチャーイベントを検出する基本的なクラス

クラス 説明
UITapGestureRecognizer タップを検出する
UIPanGestureRecognizer パン(ドラッグ)を検出する
UIPinchGestureRecognizer ピンチイン/アウトを検出する
UIRotationGestureRecognizer 回転を検出する
UISwipeGestureRecognizer スワイプを検出する
UILongPressGestureRecognizer 長押しを検出する

ジェスチャーレコグナイザの使い方

①ジェスチャーレコグナイザのインスタンスを生成する。
②検出したいジェスチャーのためのプロパティを設定する。
③addGestureRecognizer:メソッドを使ってジェスチャーレコグナイザをビューに登録する。
④コールバックメソッドを定義する。

今回作成するもの

前回作成した、タッチイベントを検出して画像を移動させるプロジェクトを利用してダブルタップしたときのみ画像を移動させるプログラムを作成します。

ダブルタップを検出する方法

以下の手順を踏みます。
(1)ジェスチャーレコグナイザのインスタンスを生成する
(2)検出したいプロパティを設定する
(3)addGestureRecognizerメソッドを使ってジェスチャーレコグナイザをビューに登録する
(4)ジェスチャーが検出されたときに実行されるメソッドを定義する

(1)ジェスチャーレコグナイザのインスタンスを生成する

インスタンスの初期化処理はビューがロードされたときに呼ばれる、(void)viewDidLoadで実装してやります。

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];

ここで注意すべきは第二引数にある@selector(singleTap:)です。これは、ジェスチャーが検出されたときに実行されるメソッドをしており、セレクタという形式で指定されます。

(2)検出したいプロパティを設定する

今回は、一本の指でタップ回数が二回となるジェスチャーを検出したいので以下のようなプロパティの指定方法となります。

    tapRecognizer.numberOfTapsRequired = 2;
    tapRecognizer.numberOfTouchesRequired = 1;

「tapRecognizer.numberOfTapsRequired」が必要なタップ回数を指定しており、「tapRecognizer.numberOfTouchesRequired」が必要な指の本数を指定しています。

(3)addGestureRecognizerメソッドを使ってジェスチャーレコグナイザをビューに登録する

以下の通り、Viewに対してジェスチャーレコグナイザを登録します。

[self.view addGestureRecognizer:tapRecognizer];

(4)ジェスチャーが検出されたときに実行されるメソッドを定義する

ジェスチャーレコグナイザのインスタンスを生成したときにセレクターで、「singleTap」というメソッドを実行するという宣言を行ったのでそのメソッドを実装します。引数であるUITapGestureRecognizerからCGPointを取り出して、画像のセンターに設定します。

- (void) singleTap:(UITapGestureRecognizer *)recognizer {
    CGPoint point = [recognizer locationInView:self.view];
    mySignal.center = point;
}

不要な実装削除

前回の実装でタッチイベントを検出したときに、画像を移動させる実装を入れたのでコメントアウトしておきます。

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    //UITouch *aTouch = [touches anyObject];
    //CGPoint point = [aTouch locationInView:self.view];
    //mySignal.center = point;
}

動かしてみる

初期状態

シングルタップ – もちろん反応しません

ダブルタップ

(2)の箇所でプロパティをいろいろな値に変更してあげると、いろんなジェスチャーを検出できます。またエミュレータ上で二本指のタップを実現するためには、[option]を押しながら、タップ(エミュレータ画面上をクリック)すると実現できます。

[iPhone]タッチイベントを検出する

タッチイベントを検出するためには

タッチイベントを検出できるのはイベントの処理を行うUIResponderクラスのサブクラスになります。UIViewはUIResponderクラスのサブクラスなので、イベントを検出できるメソッドが利用できます。

タッチイベントを検出する基本的なメソッド

メソッド 呼び出されるタイミング
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 指でタッチしたとき
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 指を動かしたとき
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 指が離れたとき

今回作成するもの

今回はまっさらな画面をベースにタッチした箇所に画像を移動させるアプリケーションを作成します。今までつぎはぎしてきたプロジェクトはいったん忘れて新しいプロジェクトを作成します。→プロジェクトの作成方法

タッチイベントを実装する

Object libraryからImageViewをドラッグ&ドロップで配置します。そうすると空のImageViewが配置されるので、表示する画像を指定します。
まず、プロジェクトの任意の場所に画像ファイルを取り込みます。次にShow the Attributes Inspectorを選択し、Imageのドロップダウンリストを選択すると、取り込んだ画像が選択肢としてあがってくるので、選択します。(サイズ等は適宜調節してください。)

また、画像の座標をプログラマティックに変更するので、Outletで取り込んだ画像を接続します。

最初にも言及しましたが、UIViewはUIResponderのサブクラスなので、タッチしたときに発生したイベントをキャッチするメソッドをオーバーライドし、***ViewController.mに以下の実装を追加します。

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *aTouch = [touches anyObject];
    CGPoint point = [aTouch locationInView:self.view];
    mySignal.center = point;
}

簡単に説明を加えると、タッチイベントを取り出します。

    UITouch *aTouch = [touches anyObject];

タッチイベントから座標を取得します。

    CGPoint point = [aTouch locationInView:self.view];

画像の中心座標とタッチイベントから取得した座標を同期します。

    mySignal.center = point;

動かしてみる

初期状態

左上をタッチ

右下をタッチ

[iPhone]UIAlertViewを使ってみる

今回作成するもの

UIAlertViewはInterface Builderを使ってではなく、プログラマティックに作成します。Clearボタンをタップすると初期化(0にする)機能を追加していきます。

数字部分をタップしたときのイベントをつける

「Clear」ボタンを追加し、イベントを追加します。

UIAlertViewを表示させる実装

Clearボタンが押されたときAlertViewを表示させる実装を追加します。

- (IBAction)clearCounterAction:(id)sender {
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"確認" message:@"消去しますか?" delegate:self cancelButtonTitle:@"キャンセル" otherButtonTitles:@"消去", nil];
    [alertView show];
}

UIAlertViewを生成するメソッドとそのパラメータの説明は以下の通りです。

- (id) initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...
引数 説明
title タイトルとして表示させる文字列
message タイトルの下に表示される文字列(メッセージ)
delegate デリゲート(不要な場合にはnil)
cancelButtonTitle キャンセルボタンのタイトル(不要な場合にはにl)
otherButtonTitle そのほかのボタンのタイトル(カンマで区切って複数のボタンを設定できる。最後にnilを指定すること)

消去ボタンが押されたときの実装はデリゲートメソッド「alertView:clickedButtonAtIndex:」を定義します。

- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 1) {
        counter = 0;
        [myLabel setText:[NSString stringWithFormat:@"%d", counter]];
    }
}

buttonIndexでどのボタンが押されたのかを検知します。今回の例では
「buttonIndex = 0」のとき「キャンセル」、「buttonIndex = 1」のとき「消去」となります。

動かしてみる

最初の状態

clearを押した場合

キャンセルを押した場合

勿論、数字は0になりません。

消去を押した場合

数字が0になりました。