Sections(5)~サンプルコードとの差異

2010. 11. 28
初版の本書とApress社のサイト内のサンプルコードとの差異を示します。

948

本書では検索バーとテーブルビューは独立して配置されています。

949

サンプルコードではテーブルビューの上にサブビューとして検索バーが配置され、起動時には見えないようにしています。

950

右端のインデックスの一番上にある拡大鏡のアイコンをタップすると、ビューが下にずれて検索バーが現れます。

また検索バーがアクティブの時、インデックスが非表示になります。

951

その他、外観に反映されていない細かな違いもあります。


●nibファイルSectionsViewController.xib

前述の通り、本書では検索バーとテーブルビューを別々のビューとして配置しています。

944

952

サンプルコードではテーブルビューのサブビューとして、検索バーをテーブルビューの上端に配置しています。

953

ViewウィンドウでSearch Barを選択し、ドラッグ&ドロップでテーブルビューの上端に移動します。

954

この状態からだとテーブルビューが選択できないので、移動でできた隙間をクリックしてSearch Barの選択を解除し、テーブルビューをクリックして選択、上端を掴んでステータスバーまでビューを広げます。

955

編集が済んだらファイルを保存します。


●カテゴリファイル名

Sections(2)~カテゴリ』で触れましたが、カテゴリファイル名とカテゴリ名が異なります。

カテゴリファイル名:旧)NSDictionary-MutableDeepCopy 新)NSDictionary-DeepMutableCopy
カテゴリ名:旧)MutableDeepCopy 新)DeepMutableCopy

ただカテゴリメソッドのmutableDeepCopyは変わりませんので、コードを修正する必要はありません。


●インスタンス変数の追加

ビューコントローラのヘッダファイルSectionsViewController.hに、検索モード時にインデックスを非表示にするためのブール値isSearchingが追加されています。
(太字が追加・修正した部分)

#import <UIKit/UIKit.h>

@interface SectionsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate> {
    UITableView *table;
    UISearchBar *search;
    NSDictionary *allNames;
    NSMutableDictionary *names;
    NSMutableArray *keys;
    BOOL isSearching;
}

(後略)

956


●resetSearch

本書では、元となる不変辞書allNamesからmutableDeepCopyで可変辞書を生成し、直接namesに代入していますが、サンプルコードでは一旦一時オブジェクトのallNamesCopyに入れてからnamesに代入する形となっています。

一度きりの代入のために手動解放を必要とする一時オブジェクトを介するのは無駄なような気がするのですが、わざわざ変更しているのですから何か意味があるはずです。
(『Googleブックス/Beginning IPhone 3 Development: Exploring the IPhone SDK』ではこの辺りから無料では読めないので、真意は分かりません)

それと、インデックス用のキー配列にはaddObject:UITableViewIndexSearchという拡大鏡のアイコンが追加されています。

これは検索バーがテーブルビューのサブビュー扱いになり、起動時は検索バーが表示されていないため、検索バーを表示(テーブルビューを下方にずらす)際に使用されます。
(太字が追加・修正した部分)

- (void)resetSearch {
    NSMutableDictionary *allNamesCopy = [self.allNames mutableDeepCopy];
    self.names = allNamesCopy;
    [allNamesCopy release];


    NSMutableArray *keyArray = [[NSMutableArray alloc] init];
    [keyArray addObject:UITableViewIndexSearch];
    [keyArray addObjectsFromArray:[[self.allNames allKeys] sortedArrayUsingSelector:@selector(compare:)]];
    self.keys = keyArray;
    [keyArray release];
}

957


・Section Index Icons

UIKIT_EXTERN NSString *const UITableViewIndexSearch;

テーブルビューにセクションインデックスのアイコンを表示させます。

UITableIndexSearch
sectionIndexTitlesForTableView:で返される文字列の配列の中に、この文字定数が含まれていた場合、セクションインデックスは拡大鏡のアイコンと一致するインデックスの位置を表示します。
この位置は、一般的にインデックスの最初のタイトルの場所になります。


●viewDidLoad

サンプルコードではresetSearchで辞書とキー配列の初期化後に、reloadDataを追加してテーブルビューを再読み込みしています。

本書のreloadDataをしていない状態でも問題なく表示されてはいますが、テーブルビュー表示用のデータを変更(初期化)しているのですから、本来はするべき処理なのかもしれません。

それと検索バーの2つの属性(autocapitalizationTypeとautocorrectionType)は、本書ではInterface Builderで設定できないためにコードで記述するとありますが、現行のInterface Builderは設定できるようになっています。

958

Search Barを選択しInspectorウィンドウのAttributesタブを開き、『Search Bar』の『Text Input Traits』欄のCapitalizeとCorrectionで設定することができます。

『Capitalize』は『None』のまま、『Correction』は『No』に設定すると、コード側の2行は不要になります。

最後にsetContentOffset:animated:でテーブルビューの表示をずらしています。

これはテーブルビューのサブビューとした検索バーを起動時には見えないようにするためで、検索バーの高さの分をオフセットに設定しています。
(太字が追加・修正した部分)

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
    self.allNames = dict;
    [dict release];
    [self resetSearch];
    [table reloadData];
    [table setContentOffset:CGPointMake(0.0, 44.0) animated:NO];

}

959


setContentOffset:animated:

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated

レシーバの原点に対応する、コンテントビューの原点からのオフセットを設定します。

contentOffset:コンテントビューの原点からのオフセットを点で表します。

animated:YESの場合、新しいオフセットへ一定速度で移行するアニメーションを行い、NOの場合はすぐに移行します。


●numberOfSectionsInTableView:

本書では検索の結果、セクション数が0になることを想定し、0以下の場合は1になるような条件が追加されましたが、サンプルコードでは元の単純にキー配列の要素数を返す文に戻っています。

リファレンスにはnumberOfSectionsInTableView:に0を渡した場合についての説明がないので、Appleとしてこの記述で問題が無いのかどうかは分かりません。

挙動としてはどちらも同じで、セクションの無い空のプレーンなテーブルビューとなります。
(太字が修正した部分)

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [keys count];
}

960


●tableView:titleForHeaderInSection:

セクション数が0の場合、本書ではセクションのタイトルを空の文字列『@""』を返していましたが、サンプルコードではnilを返すように変更されています。

また検索バーにセクションは不要なので、引数sectionが拡大鏡アイコンのUITableViewIndexSearchの場合も同様にnilを返すコードが追加されています。

この除外処理を追加しない場合、下図のように表示されます。

961

検索バーはサブビュー扱いですので、『{search}』というタイトルのセクションは検索バーの下に表示されます。
(太字が追加・修正した部分)

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if ([keys count] == 0)
        return nil;

    NSString *key = [keys objectAtIndex:section];

    if (key == UITableViewIndexSearch)
        return nil;


    return key;
}

962


●sectionIndexTitlesForTableView:

本書では単純にキー配列を返していましたが、サンプルコードでは追加したブール値isSearchingを判別してYESの場合はnilを返し、インデックスが表示されないようにしています。

isSearchingは、tableView:willSelectRowAtIndexPath(テーブルビューの行選択時)とsearchBarCancelButtonClicked:(検索バーのキャンセルボタンがタップされた時)でNO、searchBarTextDidBeginEditing:(検索バーのテキストの編集が開始された時)でYESと設定されます。

つまり、検索中はインデックスを非表示に、検索が終了したらインデックスを表示することになります。

本書の場合は検索語句の入力に対して随時インデックスが更新されていましたが、サンプルコードではインデックスが更新される様が見れないのは少し寂しいです。

(新版の本書の内容は知らないので)想像ですが、検索バーをテーブルビューのサブビューにした結果、インデックスが検索バーの上にも表示されるようになり、キャンセルボタンをタップする際の障害になるため、それを回避策として検索中のインデックスは非表示にしたものと思われます。

下図にインデックスを表示させたままの状態を示します。

963

元の場合でもキーボードで下半分は見えませんし、無くても支障はそれほど無いと判断したのでしょう。
(太字が追加した部分)

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (isSearching)
        return nil;


    return keys;
}

964


●tableView:willSelectRowAtIndexPath:

