C言語とObjective-C

ファイル構成

クラスファイルはヘッダファイルとソースファイルより構成される。

// SampleCode.h
@interface SampleCode
@end
// SampleCode.m
#import "SampleCode.h"
@implement SampleCode
@end
  • クラスファイルはヘッダファイルとソースファイルより構成される
  • ヘッダファイルには、外部に見せるメソッド(インタフェース)を記述する
  • ソースファイルに、実際のメソッドを実装する

プリプロセッサ

数値や文字列に名前を付けて定数を定義することが出来る

#define TAX_RATE 0.05

ヘッダファイルを読み込む

// 標準ライブラリのみ取り込み
#import <UIKit/UIKit.h>
// カレントディレクトリを検索し、無ければ標準ライブラリを検索する
#import "CustomLib.h"

データ型

自分で定義した列挙型(定数のリスト)の例

// 自身で定義した列挙型をViewState型と定義する
typedef enum {
ViewStateHidden
ViewStateVisible
} ViewState;

メソッド

引数の説明(メッセージキーワード)を記述するのが特徴

-(戻り値の型)メソッド名:(引数1の型)引数1の名前 引数2の説明文:(引数2の型)引数2の名前

インスタンス化の不要なクラスファイルは頭に+をつける

+(戻り値の型)メソッド名:(引数1の型)引数1の名前 引数2の説明文:(引数2の型)引数2の名前

その他

  • selfは自分自身のオブジェクトを意味する
  • initはコンストラクタ(初期化)
  • init内では self = [super init]などとしスーパークラスの初期化を行う。returnではselfを返す

処理の進行を表示する

UIActiveIndicatorView

Storyboad上で「Animating」と「Hides When Stopped」にチェックを入れておくとよい

[_indicator startAnimating];

インジケータのアニメーションが開始される

[_indicator stopAnimating];

インジケータのアニメーションが停止される。Storyboad上で「Hides When Stopped」オプションにチェックを入れている場合は、インジケータが非表示となる

UILabel

##文字を折り返す
複数行に渡って表示する場合は、UILabelのnumberOfLinesプロパティを変更する。無制限の場合はゼロを設定する

textLabel.numberOfLines = 0;

折り返すときのモードも設定できる

textLabel.lineBreakMode = UILineBreakModeCharacterWrap;

UITableViewの使い方

テーブルデータの再読み込み

データの更新などを行った後、スレッド内で単に

tableView.reloadData()
[(UITableView)tableView reloadData]

とやると落ちてしまう。そこで以下のようなコードを記述することによって、データの再読み込み処理をメインスレッドに戻して処理させるようにする。

dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self.tableView.reloadData()
})
[(UITableView)tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

また、下記のようにUserDefaultのデータをCellにデータを反映させる場合に、

@IBOutlet weak var label: UILabel!

override func viewWillAppear(animated: Bool) {
        let defaults = NSUserDefaults.standardUserDefaults()
        label.text = defaults.valueForKey("label")! as? String
}

viewWillAppear()でreloadData()しただけでは、うまくテーブルが更新されないので、

override func viewWillAppear(animated: Bool) {
        let defaults = NSUserDefaults.standardUserDefaults()
        label.text = defaults.valueForKey("label")! as? String
        tableView.reloadData()
}

viewDidAppear()でもreloadData()する必要がある。

    override func viewDidAppear(animated: Bool) {
        tableView.reloadData()
    }

セルのハイライトを解除する

UITableViewControllerを使用しない場合、View遷移時のセルハイライトの解除を自分で記述する必要がある

tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow, animated: true)
[(UITableView)tableView deselectRowAtIndexPath:[(UITableView)tableView indexPathForSelectedRow] animated:YES];

セル選択時にハイライト表示しない

セルを選択した場合もハイライト表示としない

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
        cell.selectionStyle = UITableViewCellSelectionStyle.None
        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	// セル選択時のスタイルを無表示スタイルに設定する
	cell.selectionStyle = UITableViewCellSelectionStyleNone;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	// セル選択時も非選択状態とする
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

セル選択不可

UITableView内全てのセルを選択不可にする

tableView.allowsSelection = false
[(UITableView)tableview setAllowsSelection:NO];

ヘッダビューを追加する

