【Xcode】TableViewセルの検索機能を実装する

1.TableView と Search Bar and Search Display を使うための下準備をする

1.プロジェクトからシングルビューをつくり、ストーリーボード上の ViewController に TableView と Search Bar and Search Display を配置する。

2.配置した2つの View(TableView と Search Bar and Search Display)を ViewController.mへそれぞれ接続する。

3.プロトコルを追加。

//ViewController.h(m)
----------------------------------------------
@interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate>

//searchBar を接続(ストーリーボードからドラッグして行う)。
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

//TableView を接続(ストーリーボードからドラッグして行う)。
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end
----------------------------------------------

2.テーブルビューのセルにデータを表示させる

以下の記述をファイルに追加する。

//ViewController.h(m)
----------------------------------------------
@interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate>

//searchBar を接続
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

//TableView を接続
@property (weak, nonatomic) IBOutlet UITableView *tableView;

/**
* さきほど記述した場所の下に追記する。テーブルに表示する情報が入ります
*/
@property (nonatomic, strong) NSArray *dataSourceiPhone;
@property (nonatomic, strong) NSArray *dataSourceAndroid;

@end
----------------------------------------------
これを追記したタイミングで「"Assigning to 'id<~>' from incompatible type '~' "」のようなエラーが出る場合は、プロトコロルがきちんと実装できているか確認する。

//これも記述
----------------------------------------------
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

// デリゲートメソッドをこのクラスで実装する
self.tableView.delegate = self;
self.tableView.dataSource = self;

// テーブルに表示したいデータソースをセット
self.dataSourceiPhone = @[@"iPhone 4", @"iPhone 4S", @"iPhone 5", @"iPhone 5c", @"iPhone 5s"];
self.dataSourceAndroid = @[@"Nexus", @"Galaxy", @"Xperia"];

}
----------------------------------------------

//これもまるまる追記
----------------------------------------------
@implementation ViewController

(略)

//ここから下を全部追記

#pragma mark - TableViewDataSource delegate methods

