2012年2月19日日曜日

TwitterFrameworkの利用


4 - Twitter Frameworkの利用

iOS5になってから、iOS上で簡単にTwitterAPIを利用することができるようになりました。
OAuth認証をしなくていいので、とても便利です。

そこで今回はこのTwitterFrameworkを利用して自分のタイムラインを取得したり、投稿してみたりします。

準備しておくもの:Twitterのアカウント

今回の流れ
  • プロジェクトの準備
  • タイムラインの取得
  • タイムラインをUITableViewに表示
  • Twitterへ投稿
です。
ちなみに次回は、この辺をこまごまと改良して、よりそれっぽいクライアントを作っていく予定です。



プロジェクトの準備
それではまず、プロジェクトの準備です。
いつも通り、NewProjectから進み、今回はMaster-Detail Applicationをテンプレートとして選択します。
Device FamilyはiPhone
Use Automatic Reference Countingを今回はオンにしましょう。

プロジェクトの設定画面が出て来たら、次はフレームワークを追加します。
前回と同様にLinked Frameworks and Librariesから
  • AccountFramework
  • TwitterFramework
の二つを追加します。

多分こんな画面になってると思います。
これでプロジェクトの準備は完了です。

TwitterFrameworkはiOSでTwitterを使うときに用いるライブラリ、
AccountFrameworkは、iOSがTwitterのアカウントとかNotification Centerの制御をしてますが、それを一元管理・利用するFrameworkです。多分。

MasterViewController.hでクラス変数の追加などを行います。
まず、二つのヘッダファイル
  • Accounts/Accounts.h
  • Twitter/Twitter/h
を追加します。




今回のクラス変数は
  • つぶやきのユーザー名とその内容を保存しておくための配列、
  • アカウントに関する情報にまつわるもの
を追加します。
さらに、クラスメソッドとして、タイムラインを読み込んで配列にデータを追加するメソッド loadTimeline を追加します。

MasterViewController.h
は次のようになります。



タイムラインの取得
それでは実際にTwitterのAPIを叩いてタイムラインを取得しましょう。
viewDidLoadメソッドを以下のように書き換えます。
- (void)viewDidLoad
{
    [super viewDidLoad];

accountStore = [[ACAccountStore alloc] init];
accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
userNameArray = [[NSMutableArray alloc] initWithCapacity:0];
tweetTextArray = [[NSMutableArray alloc] initWithCapacity:0];
[self loadTimeline];
}


ここではクラス変数の初期化を行っています。本来はinitメソッドでやるべきだとは思います。
その後に、loadTimelineでタイムラインを読みに行っています。


ではそのloadTimelineを実装しましょう


- (void)loadTimeline{
 
 [accountStore requestAccessToAccountsWithType:accountType
       withCompletionHandler:^(BOOL granted, NSError *error) {
        if (granted) {
         
         if (account == nil) {
          NSArray *accountArray = [accountStore accountsWithAccountType:accountType];
          account = [accountArray objectAtIndex:0];
         }
        
         if (account != nil) {
          NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"];
          NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
          [params setObject:@"20" forKey:@"count"];
          [params setObject:@"1" forKey:@"include_entities"];
          [params setObject:@"1" forKey:@"include_rts"];
          
          TWRequest *request = [[TWRequest alloc] initWithURL:url
                     parameters:params
                     requestMethod:TWRequestMethodGET];
          [request setAccount:account];
          [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
           
           if (responseData) {
            NSError *jsonError;
            NSArray *timeline = [NSJSONSerialization JSONObjectWithData:responseData
                         options:NSJSONReadingMutableLeaves error:&jsonError];
            NSLog(@"%@", timeline);
           }
           
          }];
          
          
         }
         
         
        }
       }];
 
}


長いし、変な形してるしで大変ですが、
まずは

[accountStore requestAccessToAccountsWithType:accountType
withCompletionHandler:^(BOOL granted, NSError *error) {
     /*   コード  */
}];

では、アカウントストアの機能を使ってTwitterへリクエストを実行できるように認証を行ってます。
認証で、実行するのに時間を要するので、ここで処理を止めると他の機能へ支障が出てしまうかもしれません。
なので、ここではメソッドを実行して認証待ちを行い、その結果を受けた処理は別スレッドで行います。
その処理内容が 
withCompletionHandler:^(BOOL granted, NSError *error) {
/*   コード  */
}];
の部分です。


if (account == nil) {
NSArray *accountArray = [accountStore accountsWithAccountType:accountType];
account = [accountArray objectAtIndex:0];
}