サンプルコードでは、検索語句のクリア(textプロパティに空文字列の設定)と検索状態を示すisSearching値の設定(NOを設定しているのでインデックスを表示する)、そしてテーブルビューの再読み込みが追加されています。

検索語句のクリアですが、再検索時に消す手間を考えて追加したものと思われますが、ユーザによっては『どの語句で検索したか』を示す意味で残した方が良い場合も考えられます。

その場合、tableView:willSelectRowAtIndexPath:ではなく、検索バーのテキストフィールドへの入力開始を示すsearchBarTextDidBeginEditing:でテキストをクリアした方が良いかもしれません。

テーブルビューの再読み込みは、無い状態でも正常に動作しているように見えるものの、検索でデータを組み替えた後なので、本来すべきものとして追加されていると思われます。
(太字が追加した部分)

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [search resignFirstResponder];
    search.text = @"";
    isSearching = NO;
    [tableView reloadData];

    return indexPath;
}

965


●tableView:sectionForSectionIndexTitle:atIndex:

本書には無くサンプルコードで追加されているメソッドで、テーブルビューのデリゲートメソッド欄に書かれていますが、実際にはデータソースメソッドです

tableView:sectionForSectionIndexTitle:atIndex:メソッドは、指定したタイトルやセクションインデックスのタイトルの、データソースにおけるセクションのインデックスを返します。

ここではインデックスで拡大鏡のアイコン(UITableViewIndexSearch)を指定した場合、つまり検索を実行した時、setContentOffset:animated:でテーブルビューのオフセットを0に設定して検索バーを表示するという処理になります。

戻り値は、拡大鏡アイコンを指定した場合は(検索バーにセクションは無いので)NSNotDoundを返し、それ以外の場合は指定したインデックスをそのまま返します。

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    NSString *key = [keys objectAtIndex:index];
    if (key == UITableViewIndexSearch) {
        [tableView setContentOffset:CGPointZero animated:NO];
        return NSNotFound;
    }
    else return index;
}

966


tableView:sectionForSectionIndexTitle:atIndex:

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index

指定したタイトルとセクションタイトルのインデックスを持つ、セクションのインデックスを返すよう、データソースに尋ねます。

戻り値は、セクションを識別するインデックス番号になります。

このメソッドは、インデックス番号とセクションインデックスリストに登録されているタイトルを渡し、参照しているセクションのインデックスを返します。

文中の2つのインデックス番号を明確にすると、

・sectionIndexTitleForTableView:によって返される配列内のセクションインデックスのタイトルへのインデックス
・テーブルビューのセクションへのインデックス

となり、前者が渡され後者が返されます。

このメソッドは、セクションインデックスリストを伴うテーブルビューに対して、つまりプレーンスタイル(UITableViewStylePlain)で生成されたテーブルビューでのみ実装できます。

sectionIndexTitleForTableView:で返されるセクションタイトルの配列は、テーブルビュー内の実際のセクション数よりも項目が少ない場合があるので注意してください。

tableView:この情報を要求するテーブルビューオブジェクトを指定します。

title:テーブルビューのセクションインデックス内に表示するタイトルを指定します。

index:sectionIndexTitleForTableView:で返される配列内のセクションタイトルを識別するインデックス番号を指定します。


●searchBarTextDidBeginEditing:

searchBarTextDidBeginEditing:もサンプルコードで追加されているメソッドで、検索バーのテキストを編集し始めた時に呼び出されます。

ここでは検索状態を示すisSearching値の設定(YESを設定しているのでインデックスを非表示する)と、テーブルの再読み込みを行っています。

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    isSearching = YES;
    [table reloadData];
}

967


searchBarTextDidBeginEditing:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar

ユーザが検索テキストの編集を開始したことをデリゲートに伝えます。

searchBar:編集を開始する検索バーを指定します。


●searchBarCancelButtonClicked:

サンプルコードでは、検索状態を示すisSearching値の設定(NOを設定しているのでインデックスを表示する)が追加されています。
(太字が追加した部分)

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    isSearching = NO;
    search.text = @"";
    [self resetSearch];
    [table reloadData];
    [searchBar resignFirstResponder];
}

968


●mutableDeepCopy

上記までの状態で実行すると、行やインデックスを選択した際に『unrecognized selector sent to instance(認識されていないセレクタがインスタンスに送信されている)』という例外エラーが発生して強制終了してしまいます。

原因はmutableDeppCopyで、最初に入れ物となる可変辞書retを生成・初期化する際にdictionaryWithCapacity:を使用しているからで、サンプルコードのようにinitWithCapacity:で初期化すると何事もなく動作します。

この差異は『Sections(2)~カテゴリ』の最後の項『可変辞書retの初期化について』で触れていた点なのですが、原書を読んでも理由がよく分からない上に、公式リファレンスでも両メソッドの説明に差異が無いので不可解です。

違いと言えば、コンビニエンスメソッドのdictionaryWithCapacity:の場合は自動解放プールに入るため、意図しない所で解放されている可能性があることで、実際手動解放の必要があるinitWithCapacity:を使用していながら解放を行っていませんから、(メモリリークも発生していますし)その辺に原因があるのかもしれません。
(太字が追加・修正した部分)

- (NSMutableDictionary *)mutableDeepCopy {
    NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
    NSArray *keys = [self allKeys];

    for (id key in keys) {
        id oneValue = [self valueForKey:key];
        id oneCopy = nil;

        if ([oneValue respondsToSelector:@selector(mutableDeepCopy)])
            oneCopy = [oneValue mutableDeepCopy];
        else if ([oneValue respondsToSelector:@selector(mutableCopy)])
            oneCopy = [oneValue mutableCopy];

        if (oneCopy == nil)
            oneCopy = [oneValue copy];

        [ret setValue:oneCopy forKey:key];
    }
    return ret;
}

969



参考文献

UISearchBarDelegate Protocol Reference

UIScrollView Class Reference

UITableViewDataSource Protocol Reference

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

UIScrollViewクラス

2010. 11. 27
●概要

UIScrollViewクラスは、アプリケーションのウィンドウのサイズよりコンテンツが大きい場合、表示のサポートを提供します。

これにより、ユーザはコンテンツをスワイプのジェスチャでスクロールすることができ、ピンチングのジェスチャでコンテンツの一部を拡大/縮小することができます。

UIScrollViewは、UITableViewUITextViewを含むいくつかのUIKitクラスのスーパークラスです。

UIScrollViewオブジェクト(あるいは単純にスクロールビュー)の中心となる概念は、基本的にコンテントビューを調整するビューということです。

フレームにコンテンツをクリップする際、一般的には(必ずしもそうとは限りませんが)アプリケーションのメインウィンドウに一致させます。

スクロールビューは指の動きに追従して原点を調整します。

ビューはスクロールビューでの描画を介して新しい原点を元にその一部を表示し、コンテントビューのオフセットを固定します。

スクロールビュー自身は、垂直/水平方向のスクロールを示すインジケータ以外は描画しません。

スクロールビューはスクロールが停止することを知るため、コンテントビューのサイズを知っている必要があり、デフォルトではコンテントの矩形を超えるスクロールをした時に跳ね戻ります。

オブジェクトは、スクロールビュー内に表示するコンテンツの描画を管理し、コンテンツのサブビューがスクリーンのサイズを超えるビューにならないようにする必要があります。

スクロールビュー内でユーザがスクロールした際、オブジェクトは必要に応じてサブビューの追加や削除をする必要があります。

スクロールビューはスクロールバーを持たないため、タッチの信号がスクロールを意図しているかを感知し、それに対してコンテンツ内のサブビューを追従させる必要があります。

この決定を行うには、タイマーを開始させるタッチダウンイベントを一時的に停止し、タイマーが起動する前に指のタッチがどのような操作かを観察します。

タイマーが大きな位置の変更が無く起動した場合、スクロールビューはコンテントビュー内のサブビューがタッチされたとしてイベントの追跡を送信します。

ユーザがタイマーが経過する前に十分に指をドラッグした場合、スクロールビューはサブビュー内の追跡をキャンセルし、スクロールを実行します。