UITableView上部にヘッダビューを追加する

tableView.tableHeaderView = view
(UITableView)tableView.tableHeaderView = (UIView)view;

追加するViewは、ViewcontrollerのサブViewにしない。
文字数に合わせたセルの高さを設定する

セルの高さを設定する

  • テーブル内のUILabelの文字数に合わせてセルの高さを指定する
    - (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath  
    {
    	// セルの取得
    	UITableViewCell *cell = [self tableView:_tableView cellForRowAtIndexPath:indexPath];  
    	// 最大サイズ
    	CGSize bounds = CGSizeMake(_tableView.frame.size.width, _tableView.frame.size.height);
    	// ラベルサイズ
    	CGSize size = [cell.textLabel.text sizeWithFont: cell.textLabel.font
    	constrainedToSize: bounds
    	lineBreakMode: UILineBreakModeCharacterWrap];
    	return size.height;
    }
    

  • セルの高さを自動設定する

    // とりあえず高さを指定
    tableView.estimatedRowHeight = 100
    // 高さを自動指定
    tableView.rowHeight = UITableViewAutomaticDimension
    

プロトタイプセルに含まれる子ビューを指定する

Storyboard上で設定したPrototypeCellに含まれるUIImageViewやUILabelをコード上から指定する場合、それぞれのViewにラベルを付与しておくことで、ラベル番号を指定してViewを取得することが可能である。

Prototype Cells

プロトタイプセル上にUIImageViewを作成し、Tag=1を指定している。

Prototype Cells Property

上記の例で、UIImageViewにSwift上からアクセスする場合は、

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
        let imageView: UIImageView = cell.viewWithTag(1) as! UIImageView
        return cell
    }

とすれば良い。

UIView

アクションシート

デリゲートの記述を行う。

@interface MyViewController : UIViewController<UIActionSheetDelegate>{

アクションシートの初期化と貼付けを行う。初期化の際にデリゲートの宣言をする。

// 初期化(+デリゲートの設定)
UIActionSheet *aSheet = [[UIActionSheet alloc]
initWithTitle:@"選択項目"
delegate:self
cancelButtonTitle:@"キャンセル"
destructiveButtonTitle:nil
otherButtonTitles:@"ボタン1", @"ボタン2", @"ボタン3",nil];
// アクションシートのスタイルを指定
[aSheet setActionSheetStyle:UIActionSheetStyleDefault];
// ビューに貼付ける
[aSheet showInView:[self view]];
// メモリの解放
[aSheet release];

デリゲートメソッド内で処理を記述する

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
}

## NavigationController
Xcodeのプロジェクト設定でNavigationControllerを選択した場合、RootViewController.xibとMainWindow.xibが作成される。MainWindowではUINavigationControllerが読み込まれており、このUINavigationControllerの中に上部のNavigation Barと下部のRootViewControllerがある。下部のRootViewControllerは、UITableViewControllerを継承して作成されており、テーブル状のメニュー画面を作成することが出来る。

この画面をタッチすると別のサブウインドウに移動できるようにするためには、


// NIBファイルを指定してビューコントローラを定義する
DetailViewController *dvc = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
// メニューを押すと上記ビューコントローラを表示する
[self.navigationController pushViewController:dvc animated:YES];
[dvc release];

とする。

Objective-Cのデザインパターン

クラスクラスタ

あるスーパークラスから派生したサブクラスが多数存在する場合、それぞれのサブクラスをダイレクトに利用しようとするとクラスの数が膨大になり利用しにくい。そこでサブクラスを非公開とし、サブクラスへのアクセスは公開されたスーパークラスへのアクセスによって実現する。これがクラスクラスタである。サブクラスはスーパークラスより生成されるが非公開であるため、外からはスーパークラスのインスタンスとして見える。

クラスクラスタに属するサブクラス

クラスクラスタは簡明さと拡張性のトレードオフである。拡張を必要とする場合はサブクラスを別途作成する必要がある。サブクラスを定義する際は以下のように作成する。

  • イニシャライザを全てオーバライドする
  • プリミティブメソッド(最低限実装しなければならないメソッド)をオーバライドする

他の派生メソッドはオーバライドしなくても使用することができるが、挙動を変更する場合はオーバライドして実装する。