この部分は利用するアカウントを取得しています。
accountArrayには、利用可能なアカウントが配列として格納されていて、今回はその中の一つ目のアカウントを利用します。

APIコールを行うURLとそのパラメータを設定します。
NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:@"20" forKey:@"count"];
[params setObject:@"1" forKey:@"include_entities"];
[params setObject:@"1" forKey:@"include_rts"];
TWRequest *request = [[TWRequest alloc] initWithURL:url
  parameters:params
requestMethod:TWRequestMethodGET];
[request setAccount:account];


WebアプリケーションのAPIはHTTPを利用して情報の投稿と取得を行います。
そのリクエストをHTTPリクエストと呼びます。HTTPリクエストにはいくつかの種類やパラメータがありますが、
基本的にはurlとメソッドを定義してリクエストを投げます。
今回用いるURLは
http://api.twitter.com/1/statuses/home_timeline.json
です。
このURLにブラウザでアクセスしても多分アクセスできないと思います。
今回はURLで自分のタイムラインを取得するようにしています。

次にパラメータを設定します。ここで用いているNSMutableDictionary型は
いわゆるハッシュや連想配列とよばれているもので、キーを指定してオブジェクトを追加して行きます。


がその追加の例です。キーのcountはタイムラインで取得する件数のことで、今回は20件としています。
他の条件については
で確認できます。

TWRequest *request = [[TWRequest alloc] initWithURL:url
  parameters:params
requestMethod:TWRequestMethodGET];

では、リクエストを生成しています。
さらにその後でアカウントをセットしてリクエストの生成は完了です。


[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData) {
NSError *jsonError;
NSArray *timeline = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves error:&jsonError];
NSLog(@"%@", timeline);
}
}];

でリクエストを実行し、タイムラインを取得します。
perfomeRequestWithHandlerメソッドで、リクエストを実行します。
その結果はresponceDataに入っています。
この形式はJSONと呼ばれる形式で取得しているので、アクセスのしやすい形に整形します。
これを自動的にやってくれるメソッドが
NSJSONSerialization JSONObjectWithData: options: error:

です。

この結果をNSLogで出力すると、多分自分のタイムラインがずらーっと出力されます。







タイムラインをUITableViewに表示
このままだと扱いにくいので、データをクラス変数に突っ込んで行きます。
NSLogで出力した部分を次のように変更します。

for (NSDictionary *tweet in timeline) {
 [tweetTextArray addObject:[tweet objectForKey:@"text"]];
 NSDictionary *user = [tweet objectForKey:@"user"];
 [userNameArray addObject:[user objectForKey:@"screen_name"]];
}
[self.tableView reloadData];

先ほどのJSONObjectWithDataメソッドの返り値のデータ構造は、配列の各要素にツイートの内容が納められています。
この結果を一つ一つ取り出して、tweetTextArrayとuserNameArrayに入れて行きます。
見慣れないfor文は、Objetcive-Cにある文法の一つで、配列の各要素にたいして処理を行うことのできるものです。

ツイートの入っている配列の各要素は中身がNSDictionary型です。
ツイートの内容はキーがtextNSString型で取り出すことができます。
ユーザー情報はuserNSDictionary型で取り出し、さらにキー値をscreen_nameで取り出すことができます。

すべてのデータを取り出した後は、テーブルビューのデータを再読み込みして表示します。


データを突っ込んだ後は、テーブルビューに表示する内容を作りましょう。

// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
 return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
 return [userNameArray count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    }

 // Configure the cell.
 cell.textLabel.text = [userNameArray objectAtIndex:indexPath.row];
 cell.detailTextLabel.text = [tweetTextArray objectAtIndex:indexPath.row];
 
    return cell;
}


numberOfSectionsInTableView
ではテーブルビューのセクション数を返します。今回は1で。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
では何個セルがあるかを返します。今回はツイートの数だけなので、userNameArrayかtweetTextArrayの数を返します。
countメソッドかプロパティで値を返すことができます


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
では結果を表示して行きます。
cellのスタイルはUITableViewCellStyleSubtitleとします。これはメイン、サブ二種類のテキストを貼付けることができます。
貼付けは以下のように行います。