サブクラスは(スクロールビューによって呼び出される)touchesShouldBegin:withEvent:inContentView:pagingEnabledtouchesShouldCancelInContentView:メソッドをオーバーライドすることができ、スクロールビューをどのようにスクロールするかというジェスチャの処理に効果を与えられます。

またスクロールビューは、コンテンツのパンやズームも処理します。

ユーザがピンチ・イン/アウトのジェスチャを行うと、スクロールビューはコンテンツのスケールとオフセットを調整します。

ジェスチャが終了した時、オブジェクトが管理しているコンテントビューは、必要に応じてコンテンツのサブビューを更新する必要があります。
(指が降ろされているままでもジェスチャが終了することに注意してください)

ジェスチャが進行中の間、スクロールビューはサブビューへの追跡の呼び出しを送信しません。

UIScrollViewクラスはデリゲートを持つことができ、それにはUIScrollViewDelegateプロトコルを採用する必要があります。

ズームやパンを行うには、デリゲートはviewForZoomingInScrollView:scrollViewDidEndZooming:withView:atScale:両方を追加で実装する必要があり、ズームのスケールの最大値(maximumZoomScale)と最小値(minimumZoomScale)は異なっている必要があります。

重要:UIScrollViewオブジェクト内にUIWebViewUITableViewオブジェクトを埋め込む必要はありません。
これを行った場合、タッチイベントが2つのオブジェクトに対応することができず、適切ではない処理が行われた結果、予期しない動作が発生することになります。



●タスク

●コンテンツの管理と表示

– setContentOffset:animated:
  contentOffset    property
  contentSize    property
  contentInset    property

●スクロールの管理

  scrollEnabled    property
  directionalLockEnabled    property
  scrollsToTop    property
– scrollRectToVisible:animated:
  pagingEnabled    property
  bounces    property
  alwaysBounceVertical    property
  alwaysBounceHorizontal    property
– touchesShouldBegin:withEvent:inContentView:
– touchesShouldCancelInContentView:
  canCancelContentTouches    property
  delaysContentTouches    property
  decelerationRate    property
  dragging    property
  tracking    property
  decelerating    property

●スクロールインジケータの管理

  indicatorStyle    property
  scrollIndicatorInsets    property
  showsHorizontalScrollIndicator    property
  showsVerticalScrollIndicator    property
– flashScrollIndicators

●ズームとパン

– zoomToRect:animated:
  zoomScale    property
– setZoomScale:animated:
  maximumZoomScale    property
  minimumZoomScale    property
  zoomBouncing    property
  zooming    property
  bouncesZoom    property

●デリゲートの管理

  delegate    property



●プロパティ



●インスタンスメソッド

setContentOffset:animated:

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated

レシーバの原点に対応する、コンテントビューの原点からのオフセットを設定します。

contentOffset:コンテントビューの原点からのオフセットを点で表します。

animated:YESの場合、新しいオフセットへ一定速度で移行するアニメーションを行い、NOの場合はすぐに移行します。



●定数



参考文献

UIScrollView Class Reference

Sections(4)~検索バーの追加(2)

2010. 11. 24
●ビューコントローラのソースファイルの編集

SectionsViewController.mを開き、

1)カテゴリNSDictionary-MutableDeepCopyのインポート
2)プロパティの実装
3)検索用メソッド2つの実装
4)viewDidLoadメソッドの修正
5)viewDidUnloadとdeallocへのプロパティの追加
6)テーブルビューデータソースメソッドの修正
7)テーブルビューデリゲートメソッドの追加
8)検索バーデリゲートメソッドの追加

以上の追加・修正を行います。
(太字が追加・修正した部分)

#import "SectionsViewController.h"
#import "NSDictionary-MutableDeepCopy.h"

@implementation SectionsViewController

@synthesize table;
@synthesize search;
@synthesize allNames;

@synthesize names;
@synthesize keys;

#pragma mark -
#pragma mark Custom Methods

- (void)resetSearch {
    self.names = [self.allNames mutableDeepCopy];
    NSMutableArray *keyArray = [[NSMutableArray alloc] init];
    [keyArray addObjectsFromArray:[[self.allNames allKeys] sortedArrayUsingSelector:@selector(compare:)]];
    self.keys = keyArray;
    [keyArray release];
}

- (void)handleSearchForTerm:(NSString *)searchTerm {
    NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
    [self resetSearch];
    for (NSString *key in self.keys) {
        NSMutableArray *array = [names valueForKey:key];
        NSMutableArray *toRemove = [[NSMutableArray alloc] init];
        for (NSString *name in array) {
            if ([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location == NSNotFound)
                [toRemove addObject:name];
        }
        if ([array count] == [toRemove count])
            [sectionsToRemove addObject:key];
        [array removeObjectsInArray:toRemove];
        [toRemove release];
    }
    [self.keys removeObjectsInArray:sectionsToRemove];
    [sectionsToRemove release];
    [table reloadData];
}
 

#pragma mark -
#pragma mark UIViewController Methods

(中略)

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
    self.allNames = dict;
    [dict release];
    [self resetSearch];
    search.autocapitalizationType = UITextAutocapitalizationTypeNone;
    search.autocorrectionType = UITextAutocorrectionTypeNo;

}

(中略)

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;

    self.table = nil;
    self.search = nil;
    self.allNames = nil;

    self.names = nil;
    self.keys = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [table release];
    [search release];
    [allNames release];

    [names release];
    [keys release];
    [super dealloc];
}

#pragma mark -
#pragma mark Table View Data Source Methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return ([keys count] > 0) ? [keys count] : 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([keys count] == 0)
        return 0;


    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];
    return [nameSection count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];
    static NSString *SectionsTableIdentifier = @"SectionsTableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];

    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SectionsTableIdentifier] autorelease];
    }
    cell.textLabel.text = [nameSection objectAtIndex:row];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if ([keys count] == 0)
        return @"";


    NSString *key = [keys objectAtIndex:section];
    return key;
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return keys;
}

#pragma mark -
#pragma mark Table View Delegate Methods

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [search resignFirstResponder];
    return indexPath;
}

#pragma mark -
#pragma mark Search Bar Delegate Methods

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    NSString *searchTerm = [searchBar text];
    [self handleSearchForTerm:searchTerm];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm {
    if ([searchTerm length] == 0) {
        [self resetSearch];
        [table reloadData];
        return;
    }
    [self handleSearchForTerm:searchTerm];
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    search.text = @"";
    [self resetSearch];
    [table reloadData];
    [searchBar resignFirstResponder];
}
 

@end

947_



1)カテゴリNSDictionary-MutableDeepCopyのインポート

可変辞書生成メソッドmutableDeepCopyを使用するため、NSDictionary-MutableDeepCopy.hをインポートします。


2)プロパティの実装

アウトレットであるテーブルビューのtableと検索バーのsearch、元となる不変辞書のallNames、(同名なので追加ではありませんが)可変辞書のnamesとキーkeysのプロパティを実装します。


3)検索用メソッド2つの実装

resetSearch

resetSearchメソッドは、検索をキャンセルした時などにテーブルビュー表示用の可変辞書をリセットするメソッドです。

・allNames:元になる不変辞書
・names:検索結果を反映させた可変辞書
・keys:検索結果を反映させた可変配列

メソッド内で一時的に使う変数)
・keyArray:不変辞書内のキーをソートした可変配列

可変辞書namesは、元となる不変辞書allNamesからカテゴリメソッドmutableDeepCopyで可変コピーを生成します。

可変配列keysは、一時的な可変配列keyArrayを用意して、不変辞書allNamesからallKeysメソッドで全てのキーを取得し、sortedArrayUsingSelector:メソッドで昇順で並べ替え、addObjectsFromArray:で追加したものを入れています。

handleSearchForTerm:

handleSearchForTerm:メソッドは、検索バーに入力された語句を元に可変辞書namesとそのキー配列keysを組み替えるもので、検索バーのデリゲートメソッドであるsearchBarSearchButtonClicked:searchBar:textDidChange:から呼び出されます。
(両メソッドについては後述します)

・keys:検索結果を反映させた可変配列
・names:検索結果を反映させた可変辞書
・searchTerm:メソッドの引数で、検索バーに入力された語句
・table:アウトレットのテーブルビュー