/**
* テーブルに表示するデータ件数を返します(実装必須)
*
* @param tableView テーブルビュー
* @param section 対象セクション番号
*
* @return データ件数
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger dataCount;

switch (section) {
case 0:
dataCount = self.dataSourceiPhone.count;
break;
case 1:
dataCount = self.dataSourceAndroid.count;
break;
default:
break;
}
return dataCount;
}

/**
* テーブルに表示するセクション(区切り)の件数を返します(任意実装)
*
* @param テーブルビュー
*
* @return セクション件数
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}

/**
* テーブルに表示するセルを返します(実装必須)
*
* @param tableView テーブルビュー
* @param indexPath セクション番号・行番号の組み合わせ
*
* @return セル
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

// 再利用できるセルがあれば再利用する
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (!cell) {
// 再利用できない場合は新規で作成
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}

switch (indexPath.section) {
case 0:
cell.textLabel.text = self.dataSourceiPhone[indexPath.row];
break;
case 1:
cell.textLabel.text = self.dataSourceAndroid[indexPath.row];
break;
default:
break;
}

return cell;
}

@end
----------------------------------------------

セルにデータが表示てきていることを確認する。

3.検索バーをタップした時のTableViewControllerに検索結果を表示する準備をする

//検索結果を格納するNSArrayをプロパティで宣言する
----------------------------------------------
@interface ViewController ()

@property (nonatomic, strong) NSArray *dataSourceiPhone;
@property (nonatomic, strong) NSArray *dataSourceAndroid;

//ここを追記
//検索結果を格納する配列
@property (nonatomic, strong) NSArray *dataSourceSearchResultsiPhone;
@property (nonatomic, strong) NSArray *dataSourceSearchResultsAndroid;

@end
----------------------------------------------

結果を表示するViewControllerはStoryboardで 「Search Bar and Search Display」を紐づけした時に自動で参照が追加されて Self(UIViewController)にsearchDisplayController として紐づけられている。

//numberOfRowsInSectionを変更
----------------------------------------------
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger dataCount;

// ここのsearchDisplayControllerはStoryboardで紐付けされたsearchBarに自動で紐づけられている
//検索キーワードが入力されているか判定
if (tableView == self.searchDisplayController.searchResultsTableView) {
// 検索中のテーブルビューはこちらで処理
//セクションが複数ある場合はセクションごとに
switch (section) {
case 0:
dataCount = self.dataSourceSearchResultsiPhone.count;
break;
case 1:
dataCount = self.dataSourceSearchResultsAndroid.count;
break;
default:
break;
}
} else {
// 通常時のテーブルビューはこちらで処理
switch (section) {
case 0:
dataCount = self.dataSourceiPhone.count;
//もし、検索前のセルにデータを表示したくない場合はデータの数を0にする
//dataCount = 0;
break;
case 1:
dataCount = self.dataSourceAndroid.count;
break;
default:
break;
}
}
NSLog(@"%ld", (long)dataCount);
return dataCount;
}
----------------------------------------------

//cellForRowAtIndexPathを変更
----------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
// 再利用できるセルがあれば再利用する
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (!cell) {
// 再利用できない場合は新規で作成
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}

// ここのsearchDisplayControllerはStoryboardで紐付けされたsearchBarに自動で紐づけられている
//検索キーワードが入力されているか判定
if (tableView == self.searchDisplayController.searchResultsTableView) {
// 検索中のテーブルビューはこちらで処理
//セクションが複数ある場合はセクションごとに
switch (indexPath.section) {
case 0:
cell.textLabel.text = self.dataSourceSearchResultsiPhone[indexPath.row];
break;
case 1:
cell.textLabel.text = self.dataSourceSearchResultsAndroid[indexPath.row];
break;
default:
break;
}
} else {
// 通常時のテーブルビューはこちらで処理
switch (indexPath.section) {
case 0: // iOS
cell.textLabel.text = self.dataSourceiPhone[indexPath.row];
break;
case 1: // Android
cell.textLabel.text = self.dataSourceAndroid[indexPath.row];
break;
default:
break;
}
}

return cell;
}
----------------------------------------------

4.データを絞り込む

----------------------------------------------
// 検索バーに入力された文字列を引数に、絞り込みをかけます
- (void)filterContainsWithSearchText:(NSString *)searchText
{
//CONTAINS右辺値が含まれているか,cは大文字小文字の区別なしオプション
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@", searchText];

self.dataSourceSearchResultsiPhone = [self.dataSourceiPhone filteredArrayUsingPredicate:predicate];
self.dataSourceSearchResultsAndroid = [self.dataSourceAndroid filteredArrayUsingPredicate:predicate];
}
----------------------------------------------

NSPredicate の詳細はこちら

//検索バーの文字を編集する度に呼ばれるデリゲートメソッド
----------------------------------------------
- (BOOL)searchDisplayController:controller shouldReloadTableForSearchString:(NSString *)searchString
{
// 検索バーに入力された文字列を引数に、絞り込みをかけます
[self filterContainsWithSearchText:searchString];

// YESを返すとテーブルビューがリロードされます。
// リロードすることでdataSourceSearchResultsiPhoneとdataSourceSearchResultsAndroidからテーブルビューを表示します
return YES;
}
----------------------------------------------

//セルが選択されたら
----------------------------------------------
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

NSLog(@"セルが選択されました");

}
----------------------------------------------
↑これで、1度目のタップでうまく反応しない場合は、didSelectRowAtIndexPath が didDeselectRowAtIndexPath になっていないか確認すること。

参考ページ

【Xcode】ナビゲーションバーのBackボタンのタイトルの変更

ナビゲーションバーのBackボタンのタイトルは、何も設定しないと自動的に前画面のタイトルが付けられる。自分で設定するには次のように記述する。

//画面遷移前のビューで設定する
------------------------------------------------

//ナビゲーションバーの back ボタンの指定
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"ボタンのタイトル" style:UIBarButtonItemStylePlain target:nil action:nil];

//タイトルの設定
[self.navigationItem setBackBarButtonItem:backBarButtonItem];

------------------------------------------------

【Xcode】TableViewでセルを選択してもハイライトにしない方法

Xcode でセルを選択してもハイライトにしない記述

//Objective-C での書き方
--------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell3" forIndexPath:indexPath];

//ここ
cell.selectionStyle = UITableViewCellSelectionStyleNone;

}
--------------------------------------------------

【Xcode】Supporting Files内のファイルをdocumentsディレクトリにコピーする

Supporting Files内のファイルをdocumentsディレクトリにコピーしたときのスクリプト。
初回起動時に実行したかったので、スクリプトは、AppDelegate.m 内の didFinishLaunchingWithOptions: に記述した。

--------------------------------------------------------------------
//didFinishLaunchingWithOptions は、アプリ起動時に呼び出される。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

//呼び出したいメソッドで下記を実行

NSError *error;

//NSFileManagerクラスは、Foundation フレームワーク(Cocoa Touchフレームワーク)にあるクラス。
//Objective-C で、パスの存在を調べたり、ディレクトリを作成したりするのに使う。

NSFileManager *fm = [NSFileManager defaultManager];

//documentsディレクトリのパス

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"コピーしたいファイル.jpg"];
NSLog(@"writableDBPath;%@",writableDBPath);


/*
//常に新しいDBファイルにしたい場合は、アプリ起動時に必ずファイルを消去する。
/* pathのファイルを削除 */
BOOL result = [fm removeItemAtPath:writableDBPath error:&error];
if (result) {
NSLog(@"ファイルを削除に成功:%@", writableDBPath);
} else {
NSLog(@"ファイルの削除に失敗:%@", error.description);
}
*/