cell.textLabel.text = [userNameArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [tweetTextArray objectAtIndex:indexPath.row];



これで実行すると次のようにタイムラインを表示することができます。



Twitterへ投稿
これは今回の宿題としましょう。
updateTimelineと同じように投稿できます。もちろん部分部分は異なりますが。
TWRequestMethodGETTWRequestMethodPOSTに変えたりで出来ます。



詳しくはここを参考にどうぞ!
https://dev.twitter.com/docs/ios/posting-images-using-twrequest


2012年2月15日水曜日

iPhoneで画像処理 - 2


iPhoneで画像処理

続いて、画素単位へのアクセスを行います。
案外簡単な作業だと思うんですが、この処理を行うにはいろいろと手続きを踏まないと行けません。

UIImage型から画素単位までアクセスし、再びUIImageに戻す手続きは次のデータ型を経ます。

UIImage → CGImageRef → CFDataRef → CGImageRef →UIImage

実に面倒です。おまけに、CGImageRefからCFDataRefへ変換する際にはmallocが発生するので、
処理時間もかかってしまいます。

では具体的にコードを書いて行きます。

CGImageRef cg = inputImage.image.CGImage;
size_t height = CGImageGetHeight(cg);
size_t width = CGImageGetWidth(cg);
size_t bytesPerRow = CGImageGetBytesPerRow(cg);
size_t bitsPerPixel = CGImageGetBitsPerPixel(cg);
CFDataRef dataRef = CGDataProviderCopyData(CGImageGetDataProvider(cg));
UInt8* pixel = (UInt8*)CFDataGetBytePtr(dataRef);

// 処理内容
for( int h = 0; h < height; ++h){
for (int w = 0; w < width; ++w) {
UInt8* buf = pixel + h * bytesPerRow + w * bitsPerPixel / 8;
UInt8 r, g, b;
// rgb値取得
r = *(buf + 0);
g = *(buf + 1);
b = *(buf + 2);
// rgbを入れ替えて値出力
*(buf+0) = b;
*(buf+1) = g;
*(buf+2) = r;
}
}

CFDataRef dst = CFDataCreate(NULL, pixel, CFDataGetLength(dataRef));
CGDataProviderRef dstDataProvider = CGDataProviderCreateWithCFData(dst);
CGImageRef dstCGImage = CGImageCreate(width, height, CGImageGetBitsPerComponent(cg),
  bitsPerPixel, bytesPerRow,CGImageGetColorSpace(cg),
  CGImageGetBitmapInfo(cg),dstDataProvider, NULL,
  CGImageGetShouldInterpolate(cg),CGImageGetRenderingIntent(cg));
UIImage *dstUIImage = [UIImage imageWithCGImage:dstCGImage];
outputImage.image = dstUIImage;

CGImageRelease(dstCGImage);
CFRelease(dst);
CFRelease(dataRef);
CFRelease(dstDataProvider);


UIImageからCGImageRefへの変換、アクセスは
CGImageRef cg = inputImage.image.CGImage;

で可能です。
その後、画像の幅、高さ、一行当たりのバイト数、一画素当たりのビット数などを取得します。
CFDataRef dataRef = CGDataProviderCopyData(CGImageGetDataProvider(cg));
UInt8* pixel = (UInt8*)CFDataGetBytePtr(dataRef);

では、CGImage型からCFDataRef型へ変換を行います。CFDataは多分汎用的に扱えるデータ型です。
画像以外にも文章、音声でも可能です。(多分)
CFDataGetBytePtrでその先頭アドレスを取得します。

処理はよくやるforループを二重にまわせば出来ます。
画素(h,w)のアドレスにアクセスするには

UInt8* buf = pixel + h * bytesPerRow + w * bitsPerPixel / 8;


でできると思います。最後の /8 はビット→バイトの変換です。あんまりマジックナンバーは使わない方がいいんだろうけど。

CFDataからUIImageへの変換は以下の手続きを踏んで行います。

CFDataRef dst = CFDataCreate(NULL, pixel, CFDataGetLength(dataRef));
CGDataProviderRef dstDataProvider = CGDataProviderCreateWithCFData(dst);
CGImageRef dstCGImage = CGImageCreate(width, height, CGImageGetBitsPerComponent(cg),
  bitsPerPixel, bytesPerRow,CGImageGetColorSpace(cg),
  CGImageGetBitmapInfo(cg),dstDataProvider, NULL,
  CGImageGetShouldInterpolate(cg),CGImageGetRenderingIntent(cg));
UIImage *dstUIImage = [UIImage imageWithCGImage:dstCGImage];

CGImageCreate関数が実に引数が多くてどうにかならんのかと思いますが、それは仕方ないんでしょうね。
それぞれの行の変換の流れを追うと、
  1. ポインタpixelから始まるメモリのCFDataRefを作る
  2. CFDataからCGDataProviderRefを作る
  3. CGDataProviderRefからCGImageRefを作る
  4. CGImageからUIImageを作る

という流れです。もっと簡便にしてくれたらいいのにと思いますが。。。


残りの部分では,UIImageViewに貼付けたり、後処理としてリリースをしたりしています。
CFなんとか、CGなんとかで始まる関数や型はC言語ベースで、自動的にリリースされないのでご注意ください。

微分フィルタを作ってみたらこんな感じでした。




わかりづらかったから微分値を4倍してます。

2012年2月11日土曜日

iPhoneで画像処理 - 1


今回はiOS上で画像処理を行います。
iPhoneやiPadにはカメラがついてたりして、いろんな画像処理を施したカメラアプリが登場しています。
今回はその入門編です。

大きく分けて
  • CoreImageを使ったフィルタリング
  • 画素単位へのアクセス

をやって行きたいと思います。



CoreImageを使ったフィルタリング
CoreImageとは,iOSやMacの画像クラスの一つです。CoreFilterと組み合わせて簡単なフィルタリングを行うことが出来ます.
UIImageとの違いは、UIImageはViewとして表示するのに適したクラスで、このCoreImageは画像の変換とか処理に特化している印象です。

ではまずはプロジェクトを作って行きましょう
テンプレートはSingleViewApplicationを選びます。次にライブラリを追加します。
左カラムよりプロジェクトを選択、その中の Linked Frameworks and Libraries の項から+マークをクリック、
CoreImage.frameworkをAddします。



つぎは、UIを編集します。
.xibファイルを選んで、UIImageViewを二つスライダーを設置します。
UIImageViewには処理前後の画像を、スライダーはフィルタのパラメータ調整に使います。


.hファイルを選んで、クラスを作って行きます。
今回は、UIImageView二つに加え、CIContextCIFilterの変数も追加します
メソッドはスライダーの変更をトリガーに呼ばれるメソッドを一つ作ります。

@interface SENSViewController : UIViewController{
 IBOutlet UIImageView *originalImage;
 IBOutlet UIImageView *filteredImage;
 CIContext *myCIContext;
 CIFilter *myCIFilter;
}

- (IBAction)sliderMoved:(id)sender;

それぞれのパーツをxibと接続します。
こんな感じに。
あと何か画像を追加しておいてください。できればカラーのものがいいと思います。
では、処理の追加に移りましょう
viewDidLoadを次のように変更します
- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
 
 
 originalImage.image = [UIImage imageNamed:@"dambo.jpg"];

 myCIContext = [CIContext contextWithOptions:nil];
 [myCIContext retain];

 myCIFilter = [CIFilter filterWithName:@"CIExposureAdjust"];
 [myCIFilter setDefaults];
 CIImage *srcImage = [CIImage imageWithCGImage:originalImage.image.CGImage];
 [myCIFilter setValue:srcImage forKey:@"inputImage"];
 [myCIFilter retain];

}