メソッド内で一時的に使う変数)
・key:可変配列keysの要素で値の取得に用いるキー文字列
・array:可変辞書からkeyで取得される値の可変配列
・name:array内にある値の文字列
・toRemove:検索語句が含まれていない値を入れる一時保管庫
・sectionsToRemove:検索語句が含まれていないセクションのキーを入れる一時保管庫

ここでは検索語句を元に、該当しない値やセクションのキーを削除して、検索結果を反映させたデータを生成します。

各キーと値を総当たりするため、キーの高速列挙内に値の高速列挙が組み込んであります。

最初にresetSearchメソッドを呼び出し、可変辞書namesとそのキー配列keysを初期化します。

次にキーを取得する高速列挙のループに入り、キー文字列keyを使って辞書namesからvalueForKey:で値の配列arrayを取り出します。

得られた配列arrayを使って値nameを取得する高速列挙のループに入り、値の中に検索語句searchTermが含まれているかどうかをrangeOfString:options:で検索します。
(オプションのNSCaseInsensitiveSearchは、大文字/小文字を区別しない検索です)

rangeOfString:options:は第一引数で指定した文字列が含まれている場合の範囲をNSRangeで返します。

locationはNSRange構造体のメンバ変数で範囲の開始位置を示すもので、ここでのドット演算子はプロパティへのアクセスではありません。

locationがNSNotFound、つまり検索語句が見つからなかった場合、削除対象の値を一時保管するtoRemove配列にその値nameをaddObjectで追加します。

そのキー内の全ての値をチェックし終えたら、キー内の値arrayの要素数と削除対象を保管したtoRemoveの要素数を比較し、キー内に該当する値が無い場合はそのkeyを削除対象としてsectionsToRemoveにaddObjectで追加します。

削除対象の値toRemoveは、値の配列arrayからremoveObjectsInArray:で削除します。

全てのキーの検索が終わったら、削除対象のキーsectionsToRemoveはキーの配列keysからremoveObjectsInArray:で削除します。

不要なキーと値の削除が済んだら、最後にreloadDataでテーブルビューを再読み込みします。


addObjectsFromArray:

- (void)addObjectsFromArray:(NSArray *)otherArray

レシーバの内容の末尾に、指定した他の配列のオブジェクトを追加します。

otherArray:レシーバの内容の末尾に追加する配列オブジェクトを指定します。


searchBarSearchButtonClicked:

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

検索ボタンがタップされたことをデリゲートメソッドに伝えます。

検索を開始するにはこのメソッドを実装する必要があります。

検索バーのテキストの取得にはtextプロパティを使用します。

プログラムによる編集を開始するため、検索バーにbecomeFirstResponderを送信することもできます。

searchBar:タップされる検索バーを指定します。


searchBar:textDidChange

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

ユーザが検索テキストを変更したことをデリゲートに伝えます。

このメソッドは検索テキストフィールドからテキストがクリアされた時に呼び出されます。

searchBar:編集されている検索バーを指定します。

searchText:検索テキストフィールド内の現在のテキストです。


addObject:

- (void)addObject:(id)anObject

レシーバの最後に指定したオブジェクトを挿入します。

重要:anObjectがnilの場合、NSInvalidArgumentExceptionが発生します。

anObject:レシーバの内容の最後に追加するオブジェクトを指定します。
この値をnilにしてはいけません。


removeObjectsInArray:

- (void)removeObjectsInArray:(NSArray *)otherArray

指定した別の配列内のオブジェクトを、レシーバの配列から削除します。

このメソッドはremoveObject:に似ていますが、一回の処理でオブジェクト内の大きなセットを効率的に削除することができます。

otherArray内のオブジェクトがレシーバの配列に含まれていない場合、このメソッドは何の効果も与えません。
(ただし、コンテンツを検索するオーバーヘッドは生じます)

このメソッドは、otherArray内の全ての要素がhashとisEqual:に応答することを前提にしています。

otherArray:レシーバの配列から削除するためのオブジェクトを含む配列を指定します。


reloadData

- (void)reloadData

レシーバのセクションと行を再読み込みします。

このメソッドは、セルを含むセクションのヘッダとフッタ、インデックス配列など、テーブルの構成に使用する全てのデータを再読み込みします。

効率化のため、テーブルビューは可視状態の行のみ再表示します。

再読み込みの結果、テーブルが小さくなった場合はオフセットを調整します。

テーブルビューの完全なデータの再読み込みをしたい時は、このメソッドでテーブルビューのデリゲートまたはデータソースを呼び出します。

このメソッド内で、行の挿入や削除、特にbeginUpdatesendUpdatesの呼び出しを伴うアニメーションブロックの実装は行わないでください。


4)viewDidLoadメソッドの修正

以前はプロパティリストsortednames.plistから辞書namesと配列keysを生成していましたが、namesが検索結果用の可変辞書の名前になったので固定辞書はallNamesとなり、keysは可変配列としてresetSearchメソッドで定義され、呼び出すことになっています。

それと検索バーの属性の設定が2つ追加されています。

autocapitalizationTypeはテキストフィールドに入力された文字を自動で大文字にするかどうかを決めるプロパティで、デフォルト値のUITextAutocapitalizationTypeNoneと明示し、大文字にはしないことになっています。

autocorrectionTypeはテキストフィールドに入力された文字に対する自動校正の有効/無効を決めるプロパティで、こちらはデフォルト値ではなくUITextAutocorrectionTypeNoと自動校正を無効にしています。


5)viewDidUnloadとdeallocへのプロパティの追加

追加したプロパティ(table、search、allNames)について、viewDidUnloadでの所有権の放棄とdeallocでの解放を追加しています。


6)テーブルビューデータソースメソッドの修正

テーブルビューのデータソースメソッドの内3つに修正が加えられており、検索の結果でセクションや行が0の場合の対処の追加がされています。

numberOfSectionsInTableView:

テーブルビューのセクション数を返すメソッドでは、以前はキー配列の要素数をそのまま返していましたが、検索した結果キーの要素数(セクション数)が0になる可能性があるため、0より上ならkeysの要素数をそのまま返し、0なら1を返すようにしています。

tableView:numberOfRowsInSection:

テーブルビューの指定したセクションの行数を返すメソッドでは、有効なセクションが存在しない場合には0を返す判定文が、行数カウントの処理の前に追加されています。

tableView:titleForHeaderInSection:

テーブルビューのセクションのタイトルを返すメソッドでは、有効なセクションが存在しない場合にはタイトル文字列を空にする判定分が、タイトル文字列の処理の前に追加されています。


7)テーブルビューデリゲートメソッドの追加

テーブルビューのデリゲートメソッドで追加されているtableView:willSelectRowAtIndexPath:は、行が選択される時に呼び出されるメソッドで、ここでは検索バーに対する処理を行っています。

検索バーがアクティブの状態だと画面下半分にキーボードが出たままになるので、テーブルビューの行をタップしたらキーボードが消えるよう、resignFirstResponderメソッドで検索バーがFirst Responderを放棄するようにしています。


8)検索バーデリゲートメソッドの追加

最後に検索バーのデリゲートメソッドが3つ追加されています。

searchBarSearchButtonClicked:

キーボード右下にある『Search』ボタンがタップされた時の処理で、検索バーに入力されている語句をtextプロパティで取得し、検索処理メソッドhandleSearchForTerm:に渡しています。

searchBar:textDidChange:

検索バーのテキストフィールド内の語句が変更された時に呼び出されるメソッドです。

テキストフィールド内の語句が空(length == 0)になった場合は、resetSearchメソッドで可変辞書を元の辞書で初期化し直し、reloadDataでテーブルビューの再読み込みをしています。

検索語句がある場合、その語句を引数として検索処理メソッドhandleSearchForTerm:に渡しています。

searchBarCancelButtonClicked:

検索バーの右端にある『Cancel』ボタンがタップされた時の処理で、検索語句をtextプロパティで空にし、resetSearchメソッドで可変辞書を元の辞書で初期化し直し、reloadDataでテーブルビューの再読み込みした後、resignFirstResponderメソッドで検索バーがFirst Responderを放棄するようにしています。