ファクトリメソッド

ファクトリーメソッドとはクラスメソッド、つまりインスタンス化することなく使用することのできるメソッドである。

アウトレット

(ViewControllerなどの)オブジェクトのプロパティ(パーツ)のうち、(Storyboardなど)ほかのオブジェクトを参照するもの。Storyboadを読み込む際に接続が確立しなおされる。メモリ管理の観点より、他の設定項目と同様にクラスに含めるべき項目なのかどうか検討が必要である。

@propety(weak, nonatmic) IBOutlet UILabel *label;

オブジェクトの割り当てと初期化

オブジェクトは、alloc()メソッドにより仮想メモリ領域からオブジェクトを格納するために必要なメモリ量の割り当てを受け、このあとinit()メソッドによりインスタンス変数を初期化する。シングルトンインスタンスの場合、新たなインスタンスの生成を要求されても、既存のインスタンスを返すような初期化処理を実装しておく必要がある。返り値がnilの場合、初期化に失敗したことを示す。

イメージの取得と保存

イメージを取得する

カメラやアルバムからイメージを取得するにはUIImagePickerControllerを使用する。

// 使用できるか出来ないかの判定は、isSourceTypeAvailableメソッドを使う
if ( [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] ) {
	// UIImageControllerの初期化
	UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
	/* UIImageControllerで取得できるデータは以下の3つ
	 *
	 * SourceTypeCamera             : カメラを起動する
	 * SourceTypePhotoLibrary       : フォトライブラリーを開く
	 * SourceTypeSavedPhotosAlbum   : (カメラがあるiPhoneは)カメラロールから選択する
	 */
	[ipc setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
	// Delegateをセット
	[ipc setDelegate:self];
	// NOを設定するとイメージを取得できない
	[ipc setAllowsEditing:YES];
	// 指定したViewを一番上に表示する
	[self presentModalViewController:ipc animated:YES];
	// 表示してしまった後はメモリから消去してよい
	[ipc release];
}

イメージを表示する

UIImagePickerControllerのイメージを取得した後に呼び出されるデリゲート関数(didFinishPickingMediaWithInfo)に、取得イメージをImageViewに表示する処理を追記する。UIImagePickerControllerをデリゲートする場合は、UIImagePickerControllerDelegateとUINavigationControllerDelegateをヘッダファイルに記述する必要がある。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
	// 選択したイメージをUIImageにセットする
	UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
	// UIImageをImageViewerにセットする
	[imageView setImage:image];
	// [picker parentViewController]の部分はselfでも良い?
	[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}

イメージを保存する

ImageViewに表示されたイメージをフォトアルバムに保存する。イメージを端末内に保存される前にアプリを終了してしまうと、サムネイルだけが保存されイメージが保存されないので、保存終了後にアラートを表示させるようにする。

UIImage *image = [aImageView image];
if ( image != nil ) {
	// イメージを保存する
	UIImageWriteToSavedPhotosAlbum( image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil );
}

アラート表示部分は以下のように記述する。

// アラートを表示させる部分は、image:didFinishSavingWithError:contextInfo:という書式が必須らしい
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
	// アラートを表示する
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"保存終了" message:@"画像を保存しました" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alert show];
	[alert release];
}

アプリ内部(アプリのホームディレクトリ内)に画像を保存する場合は、以下のように記述する。

UIImage *image = [aImageView image];
if ( image != nil ) {
	// 保存ディレクトリを指定
	NSString* docDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
	// 保存ディレクトリ+ファイル名を指定
	NSString* photoFilePath = [NSString stringWithFormat:@"%@/photo.jpg", docDir];
	// 圧縮率を指定しJPEGファイルで保存する
	[UIImageJPEGRepresentation(image, 0.7f) writeToFile:photoFilePath atomically:YES];
}

保存ディレクトリの指定には、文字列追加を行うことのできるstringByAppendingPathComponentメソッドを使用する。またファイル名の指定には、フォーマットを指定した文字列記述のできるstringWithFormatを使用する。最後にUIImageJPEGRepresentatio関数によって画像データをNSデータ型に変換したあと指定した保存先に保存を行う。

デリゲートとは