CIContextは処理画像をUIImageViewに貼付ける際に一度挟む中間処理的なものというイメージです。

CIFilterはフィルタリング処理を行ってくれるクラスです。フィルタの名前を指定して、初期化を行います。
今回は露光を擬似的に調整するフィルタ、CIExposureAdjustを用います

その後、フィルタのパラメータを与える必要があるんですが、
[myCIFilter setDefaults];
とすることで、デフォルトの値で初期化を行うことが出来ます。

さらに、その後で
[myCIFilter setValue:srcImage forKey:@"inputImage"];
では、入力パラメータとして処理対象の画像を指定しています。




retainについて
何度かメソッドが呼ばれています。これは、このインスタンスをまだ解放しないでねというメッセージを送っています。
これがなかった場合、viewDidLoadが終了するとmyCIContextやmyCIImageなどは解放されてしまいます
では、いつまで解放されないかというと、releaseメソッドを呼ぶまで解放されません
具体的には、Objective-Cのメモリ管理機構の話になるんで詳細は避けますが、インスタンスはそれぞれ、何個のインスタンスから
参照されているかというretainCounterというものを持っています。

  • retainをするとこのカウンタが+1され、
  • releaseをするとこのカウンタが-1されます。
カウンタが0となったときにオブジェクトは解放されます。



では、次はスライダーの値を変更したときに呼ばれるメソッドの実装を行います。

-(IBAction)sliderMoved:(id)sender{
[myCIFilter setValue:[NSNumber numberWithFloat:[(UISlider *)sender value]]
  forKey:@"inputEV"];
CIImage *dstImage = myCIFilter.outputImage;
CGImageRef myCGImage = [myCIContext createCGImage:dstImage
fromRect:dstImage.extent];
filteredImage.image = [UIImage imageWithCGImage:myCGImage];
CGImageRelease(myCGImage);
}