searchBarSearchButtonClicked:

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

検索ボタンがタップされたことをデリゲートメソッドに伝えます。

検索を開始するにはこのメソッドを実装する必要があります。

検索バーのテキストの取得にはtextプロパティを使用します。

プログラムによる編集を開始するため、検索バーにbecomeFirstResponderを送信することもできます。

searchBar:タップされる検索バーを指定します。


text

@property(nonatomic, copy) NSString *text

現在の、または開始時の検索テキストです。

デフォルト値はnilです。


searchBar:textDidChange

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

ユーザが検索テキストを変更したことをデリゲートに伝えます。

このメソッドは検索テキストフィールドからテキストがクリアされた時に呼び出されます。

searchBar:編集されている検索バーを指定します。

searchText:検索テキストフィールド内の現在のテキストです。


searchBarCancelButtonClicked:

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar

キャンセルボタンがタップされたことをデリゲートに伝えます。

通常、検索バーを閉じるためにこのメソッドを実装します。

searchBar:タップされる検索バーを指定します。



参考文献

UISearchBarDelegate Protocol Reference

NSMutableArray Class Reference

UITableView Class Reference

UISearchBar Class Reference

Wikipedia/条件演算子

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

UITextInputTraitsプロトコル

2010. 11. 22
●概要

UITextInputTraitsプロトコルは、キーボードからの入力に関連付けされた機能を定義します。

テキスト入力管理システムと適切に対話できるよう、キーボード入力をサポートする全てのオブジェクトは、このプロトコルを採用する必要があります。

UITextFieldUITextViewクラスは既にこのプロトコルをサポートしています。



●タスク

●キーボード動作の管理

autocapitalizationType    required property
autocorrectionType    required property
enablesReturnKeyAutomatically    required property
keyboardAppearance    required property
keyboardType    required property
returnKeyType    required property
secureTextEntry    required property



●プロパティ

returnKeyType

@property(nonatomic) UIReturnKeyType returnKeyType

returnキーの内容を設定します。(必須)

このプロパティは様々なキータイプを設定するもので、キーのタイトルを変更し、通常は押された時にキーボードを片付けます。

このプロパティのデフォルト値はUIReturnKeyDefaultです。



●定数

UITextAutocapitalizationType

typedef enum {
    UITextAutocapitalizationTypeNone,
    UITextAutocapitalizationTypeWords,
    UITextAutocapitalizationTypeSentences,
    UITextAutocapitalizationTypeAllCharacters,
} UITextAutocapitalizationType;

テキストベースのビューにおける自動大文字化の動作です。

スクリプトシステムで大文字化をサポートしていない場合、キーボード入力メソッドはこの定数を無視します。

UITextAutocapitalizationTypeNone
テキストを自動的に大文字にしません。

UITextAutocapitalizationTypeWords
各単語の最初の文字を自動的に大文字にします。

UITextAutocapitalizationTypeSentences
各文の最初の文字を自動的に大文字にします。

UITextAutocapitalizationTypeAllCharacters
全ての文字を自動的に大文字にします。


UITextAutocorrectionType

typedef enum {
    UITextAutocorrectionTypeDefault,
    UITextAutocorrectionTypeNo,
    UITextAutocorrectionTypeYes,
} UITextAutocorrectionType;

テキストベースのビューにおける自動校正の動作です。

スクリプトシステムでインライン自動校正をサポートしていない場合、キーボード入力メソッドはこの定数を無視します。

UITextAutocorrectionTypeDefault
現在のスクリプトシステムの動作に適した自動校正を選択します。

UITextAutocorrectionTypeNo
自動校正の動作を無効にします。

UITextAutocorrectionTypeYes
自動校正の動作を有効にします。


UIReturnKeyType

キーボードのreturnキーに表示するテキスト文字列を設定します。

typedef enum {
    UIReturnKeyDefault,
    UIReturnKeyGo,
    UIReturnKeyGoogle,
    UIReturnKeyJoin,
    UIReturnKeyNext,
    UIReturnKeyRoute,
    UIReturnKeySearch,
    UIReturnKeySend,
    UIReturnKeyYahoo,
    UIReturnKeyDone,
    UIReturnKeyEmergencyCall,
} UIReturnKeyType;

UIReturnKeyDefault
returnキーのテキストを『return』に設定します。

UIReturnKeyGo
returnキーのテキストを『Go』に設定します。

UIReturnKeyGoogle
returnキーのテキストを『Google』に設定します。

UIReturnKeyJoin
returnキーのテキストを『Join』に設定します。

UIReturnKeyNext
returnキーのテキストを『Next』に設定します。

UIReturnKeyRoute
returnキーのテキストを『Route』に設定します。

UIReturnKeySearch
returnキーのテキストを『Search』に設定します。

UIReturnKeySend
returnキーのテキストを『Send』に設定します。

UIReturnKeyYahoo
returnキーのテキストを『Yahoo』に設定します。

UIReturnKeyDone
returnキーのテキストを『Done』に設定します。

UIReturnKeyEmergencyCall
returnキーのテキストを『Emergency Call』に設定します。



参考文献

UITextInputTraits Protocol Reference

Sections(3)~検索バーの追加(1)

2010. 11. 22
Sections(1)~セクションとインデックス』で作ったセクション付きテーブルに検索バーを追加するため、『Sections(2)~カテゴリ』で検索結果表示用の可変辞書生成メソッドmutableDeepCopyを作りました。

次はこのメソッドを組み込んだビューコントローラの改造を行います。


●ビューコントローラヘッダファイルの編集

SectionsViewController.hを開き、

・検索バーを利用するため、UISearchBarDelegateプロトコルへの準拠
・テーブルビューと検索バーのアウトレット(table、search)
・元となる不変辞書のプロパティ(allNames)
・検索結果表示用の可変辞書とキー配列のプロパティ(names、keys)
・検索語句を変更した際に、キー配列をリフレッシュするメソッド(resetSearch:)
・検索語句に応じたテーブルビュー用の可変配列を生成するメソッド(handleSearchForTerm:)

を追加します。

元にあった不変辞書namesとキー配列keysのプロパティは、検索結果用の可変辞書と可変配列のプロパティとして同名で使われているので注意してください。
(太字が追加・修正した部分)

#import <UIKit/UIKit.h>

@interface SectionsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate> {
    UITableView *table;
    UISearchBar *search;
    NSDictionary *allNames;
    NSMutableDictionary *names;
    NSMutableArray *keys;

}

@property (nonatomic, retain) IBOutlet UITableView *table;
@property (nonatomic, retain) IBOutlet UISearchBar *search;
@property (nonatomic, retain) NSDictionary *allNames;
@property (nonatomic, retain) NSMutableDictionary *names;
@property (nonatomic, retain) NSMutableArray *keys;

- (void)resetSearch;
- (void)handleSearchForTerm:(NSString *)searchTerm;


@end

939

Apress社のサイト内のサンプルコードではUISearchBarDelegateの追加がされていないので注意してください。
(『Googleブックス/Beginning IPhone 3 Development: Exploring the IPhone SDK』では記述がされています。UISearchBarDelegateの追加がされていなくても検索はできています)


●ビューコントローラのnibファイルの編集

nibファイルを開いて、ビューに検索バーを追加します。

SectionsViewController.xibをダブルクリックしてInterface Builderで開きます。

ViewウィンドウでTable Viewを選択し、上端を下方にドラッグして検索バーを入れるスペースを多めに確保します。

940

LibraryウィンドウからSearch BarをViewウィンドウにドラッグ&ドロップし、ステータスバーのすぐ下に設置します。

941

再びViewウィンドウのTable Viewを選択し、上端を上方にドラッグして隙間を無くします。

942

次にSearch Barを選択してInspectorウィンドウのAttributesタブを開き、検索バーの属性を設定します。

『Search Bar』の『Placeholder』(未入力時にテキストフィールドに表示される語句)に『search』と入力し、『Shows Cancel Button』のチェックボックスにチェックを入れて、キャンセルボタンを追加します。

943

944

Connectionsタブを開いて『Outlets』の『delegate』をFile's Ownerに接続します。