BOOL result_flag = [fm fileExistsAtPath:writableDBPath];
if(!result_flag){

//dbが存在してなかったらここが呼ばれて、作成したDBをコピー

//Supporting Files内に置いたファイルのパス
//ここで null が帰ってきたりうまくコピーができなかったら、ファイルのコピーを許可していない可能性がある。許可の仕方はスクリプトのすぐ下に記述。

NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"コピーしたいファイル.jpg"];

//こっちの記述でもいい

//NSString *defaultDBPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"db"];

//コピーを実行

BOOL copy_result_flag = [fm copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if(!copy_result_flag){

//失敗したらここ

NSLog(@"コピーに失敗");
}
}

return YES;
}
--------------------------------------------------------------------

コピーするファイルは、あらかじめ許可しておく必要があるので、うまくコピーできない場合は許可されているか確認する。

targets > BuildPhase > Copy Bundle Resources
から適宜該当ファイルを追加すれば解消される。参考

//FMDB使っていて、データファイルをコピーしたときのスクリプトがこれ↓ 参考
--------------------------------------------------------------------
NSError *error;
NSFileManager *fm = [NSFileManager defaultManager];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"test.db"];
NSLog(@"writableDBPath;%@",writableDBPath);

BOOL result_flag = [fm fileExistsAtPath:writableDBPath];
if(!result_flag){
//dbが存在してなかったらここが呼ばれて、作成したDBをコピー
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"test.db"];

BOOL copy_result_flag = [fm copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if(!copy_result_flag){
//失敗したらここ
NSLog(@"データベースのコピーに失敗");
}
}

//作成したテーブルからデータを取得
FMDatabase* db = [FMDatabase databaseWithPath:writableDBPath];
if ([db open]) {
[db setShouldCacheStatements:YES];

FMResultSet *rs = [db executeQuery:@"SELECT * FROM testdb"];
while ([rs next]) {
//ここでデータを展開
NSLog(@"データ展開%d %@", [rs intForColumn:@"id"], [rs stringForColumn:@"title"]);
}
[rs close];
[db close];
}else{
//DBが開けなかったらここ
}
--------------------------------------------------------------------

カスタムセルを使ったTableViewで画像とラベルと並べて表示

TableViewCellに画像とラベルを単純に並べて表示するだけなら、Object libraryからImage view と Label をドラッグ&ドロップするだけでいい。

(↑これをして「Need constraints for. Y position」のようなエラーが出たら、「制約」が設定されていないということなので、Auto Layout を解除するか、シーンの右下にある「Resolve Auto Layout Issues」から「add missing constraints」を実行するといい)

ただし、これだと画像とラベルの高さは同じになる。ラベルに複数行の渡る文字列を表示しようとすると、セルの高さが高くなって、画像の高さまで変わってしまう。