まず、最初にフィルタのパラメータの一つであるinputEVを設定しています。


NSNumber型は、整数型から浮動小数点型までをラップしてくれる便利なクラスです。今回は引数としてfloat型を与えるのでnumberWithFloatとしています。

フィルタ処理の結果は、フィルタのoutputImageというプロパティです。
多分このプロパティが呼ばれるときにフィルタリング処理を行っています。

その後、CIImage型からUIImage型までの変換を行っています。順番は

CIImage → CGImageRef → UIImage

という順番です。CGImage型はCoreGraphicsImageの略(多分)で、たしかハードウェアよりのなんかだったような…。

これを実行すると多分こんな感じになります。


どうしても一度CGImageRefをメモリ確保しているので、処理が遅くなってしまいますよね。
プロの方々はどうされてるんでしょうか…。






他のフィルタを使ってみる。

今回はフィルタとして露光量を擬似調整するCIExposureを使いましたが、他にはどういったフィルタがあるんでしょうか。
実装されているフィルタは以下のコードで確認することが出来ます。

 
 NSArray *options = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
 NSLog(@"options of filter");
 for (NSString *str in options) {
  CIFilter *filter = [CIFilter filterWithName:str];
  NSLog(@"%@", filter.attributes);
 }
名前だけ出力した結果が次のようになります。
  • CIAdditionCompositing
  • CIAffineTransform
  • CICheckerboardGenerator
  • CIColorBlendMode
  • CIColorBurnBlendMode
  • CIColorControls
  • CIColorCube
  • CIColorDodgeBlendMode
  • CIColorInvert
  • CIColorMatrix
  • CIColorMonochrome
  • CIConstantColorGenerator
  • CICrop
  • CIDarkenBlendMode
  • CIDifferenceBlendMode
  • CIExclusionBlendMode
  • CIExposureAdjust
  • CIFalseColor
  • CIGammaAdjust
  • CIGaussianGradient
  • CIHardLightBlendMode
  • CIHighlightShadowAdjust
  • CIHueAdjust
  • CIHueBlendMode
  • CILightenBlendMode
  • CILinearGradient
  • CILuminosityBlendMode
  • CIMaximumCompositing
  • CIMinimumCompositing
  • CIMultiplyBlendMode
  • CIMultiplyCompositing
  • CIOverlayBlendMode
  • CIRadialGradient
  • CISaturationBlendMode
  • CIScreenBlendMode
  • CISepiaTone
  • CISoftLightBlendMode
  • CISourceAtopCompositing
  • CISourceInCompositing
  • CISourceOutCompositing
  • CISourceOverCompositing
  • CIStraightenFilter
  • CIStripesGenerator
  • CITemperatureAndTint
  • CIToneCurve
  • CIVibrance
  • CIVignette
  • CIWhitePointAdjust

CIToneCurveは多分色調変換するフィルタですね。
CISepiaToneは色味をセピア調に変換してくれます。
他にも二つの色を合成するCIOverlayBlendModeなどがあります。

フィルタの詳細とそのパラメータはAppleのデベロッパー用サイトから確認できます。

例えばCISepiaToneは

CISepiaTone

Maps the colors of an image to various shades of brown.
Parameters
inputImage
CIImage class whose display name is Image.
inputIntensity
An NSNumber class whose attribute type is CIAttributeTypeScalar and whose display name is Intensity.
Default value: 1.00 Minimum: 0.00 Maximum: 0.00 Slider minimum: 0.00 Slider maximum: 1.00 Identity: 0.00
Member of
CICategoryBuiltInCICategoryStillImageCICategoryNonSquarePixelsCICategoryInterlacedCICategoryVideo,CICategoryColorEffect
Localized Display Name
Sepia Tone
と書いてあります。
パラメータは二つで、
入力画像のinputImage、どれくらいセピア感を出すかというinputIntensityです。
inputImageはCIImage型
inputIntensityはNUNumber型と書いてあります。

この辺をどうやって入力するか、ですが、
inputImageは先ほどの例と同じで

[myCIFilter setValue:srcImage forKey:@"inputImage"];

とすればOK.
inputIntensityは先ほどの"inputEV" と同じように

[myCIFilter setValue:[NSNumber numberWithFloat:(何らかの値)]
  forKey:@"inputIntensity"];

で設定できます。何らかの値はスライダーの値を引っ張ってくるなり、予め与えておくなりで行けます。

2012年2月9日木曜日