945

最後にDocumentウィンドウでFile's Ownerを選択し、Connectionsタブを開いて『Outlets』の『search』をViewウィンドウの検索バーに、『table』をテーブルビューに接続します。

946



参考文献

UISearchBarDelegate Protocol Reference

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

UISearchBarDelegateプロトコル

2010. 11. 19
●概要

UISearchBarDelegateプロトコルは、UISearchBarコントロール機能を実装するオプションメソッドを定義します。

UISearchBarオブジェクトは、検索フィールドを持つバーのユーザインターフェイスを提供しますが、ボタンがタップされた時のアクションの実行はアプリケーションの責任となります。

少なくともデリゲートは、テキストフィールドにテキストが入力された時に、実際の検索を実行する必要があります。



●タスク

●テキストの編集

– searchBar:textDidChange:
– searchBar:shouldChangeTextInRange:replacementText:
– searchBarShouldBeginEditing:
– searchBarTextDidBeginEditing:
– searchBarShouldEndEditing:
– searchBarTextDidEndEditing:

●ボタンのクリック

– searchBarBookmarkButtonClicked:
– searchBarCancelButtonClicked:
– searchBarSearchButtonClicked:
– searchBarResultsListButtonClicked:

●スコープボタン

– searchBar:selectedScopeButtonIndexDidChange:



●インスタンスメソッド

searchBar:textDidChange:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

ユーザが検索テキストを変更したことをデリゲートに伝えます。

このメソッドは検索テキストフィールドからテキストがクリアされた時に呼び出されます。

searchBar:編集されている検索バーを指定します。

searchText:検索テキストフィールド内の現在のテキストです。


searchBarCancelButtonClicked:

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar

キャンセルボタンがタップされたことをデリゲートに伝えます。

通常、検索バーを閉じるためにこのメソッドを実装します。

searchBar:タップされる検索バーを指定します。


searchBarSearchButtonClicked:

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

検索ボタンがタップされたことをデリゲートメソッドに伝えます。

検索を開始するにはこのメソッドを実装する必要があります。

検索バーのテキストの取得にはtextプロパティを使用します。

プログラムによる編集を開始するため、検索バーにbecomeFirstResponderを送信することもできます。

searchBar:タップされる検索バーを指定します。


searchBarTextDidBeginEditing:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar

ユーザが検索テキストの編集を開始したことをデリゲートに伝えます。

searchBar:編集を開始する検索バーを指定します。



参考文献

UISearchBarDelegate Protocol Reference

UISearchBarクラス

2010. 11. 19
●概要

UISearchBarクラスは、テキストベースで検索するためのテキストフィールドコントロールを実装します。

コントロールは、テキストを入力するテキストフィールドと検索ボタン、ブックマークボタン、そしてキャンセルボタンを提供します。

UISearchBarオブジェクトは実際に検索を実行するわけではありません。

テキストが入力されボタンがクリックされた時のアクションの実装は、オブジェクトをUISearchBarDelegateプロトコルに準拠させ、デリゲートを使用してください。



●タスク

●テキストコンテンツ

  placeholder    property
  prompt    property
  text    property

●表示属性

  barStyle    property
  tintColor    property
  translucent    property

●テキスト入力プロパティ

  autocapitalizationType    property
  autocorrectionType    property
  keyboardType    property

●ボタンの構成

  showsBookmarkButton    property
  showsCancelButton    property
– setShowsCancelButton:animated:
  showsSearchResultsButton    property
  searchResultsButtonSelected    property

●スコープボタン

  scopeButtonTitles    property
  selectedScopeButtonIndex    property
  showsScopeBar    property

●デリゲート

  delegate    property



●プロパティ

autocapitalizationType

@property(nonatomic) UITextAutocapitalizationType autocapitalizationType

テキストオブジェクトの自動大文字化のスタイルです。

このプロパティは入力された文字が大文字になるよう、Shiftキーが自動的に押された状態になることを決めます。

このプロパティのデフォルト値はUITextAutocapitalizationTypeNoneです。


autocorrectionType

@property(nonatomic) UITextAutocorrectionType autocorrectionType

テキストオブジェクトの自動校正のスタイルです。

このプロパティは入力中の自動校正について、有効または無効を決めます。

自動校正が有効な場合、未知な語句を検出して代替候補をユーザに提示し、ユーザが明示的に無効にするアクションを起こさない限り自動的にテキストを置換します。

このプロパティのデフォルト値はUITextAutocorrectionTypeDefaultで、ほとんどの入力メソッドは自動校正を有効にしています。


text

@property(nonatomic, copy) NSString *text

現在の、または開始時の検索テキストです。

デフォルト値はnilです。



●インスタンスメソッド



参考文献

UISearchBar Class Reference

Sections(2)~カテゴリ

2010. 11. 18
前回の『Sections(1)~セクションとインデックス』で作ったテーブルはデータ量が多く、インデックスバーだけでは絞り込みが足りないので、検索バーを追加します。

検索バーに文字列を入力すると、その文字列を含む要素が随時絞り込まれてテーブルが更新されていき、検索をキャンセルすると元のテーブルに戻ります。

したがって、元になる不変の辞書とは別に、入力された文字列に応じて適合する要素だけを集めた可変の辞書が必要になります。


●ディープミュータブルコピー

本書の『ディープミュータブルコピー』の項の説明を少し補足します。

NSDictionaryクラスのリファレンスを見ると、概要に『辞書クラスはNSCopyingNSMutableCopyingプロトコルを採用しており、他の辞書の型を容易に変換することができます。』とあります。

したがってNSMutableCopyingプロトコルのmutableCopyWithZone:メソッド(もしくはゾーン指定をしないNSObjectmutableCopyメソッド)を使って、NSDictionaryオブジェクトを可変のNSMutableDictionaryオブジェクトに変換することができます。

しかし『コレクションのコピー』の説明のように、mutableCopyWithZone:メソッドは浅いコピーとなるため、実質的には『可変型のコピーを生成』するのではなく『可変型への変換を行う』ことになり、今回のように元の辞書を不変で別途に保持する必要がある場合には使えません。

『コレクションのコピー』では深いコピーや可変コピーの方法がいくつかあげられていますが、以下の理由により今回のケースでは使えません。

1)initWithArray:copyItems:の第2引数をYESにして使う
辞書の場合はinitWithDictionary:copyItems:になりますが中身は同じですので、返されるオブジェクトが不変になる。

2)コレクションをアーカイブしてアンアーカイブする
全てのコンテンツをNSCodingプロトコルに準拠させる必要があるのと、検索バーに一文字入力する度に辞書をアーカイブ&アンアーカイブするとなると、オーバーヘッドが大変そうです。
全ての階層をコピーできますが、元の特性そのままのコピーになる。

結論として既存のメソッドを使うのではなく、カテゴリで拡張したメソッドを追加するということになります。


●カテゴリ

詳解 Objective-C 2.0』によると、元々は肥大化したクラスを似たような特性毎に複数のモジュールに分割して定義するための仕組みとあります。

その仕組みの中に既存のクラス(システムか自作かを問わず)にメソッドを追加できる機能があります。

インスタンス変数の追加はできないものの、『Dynamic Objective-C』によればサブクラス化との違いとして、

・メソッド呼び出しのオーバーヘッドが少ない
・既存のインスタンスに対しても拡張できる

というメリットがあげられています。

カテゴリの宣言と定義はクラスとほぼ同様で、インスタンス変数の宣言が無いのが特徴です。

・宣言:@interface クラス名 (カテゴリ名)
・定義:@implementation クラス名 (カテゴリ名)

カテゴリを利用する際には、以下の3つの参照(インポート)が必要になります。

・カテゴリのヘッダファイルで元になるクラスを参照する
・カテゴリのソースファイルでカテゴリのヘッダファイルを参照する
・カテゴリのメソッドを使う場合、呼び出すソースファイルはカテゴリのヘッダファイルを参照する

なお『詳細Objective-C 2.0』では、ファイル名は『クラス名 + カテゴリ名』にするのが一般的とありますが、ここでは本書内容に従って『クラス名 - カテゴリ名』のままで作成します。