今回自分がやりたいことは、画像の高さはそのままで、ラベルのみ文字列の量に比例して高さを上げるという表示の仕方。

以下はその手順。

1.TableView を使うための下準備をする

1.ストーリーボードで、 Table View を ViewController にドラッグして、プロトコルの実装先である ViewController に dataSource と delegate を設定する。

2.プロトコルを実装する。

ViewController.m (h) 内に記述
------------------------------------------------

//プロトコルを実装
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@end

------------------------------------------------

3.セルの表示以外のプログラムをいろいろ記述する

2.テーブルビューにテーブルビューセルを追加し、ID を設定する

1.ストーリーボードで、テーブルビューに UITableViewCell をドラッグ&ドロップする。

2.追加したセルにセルIDを「myCell」と設定する。

3.追加したセルの中にImage View と Label を追加する

1.Object Library 内になる Image View と Label をセルの中に並べて配置する。各オブジェクトのサイズ調整も行う。

2.それぞれの tag に、「1」「2」と設定する。

3.Label の numberOfLines を「0」にして、複数行表示を有効にする。

4.プログラムを作る

ViewController.m 内に記述
------------------------------------------------
//セクション数
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}

//行数
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}

//表示するセル
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

//セルIDを myCell と設定したセルを使う
NSString *cellIdentifier = @"myCell";

//カスタムセルを準備する
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
}

//tag番号1のImagaViewに画像を設定する
UIImageView *imgView = (UIImageView *)[cell viewWithTag:1];
//Supporting Files に sample.jpg という画像データを入れておく
UIImage *img = [UIImage imageNamed:@"sample.jpg"];
imgView.image = img;

//tag番号2のラベルに文字を設定する
UILabel *label2 = (UILabel *)[cell viewWithTag:2];
label2.text = [NSString stringWithFormat:@"%ld行目", (long)indexPath.row];

//表示する文字列にフィットするようにラベルを伸縮させる
[label2 sizeToFit];

return cell;

}
------------------------------------------------

sizeTOFit を有効にするために、セルの AutoLayout をオフにすること!

5.各行の高さを個別に計算して決める

------------------------------------------------
//各行の高さを決めるメソッド
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

//表示したい文字列。この文字列が表示できるだけ、セルの高さを確保する
NSString * text = [NSString stringWithFormat:@"%ld行目", (long)indexPath.row];

//表示最大幅と高さ
CGSize maxSize = CGSizeMake(200, CGFLOAT_MAX);

//表示するフォントのサイズ
NSDictionary *attr = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:17.0]};

//以上を踏まえた上で、表示に必要なサイズ
CGSize modifiedSize = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size;

//上下10pxずつの余白を加えたものと70pxのうち、大きい方を返す
return MAX(modifiedSize.height + 20, 70);
}
------------------------------------------------

UITableViewAutomaticDimension を使えばもっと簡単にセルの高さは決められるらしい。

以上。

実行して動作を確認。セルの高さが伸縮しない場合は、AutoLayout がオフになっているか確認する。sizeTOFit は、AutoLayout が有効だと機能しないよう。

参考:こちらおよび、iPhoneアプリ開発の教科書 P.364~

【Xcode】TableViewで画像とテキストを並べる

Xcode の TableView で画像とテキストを並べる表示をする Objective-C のスクリプト例
-----------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

//テキスト
cell.textLabel.text = myArray[indexPath.row];

//画像
cell.imageView.image = [UIImage imageNamed:@"画像.gif"];

return cell;
}
-----------------------------------------------------------------------------

参考ページ

【Objective-C】stringWithFormatでエスケープする

stringWithFormatで文字連結するとき、文字列の中に「%」を使う記述の仕方。

記述例
-------------------------------------------
NSString *str1 = @"割引率は";
NSString *str2 = [stringWithFormat:@"@%はひゃく%%",str1];
-------------------------------------------

結果:
割引率はひゃく%

stringWithFormat で「%」を使いたいときは「%%」にする。

【Xcode】AppDelegateで変数を他の画面に渡す

Xcode の AppDelegate は中央センターの役割をするので、AppDelegate 内で作った変数はどのクラスからもアクセスできる。

以下は AppDelegate の設定の手順。Single View Application でつくったものとする。