デリゲートとは、本来他のクラスのメソッドを用いて処理すべき内容を自分のクラス内で処理できるように、他のクラスのメソッドを自分のクラス内で呼び出して利用することを言う。例えば、ボタンを押せば録音を行うというプログラムで、録音が終了した際にひと手間処理を入れたい場合、本来であれば録音を行うクラスの録音終了メソッド内で処理が行われるが、デリゲートを宣言することでViewController上でこの録音終了処理を行うことが出来る。つまり、本来他のクラスがやるべき仕事を委譲されて自分自身で処理できるようになる。

まず、

@interface MyViewController : UIViewController <AVAudioRecorderDelegate>
{

と書くとこのViewControllerでAVAudioRecorderを代行すると宣言したことになる。次に処理を代行する部分をプログラムに明示する。例えば、

// 録音を行うクラス
AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:soundURL settings:nil error:&error];
// 各処理を記述
// self=MyViewControllerにrecorderのデリゲートをセットする
[recorder setDelegate:self];

と書くと、recorderが行う処理の一部をMyViewControllerが代行することが出来る。代行する部分の処理は、

- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
}

などの部分に記述する。各クラスはどのメソッドをデリゲートで使って良いか定義している。デリゲートしたクラスのうち使用出来るメソッドのみ処理を代行できる。

画面の向きを固定する

info.plistを編集する

info.plistにinitial interface orientationの項目を追加し、起動時の画面の向きを設定する。

  • Portrait (bottom home button)
  • Portrait (top home button)
  • Landscape (left home button)
  • Landscape (right home button)

ステータスバーを表示させない場合は、status bar is initialy hiddenの項目も追加する。

IBでUIの構築を行う

Viewの右上の矢印をクリックするとViewの向きを変えることが出来る。横向きの画面を作成する場合は、Viewの向きを変えてから各パーツを貼付ける。ステータスバーを表示させない場合はViewのAttributesを開き、status barをNoneとする。ついでにViewの画面サイズも480×320に変更する。

画面の向きを固定する

UIViewControllerで定義されているshouldAutorotateToInterfaceOrientationというメソッド(コメントアウトされた状態で記述されている)を使い回転を許可する向きを定めることが出来る。固定したい向きを記述することでその向きのときだけ回転する、すなわちその向きに画面が固定される。

// 現在の向きがあらかじめ規定した向きと合致するとYESを返す
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// YESを返すときのみ回転を許可する
	return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}

InterfaceBuilderとイベント処理

IBで作成したオブジェクトを参照する

プログラムは、IBOutletを宣言したインスタンス変数(アウトレット)を通して、IBで作成したオブジェクトを参照することができる。プログラム側のヘッダファイルで以下のようにアウトレットを宣言する。

#import <UIKit/UIKit.h>
 
@interface DigitalClock1ViewController : UIViewController {
	// インスタンス変数の宣言
	UILabel *label;
}
@property(nonatomic, retain) IBOutlet UILabel *label;
@end

@propertyで記述されたアクセサに関しては、@implementation部に以下の記述が必要。

@synthesize label;

アウトレットがIB上のどのパーツを参照するのかはInterfaceBuilderで規定する。File’s Ownerを右クリックして先ほど宣言したアウトレットを選択し、これをView上の任意のパーツまでドラッグすることで、アウトレットとパーツとの参照関係が規定される。

オブジェクトに対するアクションを受け取る

ボタンが押された等のアクションをプログラム側で受け取る場合は、アクションを受け取るメソッドにIBActionを宣言する。プログラム側のヘッダファイルで以下のようにIBActionメソッドを宣言する。

#import <UIKit/UIKit.h>
 
@interface DigitalClock1ViewController : UIViewController {
}
 
- (IBAction) doKeyDown: (id) sender;
 
@end

また、@implementationにてメソッドを実装する。

- (IBAction) doKeyDown: (id) sender{
	// sender tagでアクションの発生したオブジェクトを特定することが可能
	NSLog:@"%d", [sender tag];
}

IBActionがIB上のどのパーツのアクションを受け取るのかはInterfaceBuilderで規定する。File’s Ownerを右クリックして先ほど宣言したメソッドを選択し、これをView上の任意のパーツまでドラッグすることで、IBActuionとパーツとの参照関係が規定される。