UIView を使いこなそう


今回の目次
  • UIViewの種類
  • UITableViewとUINavigationContoller
  • UIImageViewとUIScrollView
iOSの画面の構成は、UIViewに溢れています。

なので、UIViewを理解することは即ち、iOSの画面構成を理解する!といっても過言ではありません。
今回は、そんなUIViewについて説明して行きます。


UIViewの種類
UIViewの種類と一口に言っても、いろいろな種類があります。
例えば、
  • 画像を保持するUIImageView
  • 文字列を表示するUILabel (前回使いました)
  • 画面のスクロールを管理するUIScrollView
  • 表を出力するUITableViewと各セルを表すUITableViewCell
などなどです。

そしてiOSの画面は、このUIViewの積み重ねで出来ています。
例えば、でかいビューがあって、その上にUIImageViewとUILabelが乗っかってて、
さらに、UIScrollViewがラップしてる、などいろんなViewが積み重なっています。
どういうViewがどうやって積み重なっているかの順序関係で時々はまったりするので、抑えておくことをお勧めします。

・UINavigationControllerによる画面遷移
iOSの画面遷移にはいろんな方法がありますが、一般的なのは、テーブルビューから横にヒュイッと移動するのが割と多いと思います。
(最近はもっと洗練された奴がでてきてたりしますが)
これは、UINavigationControllerというクラスが、管理しています。
pushViewControllerというメソッドを呼び出すと次の画面に移ります。
前の画面に戻るのは popViewControllerというメソッドです。
自分で呼び出すこともできますし、NavigationControllerも勝手にボタンを作ってくれます。



UITableViewとUINavigationController
それでは、前回と同じようにXcodeを起動して、プロジェクトを作る所から始めましょう。
今回はひな形に Master-Detail Applicationを使います。その際に、Device familyはiphoneを、Use StoryboardUse Automatic Reference Countingのチェックボックスは外しておいてください

多分、起動するとこんな画面になると思います。


Detailをクリックすると、こんな画面に行って、


Masterを押すと元に戻ると思います。

まずは、新しいViewを追加しましょう
New file NewFile として、新しいファイルを追加します。
クラステンプレートはUIViewController subclass
Target for iPadのチェックは外す
With XIB for user Interfaceのチェックはオン
としてファイルを作ります。ファイル名はなんでもいいですが、ImageViewControllerとしました。

次にMasterViewController.hを修正します。

まずクラス変数として、ImageViewController型の変数を一つ追加します。
そしてメソッドを定義する所に
@property (strong, nontoxic) ImageViewController *imgViewController;
と入れます。これは、自動的にアクセサを作ってくれるおまじないです。

では、次は.mファイルへうつります
@implementationの下に
@synthesize imgViewController;
と入れておいてください。

まずは、
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
とします。これは、テーブルが何個セルがあるかを返すメソッドです。
テンプレートファイルでは初期状態で1個あり、今から自分たちで追加するので2を返します。

次に、メソッド
(UITableViewCell *)tableView:tableView cellForRowAtIndexPath:indexPath
を修正します。if文以降を次のように変更し、次のようにします。
このメソッドは、各テーブルの一つ一つに何を表示するかを与えて返します。テーブルのセルはUITableViewCell型で、自分でいろいろといじれますが、今回は割愛します。
indexPath にどのグループの何番目のセルかという情報が入っています。
今回は0番目の列にはテンプレートのものを
1番目の列には、自分の作るものを書きました。


そしてメソッド
- tableView:(UITableView *)tableView didSelectRowAtIndexPath:indexPath

は次のように変更します。

このメソッドは、indexPathのセルが選択されたときに、何をするかを書きます。
今回は選択したら、次の画面へ遷移するようにします。
if (!self.imgViewController) {
    self.imgViewController = [[[ImageViewController alloc] initWithNibName:@"ImageViewController" bundle:nil] autorelease];
   }

の部分は、メンバ変数 imgViewControllerの初期化を行っています。
initWithNibNameは,@"".xib の仕組みを使って初期化しますよというメソッドです。

[self.navigationController pushViewController:self.imgViewController animated:YES];
が画面遷移を行うメソッドです。

以上を書いて実行すると、次のような画面になると思います。






UIImageViewとUIScrollView
では、このViewをいじって行きましょう.今回はUIImageViewを用いた画像の表示を行います。

まずは、プロジェクトに画像を追加します。
画像をプロジェクトにドラッグして行くと勝手に追加してくれます。簡単ですね。



