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 他

商品詳細を見る






bose_soundsport_free
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
09 | 2017/10 | 11
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 31 - - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

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

Wish List
WACOM


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

bose_soundsport_free
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