1.AppDelegate.h に全てのクラスから参照できる変数を設定する

//AppDelegate.h 内に記述
-----------------------------------------
//全てのクラスから参照できる変数
@property (nonatmic, weak) NSString *shareString;
-----------------------------------------

2.ViewController.h から AppDelegate.h をインポートする

//ViewController.h 内に記述
-----------------------------------------
//インポート
#import "AppDelegate.h"
-----------------------------------------

3.ViewController から AppDelegate 内の変数を参照する

//ViewController.m 内の viewDidLoad に記述
-----------------------------------------
// AppDelegateのオブジェクトを取得する
AppDelegate *AD = (AppDelegate *)[[UIApplication sharedApplication] delegate];

//変数に文字列を代入する
AD.shareString = @"代入する文字列";
-----------------------------------------

ラベルに表示したい場合

//ViewController.m 内の interface に記述
-----------------------------------------
//変数 myString を宣言する
NSString myString
-----------------------------------------

つづいて、

//ViewController.m 内の viewDidLoad に記述
-----------------------------------------
//変数に文字列を代入する
AD.shareString = @"表示したい文字列";

myString = AD.shareString;

//ラベル(myLabelと命名)に表示
[self.myLabel setText:myString];
-----------------------------------------

【Xcode】master detail applicationの画面遷移をSingle View Applicationテンプレートから作る

「master detail application テンプレート」を使うと作れる画面遷移を「Single View Applicationテンプレート」から作るときの覚書。

参考ページ:こちら

新しいプロジェクトをつくるとき「Single View Applicationテンプレート」を選択すると、最初に storyboard に View Controller シーンがひとつできている。

このシーンを「画面1」と名づける。Object library からもうひとつ View Controller をドラッグして作り「画面2」と名づける。「画面1」に設置したボタンをタップすると「画面2」に遷移する仕組みをつくる。

手順。

1.Navigation Controller シーンを設置する

Object library から Navigation Controller をドラッグして設置する。Navigation Controller を選択して「Is initial View Controller」にチェックを入れる。すると、いままで View Controller の左横にあった矢印(→)が、Navigation Controller の横に移る。

新しく設置した Navigation Controller には Table View が勝手にくっついてくるが、今回は Single View Applicationテンプレート を選択したときにできた View Controller を使用したいので、Table View は delete する。

2.Navigation Controller シーンと View Controller シーンを、Relationshipセグエでつなぐ

設置した Navigation Controllerシーン は最初、Table View と Relationshipセグエでつながっている。これを View Controller シーンにつなぎかえるため、Navigation Controllerシーンを右クリック、もしくは Connections inspector を開く。「Triggered Segues」の「root view controller」に関連付けられている Table View Controller を解除して、View Contoroller につなぎ直す。

3.View Controller(画面1)にボタンを設置する

View Controller シーン(画面1)に、Object library からドラッグした Button を設置する。

4.View Controller(画面2)を設置する

ボタンを押したときの遷移先になる、View Controller シーン(画面2)を設置する。Object library から View Controller を選択してドラッグする。分かりやすく、label で「画面2」とでも書いておく。

新しく設置した View Controller シーン(画面2)には、ViewController.h(m)などのファイルがまだないので追加する。「File」-「New」-「File」-「Cocoa Touch Class」で「NextViewClass」などとそれっぽい名前をつける。そのあと、Storyboardでできたファイルと関連付けをおこなう。「identity inspector」の「Custom Class」から先ほどつけた名前を選択。

詳細はこちら

5.「画面1のボタン」とView Controller(画面2)をPush セグエでつなぐ

「画面1のボタン」を右クリック、および Connections Inspector にある Triggered Segues 項目の action を View Controller シーン(画面2)にドラッグする。選択項目が出てくるので「push(deprecated)」を選択すると、Push セグエで関連付けされる。

プログラムをビルドして実行して、動作に問題が無いか確認する。

画面が真っ暗で何も表示されない場合は、Navigation Controller に「Is initial View Controller」にチェックが入っている確認する。

【Objective-C】配列の要素数を数える

【Objective-C】配列の要素数を数える記述

[配列名 count]

もしくは

配列名.count