ではまず、Viewの見た目と接続を行います
  1. .xibファイルの右タブのアイテムから、UIImageViewと書かれたアイテムをドラッグして、Viewにドロップします
  2. ImageViewController.hに移り,クラスImageViewControllerにメンバ変数として IBOutlet UIImageView型の変数を追加します。名前は何でも構いませんが、今回はimgViewとしました。
  3. もう一回.xibファイルに戻って,imgViewと配置したimageViewとを接続します.

これは前回とほぼ同じ内容なので簡単ですね。

では次に,コードを書いて画像を表示させましょう。
imageViewController.mに移ります。
そして,viewDidLoadメソッドを以下の行を追加します。

imgView.image = [UIImage imageNamed:@"LENNA.bmp"];

viewDidLoadは画面が呼び出されたときに呼ばれるメソッドで、
ここで画面に貼る画像を呼び出して貼付けます。
UIImageとは、画像を簡単に扱ってくれるクラスです。残念なことに低レベルな処理(画素単位の修正)などは出来ませんが。
imageNamedメソッドは@""のファイル名のファイルを読み込んでUIImageで初期化してくれるメソッドです.
読み込んだUIImageをimgViewの持つ画像のプロパティ imgView.image にセットすることでimageViewへの画像の読み込みが完了します。
めっちゃ簡単ですね。

実行するとこんな感じになると思います。



画像のアスペクト比がおかしいことになったりするので、そういった場合は
貼付ける前に
imgView.contentMode = UIViewContentModeScaleAspectFit;
とすると、画像のアスペクト比を保ったまま、ちょうど目一杯に入るように画像を調整してくれます。





ではiPhoneらしいUIの一つ、スクロールに挑みましょう
まず、.xibファイルより先ほど追加したUIImageViewを削除します。
次にScrollViewを引っ張って来て、Viewに貼付けます。


ImageViewController.hを変更しましょう
@interface ImageViewController : UIViewController
<
UIScrollViewDelegate
>
{
 IBOutlet UIImageView *imgView;
 IBOutlet UIScrollView *scrollView;
}

@end

まず,UIViewControllerの後に 
< UIScrollViewDelegate > 
を追加して、メンバ変数に UIScrollView型を追加します。

そして再び.xibファイルに戻って、変数と配置したパーツとを繋ぎます。

ではImageViewController.mを変更して行きます。
まず, viewDidLoadメソッドを次のように変更します。


今回imgViewはUIパーツの方に入っていないので、自分で初期化する必要があります。
UIImageView initWithImageメソッドは,UIImageを用いて初期化するメソッドです.

その下のscrollViewに関する設定は、
まずcontentSizeで表示するコンテンツの大きさを設定しました。今回のコンテンツは画像の大きさとしました。
そのあと,maximumZoomScaleとminimumZoomScaleでズームの範囲を設定しています.
delegateについては後ほど述べます.
そして addSubViewでスクロールビューの下にサブビューを貼付けます.


そして,次のメソッドを追加します。
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
 return imgView;
}

これは,スクロールビューでズームが行われたときに,どのビューを拡大縮小しますかというのを返すメソッドです.
今回は,imgViewを返します.
本来,このメソッドhaUIScrollViewに定義されていて,UIScrollViewが実装するはずです.
しかし,objective-Cでは定義だけして,あとはサブクラスとか利用するクラスが代わりに実装して!という宣言の仕方が
可能で,その仕組みをdelegate(デリゲート)と呼びます.

どれをデリゲートしないといけないかは,ドキュメントなどに書いてあります.


実行すると,こんな感じでスクロールできると思います.
シミュレータ上での二本指シミュレーションはoptionキーを押しながらドラッグです。




2012年2月6日月曜日

Hello World


UIのパーツを触ってみる

今日のメニュー
  1. UIの配置
  2. アクションとその反映
  3. 他のUIパーツ

プログラミングのHello Worldまでが今日の目標です。


UIの配置

iOSにはUIのパーツが何種類かあり、どこに何を配置しているかという情報は拡張子が.xibのファイルに保存されています。
左のカラムからそのファイルをクリックすると、次のような画面になると思います。


んで、右上のViewと書かれたセグメントの右のボタンをクリックすると、右からにゅいっとビューが出てきます。

そこからLabelをドラッグして貼付けます。
次はその下の項目のRound Rect Buttonも同じようにドラッグします。



⌘+Sで一回保存しておきましょう。

これでUIの配置はひとまず終了です。


アクションとその反映
次は配置したパーツをソースコードに反映させます。
MPAViewController.hを左カラムから選びます。
んで、次のようにコードを書いてください。