●カテゴリファイルの追加

今回は『Sections(1)~セクションとインデックス』のプロジェクトに元に改造を行っていきます。

まずカテゴリファイルを追加していきます。

Xcodeの『Classes』を右クリックして『追加』→『新規ファイル...』を選択します。

934

本書で解説されているように、カテゴリ用のテンプレートはありませんが、クラスとほぼ同じなので『iOS』の『Cocoa Touch Class』にある『Objective-C class』(Subclass ofはNSObject)テンプレートを使用し、ファイル名は『NSDictionary-MutableDeepCopy』とします。
Apress社のサイト内のサンプルコードでは『NSDictionary-DeepMutableCopy』となっていますので注意してください)


●カテゴリヘッダファイルの編集

ファイル名にあるように、NSDictionaryクラスの追加メソッドとして、NSDictionaryオブジェクトからNSMutableDictionaryオブジェクトを新規コピーするmutableDeepCopyを作成します。

935

カテゴリのヘッダファイルでは、元となるクラスのインポートと『@interface クラス名 (カテゴリ名)』での宣言を行います。

『Objective-C class』テンプレートで作られているものの内、#import文はNSDictionaryのフレームワークである『Foundation.h』が既に記述されているので変更する必要はありませんので、カテゴリと追加メソッドの宣言を記述するだけとなります。
(太字が追加・修正した部分)

#import <Foundation/Foundation.h>

@interface NSDictionary (MutableDeepCopy)

- (NSMutableDictionary *)mutableDeepCopy;

@end

937

@interfaceでNSDictionaryクラスにMutableDeepCopyカテゴリを追加します。

追加メソッドとして、可変辞書NSMutableDictionaryを返すmutableDeepCopyメソッドの宣言を行っています。


●カテゴリソースファイルの編集

カテゴリのソースファイルでは、カテゴリヘッダファイルのインポートと、カテゴリと追加メソッドの定義を行います。

936

『Objective-C class』テンプレートでヘッダファイルのインポートは済んでいますので、カテゴリと追加メソッドの定義のみ記述するだけとなります。
(太字が追加した部分)

#import "NSDictionary-MutableDeepCopy.h"

@implementation NSDictionary(MutableDeepCopy)

- (NSMutableDictionary *)mutableDeepCopy {
    NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:[self count]];
    NSArray *keys = [self allKeys];

    for (id key in keys) {
        id oneValue = [self valueForKey:key];
        id oneCopy = nil;

        if ([oneValue respondsToSelector:@selector(mutableDeepCopy)])
            oneCopy = [oneValue mutableDeepCopy];
        else if ([oneValue respondsToSelector:@selector(mutableCopy)])
            oneCopy = [oneValue mutableCopy];

        if (oneCopy == nil)
            oneCopy = [oneValue copy];

        [ret setValue:oneCopy forKey:key];
    }
    return ret;
}


@end

938

カテゴリの定義は『@implementation クラス名 (カテゴリ名)』となっています。

mutableDeepCopyメソッドは、レシーバのNSDictionaryオブジェクトを可変のNSMutableDictionaryオブジェクトにして返すメソッドです。

最初にdictionaryWithCapacity:メソッドで新規に可変辞書retを生成します。

初期容量として指定する引数numItemsには、レシーバの辞書内のエントリ数をcountメソッドで返しています。

次にallKeysメソッドで、レシーバの辞書内のキーを新規配列keysに取り込みます。

続いて高速列挙for...in文で元になるNSDictionaryオブジェクトからエントリ(要素)を取得します。

高速列挙では、取得するオブジェクトがどんな型でも大丈夫なようにid型の変数を指定します。

先程取り込んだ配列keysからキーを取得してkeyに入れ、{}内でkeyによる処理が終わったら次のキーをkeysから取り出して・・・という繰り返しになります。

取り出したkeyはvalueForKey:メソッドで値を取り出し、その値oneValueをrespondsToSelector:でコピーするメソッドの判別を行っています。

respondsToSelector:メソッドでは、引数で指定したメソッドが応答できるかどうかを判別しています。

つまりキーで取得した値oneValueが、

・mutableDeepCopyメソッドで処理可能な場合、mutableDeepCopyでoneCopyにコピー

mutableCopyメソッドで処理可能な場合、mutableCopyでoneCopyに(浅い)コピー

をすることになり、NSMutableCopyingプロトコルに準拠しているオブジェクトのコピーをしていることになります。

両メソッドでコピーできない(NSMutableCopyingプロトコルに準拠していないオブジェクト)場合、oneCopyの値がnilなので、次のif文に入りcopyメソッドでoneCopyにコピーされます。

これはNSCopyingプロトコルに準拠している場合となり、NSMutableCopyingとNSCopying両プロトコルに準拠していないオブジェクトは、oneCopyがnilのままとなるはずです。

そしてoneCopyの値とkeyのキーを使ってsetValue:forKey:メソッドで新規の可変辞書retに追加します。

高速列挙で全てのキーに対するコピーが終了したら、最後に可変辞書retを返します。


dictionaryWithCapacity:

+ (id)dictionaryWithCapacity:(NSUInteger)numItems

numItemsで指定したエントリ数を保持するのに十分なメモリを割り当て、可変辞書を生成して返します。

可変辞書は必要に応じてメモリを追加して割り当てるので、numItemsは単純にオブジェクトの初期容量を確立するためのものです。

numItems:新規辞書の初期容量を指定します。


count

- (NSUInteger)count

辞書内のエントリ数を返します。


valueForKey:

- (id)valueForKey:(NSString *)key

指定したキーに関連付けされた値を返します。

keyが『@』で始まっていない場合はobjectForKey:を呼び出します。

keyが『@』で始まっている場合は、『@』を取り除いた残りの部分で [super valueForKey:] を呼び出します。

key:対応する値を返すためのキーを指定します。
キー値コーディングを使用する場合、キーは文字列である必要があります。
(『Key-Value Coding Programming Guide』を参照してください)


respondsToSelector:

- (BOOL)respondsToSelector:(SEL)aSelector

指定されたメッセージに応答することができる継承されたメソッド、またはレシーバが実装されているかを示すブール値を返します。

aSelectorに応答可能な継承されたメソッド、またはレシーバが実装されている場合はYESを、それ以外の場合はNOを返します。
(必須)

アプリケーションには、NOを返された時にエラーかどうかを検討して判断する責任があります。

オブジェクトがsuperキーワードを使ってrespondsToSelector:を送信することにより、メソッドがスーパークラスから継承しているかをテストすることはできません。

このメソッドはオブジェクト全体としてテストするだけで、実際のスーパークラスの実装まではテストしません。

したがって、superにrespondsToSelector:を送信することは、selfに送信するのと同じです。

代わりにオブジェクトのスーパークラスで直接NSObjectクラスメソッドのinstancesRespondToSelector:を呼び出す必要がある場合は、以下に示すのコードで表します。

if( [MySuperclass instancesRespondToSelector:@selector(aMethod)] ) {
    // invoke the inherited method
    [super aMethod];
}

単純に [[self superclass] instancesRespondToSelector:@selector(aMethod)] とすると、サブクラスによって呼び出された場合に失敗する可能性があるため、使用することはできません。

間接的ではありますが、レシーバは他のオブジェクトにaSelectorメッセージを転送し、メッセージの応答を得ることができますが、実際にはこのメソッドはNOを返します。

aSelector:メッセージを識別するセレクタを指定します。


mutableCopy

- (id)mutableCopy

ゾーンがnilのmutableCopyWithZone:で返されるオブジェクトを返します。

これはNSMutableCopyingプロトコルを採用するクラスの便利なメソッドです。

mutableCopyWithZone:が実装されていない場合は例外が発生します。

特別な考慮事項

(ガベージコレクションではない)メモリ管理を使用している場合、このメソッドはオブジェクトを返す前に保持します。

メソッドを呼び出した側は、返されたオブジェクトを解放する責任があります。


setValue:forKey:

- (void)setValue:(id)value forKey:(NSString *)key

辞書にキーと値のペアを追加します。

このメソッドはsetObject:forKey:を使って辞書にvalueとkeyを追加します。