※一部は多分自動的に書いてあります。

では簡単に仕組みを説明して行きます。
iOSはObjective-Cという言語で動いています。そしてObjective-CはCやC++をオブジェクト指向に拡張したものという風になっています。
ソースは主に二種類で、
  • クラスやメソッドの宣言をする.hファイル
  • メソッドの中身を書く.mファイル
です。

.hファイルを一行ずつ解説しましょう。

一行目:
@interface MPAViewController : UIViewController

@interfaceはクラスを宣言するときの最初の言葉です。続いて、MPAViewControllerがクラス名、
そのあとのUIViewControllerは親クラスです。
親クラスとはなんぞやとか、UIViewControllerとは何ぞやとかを説明しだすとキリがないので、
今から自分たちが作って行くものは、基本的にiOS SDKに最初から入っている機能を拡張して行くものだと考えてください。

二行目:
IBOutlet UILabel *myLabel;

ここでは、クラス変数を定義します。UILabelがクラス名、*myLabelが変数名です。
IBOutletは、今のところあまり気にしないでください。
今回はUILabel型の myLabelというラベルにhello worldを表示させます。先ほど作ったラベルと
アクションの接続はその後行います。


五行目:
-(IBAction)myPushButton:(id)sender;

この行はメソッドの定義を行っています。
-(IBAction)はメソッドの戻り値の型
myPushButton:はメソッド名
(id)sender; 引数です。

id型はC言語におけるvoid型のような物で、どのようなオブジェクトにも型を姿を変えます。
使うときは適宜キャストする必要があります。
今回、このメソッドはボタンを押されたときに呼び出されるように次で接続を行います。


UIパーツとメソッド、変数の接続

さきほど作ったxibファイルと.hファイルを接続しましょう。
再び左カラムより.xibファイルを選択します。
左から二つ目のカラムにPlaceholdersという欄があると思います。その欄のFile's Ownerを左クリックすると
下のような画面が出てきます。


myLabelの右側の丸い部分をにゅいっとドラッグして、Labelまで持ってきて離す。すると配置したLabelと.hファイルで定義したmyLabelが接続されます。


次にmyPushButtonも同様に、今度は配置したボタンまでドラッグします。離すといくつかの選択肢が出てくると思うので、
ButtonTouchDownを選択します。

これで接続は終了です。多分下の画像みたいになってると思います。
保存するのをお忘れなきよう。



メソッドの実装
それではボタンをクリックしてhello worldと表示するソースを実装しましょう。

MPAViewController.mを開きます。
そして次のソースコードを追加します。

これは、自分で定義したmyLabelというインスタンスに、 Hello Worldという文字列をセットしなさい
というメソッドを呼び出しています。
Objective-Cのメソッド呼び出しは
[インスタンス名 メソッド名:引数, ….];

で行います。見た目が気持ち悪いですが。

それでは、実行してみましょう。
ボタンを押して、以下のようになれば成功です。


Hello Worldがちゃんと表示されてないですが、
.xibファイルから、ラベルの大きさをいじるときちんと表示できます。



他のUIパーツ

今から書く

XcodeのインストールとiOSシミュレータを動かす

今回の目標:

  • Xcodeのインストール
  • iPhoneシミュレータを動かしてみる
です。


Xcodeのインストール
iPhoneiOS周りの開発は、Appleが作った統合開発環境Xcodeを使って行います。

まずは、XcodeとiOS SDKのインストールから。
https://developer.apple.com/xcode/
に行って、
Download Xcode 4 for Free

の箇所から Sign in へ
Apple ID を入力して(iPhoneとかで使うアレ、なければ登録してください)進みます。

そうすると、
Xcode 4.2 for Snow Leopard

っていうリンクがあると思うので、XcodeをDL.
インストールは指示に従えば特に難しいことはないと思います。
んで、実はこのXcodeの中にiOS SDKは含まれているので、そのまま使うことが出来ます。


iPhoneシミュレータを動かしてみる

では、インストールできたXcodeを使って、iPhoneシミュレータが動くことを確認しましょう。
Xcodeを起動 → File → New → Project
で新しいプロジェクトを作ります。





iOS ApplicationのSingle View Applicationを作ります。
Product Nameとかは適当で構いません。
Device familyはiPhoneで、その下のチェックボックスはそのままで。
Nextへ進んで、適当なフォルダにプロジェクトを作ってひとまず完了。

多分こんな画面が出来ています。

これで基本的なフレームはできているので、この状況でちゃんと動くか確認します。
"⌘+R"で実行します。

こんな画面がでてくればOK