valueがnilの場合、removeObjectForKey:を使ってkeyの削除を試みます。

value:keyの値を指定します。

key:valueのキーを指定します。
キー値コーディングを使用する場合、キーは文字列である必要があります。
(『Key-Value Coding Programming Guide』を参照してください)


initWithCapacity:

- (id)initWithCapacity:(NSUInteger)numItems

numItemsで指定したエントリ数を保持するのに十分なメモリを割り当て、可変辞書を生成して返します。

可変辞書は必要に応じてメモリを追加して割り当てるので、numItemsは単純にオブジェクトの初期容量を確立するためのものです。

numItems:新規辞書の初期容量を指定します。


●可変辞書retの初期化について

Apress社のサイト内のサンプルコードでは、可変辞書retの初期化に(autoreleaseで自動解放される)コンビニエンスメソッドのdictionaryWithCapacity:ではなく、(手動解放する必要がある)通常のイニシャライザのinitWithCapacity:が使われています。

コード中に解放する記述が無いので『Googleブックス/Beginning IPhone 3 Development: Exploring the IPhone SDK』を読んでみると、

我々の(mutableDeepCopy)メソッドは名前に『copy』が入っているので、copyWithZone:と同じメモリの規則に従うため、retainカウントが1のオブジェクトを返すことになっています。
(原文:Because our method has "copy" in its name, it follows the same memory rules as the copyWithZone: method, which are supposed to return an object with a retain count of 1.)

と、説明されていました。

公式リファレンスなどで裏を取ろうと思ったのですが調べきれず、『詳解 Objective-C 2.0』の『コーディングの指針』や関連項目にもそのような記述が見当たらないのでよく分かりません。

ちなみにXcodeの『実行』メニューの『パフォーマンスツールを使って実行』で『Leaks』を選択し、Instrumentsでメモリリークを確認してみると、旧版本書のコードでも新版のサンプルコードでも検索を試しているとリークが発生します。
(このことが原因かどうかは、まだ追いかけていません。未だにInstrumentsの使い方もわかりませんので・・・)



参考文献

NSMutableDictionary Class Reference

NSDictionary Class Reference

NSObject Protocol Reference

NSObject Class Reference

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

Dynamic Objective-CDynamic Objective-C
(2009/03/27)
木下 誠

商品詳細を見る

コレクションのコピー

2010. 11. 15
はじめてのiPhoneプログラミング』の本文中で、『NSMutableCopyingのコピーは浅いコピーなので~』の説明が足りない気がしたので調べてみました。

オブジェクトのコピーとしては『浅いコピーと深いコピー』として『詳解 Objective-C 2.0』に解説があるのですが、辞書の場合についてもう少し知りたくて検索した所『Collections Programming Topics』というリファレンスがありましたので、『Copying Collections』の項を訳してみます。


●コレクションのコピー

オブジェクトのコピーには浅いコピー(Shallow copy)と深いコピー(Deep copy)の二種類があります。

通常のコピーは浅いコピーで、新しく生成したコレクションの所有権は元のオブジェクトと共有しています。

深いコピーは元から新しいオブジェクトを生成し、新しいコレクションとして追加されます。

図1にこの違いを示します。

図1 浅いコピーと深いコピー

933


●浅いコピー

コレクションの浅いコピーを作成する方法にはいくつかあります。

浅いコピーを生成する際、元のコレクションのオブジェクトは保持メッセージを送信し、ポインタが新しいコレクションへコピーされます。

リスト1に浅いコピーを使った新しいコレクションの生成方法をいくつか示します。

リスト1 浅いコピーの作成

NSArray *shallowCopyArray=[someArray copyWithZone:nil];

NSDictionary *shallowCopyDict=[[NSDictionary alloc] initWithDictionary: someDictionary copyItems: NO];

これらの手法はコレクションに制限されません。

例えば、copyWithZone:メソッドやmutableCopyWithZone:メソッドでセットの、initWithArray:copyItems:メソッドで配列のコピーを作ることができます。


●深いコピー

コレクションの深いコピーの作成には二つの方法があります。

initWithArray:copyItems:の第2引数をYESにして使うと、同じコレクションを得ることができます。

この方法でコレクションの深いコピーを生成すると、コレクションの各オブジェクトはcopyWithZone:にメッセージを送信します。

コレクションのオブジェクトがNSCopyingプロトコルを採用している場合、オブジェクトは新しいコレクションに深くコピーされ、コピーされたオブジェクトの唯一のオーナーとなります。

オブジェクトがNSCopyingプロトコルを採用していない場合、コピーを試みるとランタイムエラーが発生します。

しかし、copyWithZone:が生成するのは浅いコピーです。

このコピーでは1レベル層のコピーしか生成することができません。

1レベル層のコピーが必要な場合は、リスト2のように明示的に呼び出します。

リスト2 深いコピーの作成

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: someArray copyItems: YES];

この手法は他のコレクションでも同様に適用されます。

initWithArray:copyItems:の第2引数をYESにして使うと、同じコレクションを得ることができます。

配列内の配列などの本当の深いコピーが必要な場合は、全てのコンテンツをNSCodingプロトコルに準拠させ、コレクションをアーカイブしてアンアーカイブする方法があります。

リスト3にこの手法を示します。

リスト3 本当の深いコピー

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
    [NSKeyedArchiver archivedDataWithRootObject: oldArray]];


●可変コピー

コレクションをコピーする際、可変コレクションであったり含まれているオブジェクトが影響受ける可能性がある場合があります。

その場合コピーを行う各メソッド毎に、コレクションの深さによるオブジェクトの可変性が僅かに異なります。

・copyWithZone:
表層レベルが不変で作成されます。
より深いレベルは全て元の可変性を持ちます。

・第2引数がNOのinitWithArray:copyItems:
第2引数をNOにすると、表層レベルはその可変クラスで割り当てられます。
より深いレベルは全て元の可変性を持ちます。

・第2引数がYESのinitWithArray:copyItems:
第2引数をYESにすると、表層レベルはその可変クラスで割り当てられます。
次の階層は不変になり、より深いレベルは全て元の可変性を持ちます。

・アーカイブとアンアーカイブ
コレクションの全ての階層レベルで元と同じ可変性を持ちます。



参考文献

Collections Programming Topics

0 CommentsPosted in 資料

NSMutableCopyingプロトコル

2010. 11. 15
●概要

NSMutableCopyingプロトコルはオブジェクトの可変コピーを提供するためのメソッドを宣言します。

このプロトコルを採用する必要があるのは、『不変と可変』を区別して定義するクラスのみです。

区別して定義する必要が無いクラスは、代わりにNSCopyingを使用してください。

NSMutableCopyingはmutableCopyWithZone:という一つのメソッドを宣言していますが、一般的に可変コピーをする際には便利なmutableCopyメソッドを呼び出します。

mutableCopyメソッドは全てのオブジェクトが継承しているNSObjectで定義されているメソッドで、単純にデフォルトゾーンでのmutableCopyWithZone:の呼び出しになります。

スーパークラスからNSMutableCopyingを継承してインスタンス変数を追加したサブクラスを作る場合、サブクラスは独自のインスタンス変数を適切に処理するようmutableCopyWithZone:をオーバーライドし、呼び出す際には最初にスーパークラスの実装をしてください。



●タスク

・コピー

– mutableCopyWithZone:    required method



●インスタンスメソッド

mutableCopyWithZone:

- (id)mutableCopyWithZone:(NSZone *)zone

レシーバの可変コピーである新しいインスタンスを返します。
(必須)

返されるオブジェクトはセンダーによって暗黙的に保持されるため、解放する責任があります。

元が可変または不変であっても、返されるコピーは可変になります。

zone:新しいインスタンスを割り当てるメモリ領域を識別するゾーンを指定します。
zoneがNULLの場合、新しいインスタンスはデフォルトゾーンに割り当てられ、関数からはNSDefaultMallocZoneが返されます。



参考文献

NSMutableCopying Protocol Reference






QuietControl 30 wireless headphones
Calendar
10 | 2010/11 | 12
Sun Mon Tue Wed Thu Fri Sat
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 - - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

QuietControl 30 wireless headphones
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR