iOS – 画面を開いたときの初期フォーカスの設定方法

画面を開いたときにフォーカスを設定したい画面を開いた時点でテキストフィールドにフォーカスを設定するには、そのテキストフィールドをファーストレスポンダに設定すればいいだけです。

テキストフィールドをファーストレスポンダに設定するためにはbecomeFirstResponderメソッドを使用します。

さて、テキストフィールドをファーストレスポンダに指定するタイミングですが、画面描画が完了した後に実行されるviewDidLoadメソッドがその処理を記述するのに適しています。

以下にサンプルコードを載せておきます。

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 最初からテキストフィールドにフォーカスをあわせておく
    [self.mainTextField becomeFirstResponder];
}

 

 

 

iOS – UITableViewで論理削除をする実行する例

UITableViewにおける削除UITableViewにはデフォルトでデータの編集機能が備わっており、CoreDateフレームワークと組み合わせて使用するときはNSFetchedResultsControllerDelegateプロトコルを当該ViewControllerに採用して制御するのが一般的です。

データを物理削除する場合は他のサイトでいろいろと紹介されていますが、ここではデータを論理削除する場合の例をメモとして残しておきます。

 

論理削除処理をどこに書くか
UITableViewの「編集」ボタンをタップして削除したいデータを選択し、「削除」ボタンをタップすると(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPathメソッドが呼び出されます。そのため、この中に論理削除の処理を書けばよいわけです。以下がそのサンプルコードです。

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        Unit *unit = [self.fetchedResultsController objectAtIndexPath:indexPath];
        unit.del_flg = @"1";

        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            return;
        }
    }
}

上記のコードではUnitというManagedObjectのdel_flg属性を"1"を設定することで論理削除を行なっています。ポイントはユーザによって削除処理が行わたかどうかをeditingStyle変数を調べて判断しているところです。

 

削除されたデータを非表示にするにはどうするか
上記のコードでデータ自体は論理削除したのですが、それを画面に反映させる必要があります。ManagedObjectに変更があるとNSFetchedResultsControllerDelegateプロトコルのデリゲートメソッドである(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType (NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPathが呼び出されます。これを利用しましょう。以下がよく見られる実装例です。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
    }
}

上記コードの13行目がポイントです。テーブルビューに対してdeleteRowsAtIndexPathsメソッドを実行し、テーブルビューの見た目から削除対象の行を削除しています。

 

あ、あとNSFetchedResultsControllerのインスタンス作成時に論理削除されたものを抽出しないように検索条件を設定するのを忘れずに。一応、サンプルコードを載せておきます。

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Unit" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    [fetchRequest setFetchBatchSize:20];

    // 検索条件を指定。削除フラグがたっていないもののみ取得する
    NSString *searchCondition = [NSString stringWithFormat:@"del_flg MATCHES '0'"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:searchCondition];
    [fetchRequest setPredicate:predicate];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

	NSError *error = nil;
	if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
	    return  nil;
	}

    return _fetchedResultsController;
}

 

以上になります。

 

環境

XCode 4.6

 

iOS – 数値を三桁カンマ区切りの文字列にする方法

数値を3桁ごとにカンマで区切るためにはNSNumberFormatterクラスを使い、このフォーマッタークラスにNSNumberFormatterDecimalStyleというスタイルを適用した上で、数値を文字列に変換します。

以下、そのサンプルコードです。

NSNumber *number = ...;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSString *string = [formatter stringFromNumber:number];

 

 

iOS – CoreDataにおける検索条件の指定の仕方

CoreDataフレームワークを使用してデータを扱う場合、NSFetchRequestクラスを使用してローカルDBからデータを読み出します。この際に抽出条件を指定するのがNSPredicateクラスです。このNSPredicateクラスのインスタンスは文字列を引数とし以下のように生成することができます。

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = '田村倉庫'"];
[fetchRequest setPredicate:predicate];

 

このメモでは抽出条件の指定する文字列中に記述する演算子について解説します。NOTの使い方などは一般のSQLとは異なるため、注意が必要です。

演算子 使い方 解説
= A = B AとBが等しいものを抽出
MATCHES A MATCHES B 正規表現を使ってAとBが等しいものを抽出
NOT NOT(A = B) AとBが等しくないものを抽出
カッコで囲むところがポイント
CONTAINS A CONTAINS B AにBが含まれているものを抽出
[c]オプション - 大文字小文字を区別しない
[d]オプション - 発音記号を区別しない

 

 

iOS – UIButtonのタイトルを動的に変える方法

UIButtonのタイトルを動的に変えるにはsetTitleメソッドを使用します。以下はボタンのタイトルを"田村倉庫"に変える例です。

// ※buttonはUIButtonのオブジェクト
[button setTitle:@"田村倉庫" forState:UIControlStateNormal];

尚、以下のようにしてボタンのタイトルを変えても設定内容が元に戻ってしまうので上記のメソッドを使う必要があります。

// ※buttonはUIButtonのオブジェクト
button.titleLabel.text = @"田村倉庫";

 

また、タイトルの文字列の配置を左寄せや右寄せに変える場合にはUIButtonオブジェクトのcontentHorizontalAlignmentプロパティに値を設定する必要があります。

// 左寄せ
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
// 右寄せ
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;

 

 

iOS – Navigation itemの「戻る」ボタンの表示を変更する方法

Snip20130221_2

ナビゲーションバー上のボタンに表示される「戻る」ボタンのラベル名を変更する方法を紹介します。

ポイントは、push元のNavigation itemにラベル名を設定することです。

下図のような「新規作成」画面と「カテゴリ」画面がある場合を例に取って解説します。カテゴリ画面へは新規作成画面からpush方式で画面遷移をするとします。

Snip20130221_3

上図のようにセグエを設定した場合は、デフォルトで遷移元の画面に戻るためのボタンが表示されるのですが、そのボタンのラベル名が遷移元の画面のタイトルになっているとおもいます。そのボタンのタイトルを変更する場合、Navigation item(ナビゲーションバー)を選択して、画面右側のAttributes inspectorに表示される「Back Button」にボタンに表示したい文字列を入力すればよいのですが、上記のカテゴリ画面のNavigation itemのBack Buttonに値を設定しても反映されません。カテゴリ画面ではなく、新規作成画面、つまり遷移元の親画面のBack Buttonにその値を設定することで戻るボタンにその値が表示されます

iOS – Core Dataで多対多のデータを扱う

データモデルで多対多の関係を扱うことはけっこう多かったりします。代表的なのが「ブログ記事」エンティティと「タグ」エンティティの関係がそれですね。ブログ記事は複数あり、またその記事には複数のタグが付けられ、かつタグは複数のブログ記事の間で重複して指定できるのでまさに多対多の関係にあるわけです。

今回はメモはそんな多対多のデータをCore Dataフレームワークで扱う方法についてのメモです。Core Dataを使うと多対多のデータ構造が非常に簡単に扱えます。やり方さえ一度覚えてしまえば後は楽ちんです。

 

データモデル

many_to_many_relation

左図に示すように、ブログ記事を表す「Blog」エンティティと、そのタグを表す「Tag」エンティティがある場合を例にとって解説します。

 

上記の2つのエンティティはそれぞれリレーションを持っています。BlogエンティティはTagエンティティの要素を複数持つことを表すリレーション「tags」を持ち、 同様にTagエンティティはBlogエンティティの複数要素を持つことを表すリレーション「blogs」を持っています。複数要素(多対)の指定は、下図のように、「To-Many Relationship」にチェックを入れることで指定できます。ポイントはBlogエンティティ、Tagエンティティの双方から互いに多対リレーションを設定するところです。

tags_relation

blogs_relation

 

BlogエンティティのManagedObjectサブクラスの作成

メニューから「File」→「New」→「File...」を選択し、下図のようにCore Dataのの「NSManagedObject subclass」を選択してBlogエンティティを扱うBlogクラスを作成します。この作業は上記のデータモデルのところでリレーションを設定した後にやってください。そうじゃないと自動生成されるソースを後で手直しすることになってしまいます。

ManagedObjectSubclassCreation

Blogクラスが自動生成されると、以下のようになっているはずです。

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Blog : NSManagedObject

@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSSet *tags;
@end

@interface Blog (CoreDataGeneratedAccessors)

- (void)addTagsObject:(NSManagedObject *)value;
- (void)removeTagsObject:(NSManagedObject *)value;
- (void)addTags:(NSSet *)values;
- (void)removeTags:(NSSet *)values;

@end

上記のコードを見てみるとプロパティとしてtagsを持っています。このtagsを用いることでBlogオブジェクトに紐づくTagオブジェクトを参照できます。またaddTagsObjectメソッドなども生成されていることがわかります。

 

データの登録

ここまで来ればなんとなく予想がついているとは思いますが、あるBlogオブジェクトにTagオブジェクトを追加する(紐付ける)場合には以下のようなコードになります。

// blogObjはBlogエンティティに対応するNSManagedObjectのサブクラス
// tagObjはTagエンティティに対応するNSManagedObjectのサブクラス
[blogObj addTagsObject tagObj];

 

データの参照

あるBlogオブジェクトに紐付けられたTagオブジェクを参照したい場合は、Blogオブジェクトのtagsプロパティにアクセスします。例えば、あるBlogオブジェクトにどんなTagオブジェクトが紐付いているかログでみたいときには以下のようなコードになります。

for (Tag *tagObj in blogObj.tags) {
  NSLog(@"%@", tagObj.name);
}

解説は以上となります。

いや〜しかしCore Dataよくできてますね。最初さわったときは「なんだこれ?記述量多すぎ!わけわからん!」てな感じでしたが、リファレンスを読めば読むほど「こんなことまで自動でやってくれるんだ!すごい!」という感想に変わりつつあります。

 

環境
XCode 4.6

 

iOS – NSSetの要素へのアクセス方法

NSSetは「集合」を扱うクラスです。そのNSSetオブジェクトに格納された要素にアクセスする方法のメモです。

いくつかやり方があるようですが、すべての要素にループでアクセスする場合は以下のようにNSEnumeratorを使うとスッキリ書けます。

NSSet *categories = [NSSet setWithObjects:@"cat", @"dog", nil];

NSEnumerator *enumerator = [categories objectEnumerator];
NSString *category;
while (category = [enumerator nextObject]) {
  NSLog(@"%@", category);
}

 

(以下、2013/03/10に追記)

上記の方法よりもいい方法がありました。NSSetクラスはNSFastEnumerationプロトコルに準拠しているので、以下のように拡張For文が使えます。

NSSet *categories = [NSSet setWithObjects:@"cat", @"dog", nil];

for (NSString *category in categories) {
    NSLog(@"%@", category);
}

こっちのほうがずっとスッキリしてますね。お恥ずかしい。。

 

iOS – アクションシートの実装方法

ActionSheetの例アクションシート(ActionSheet)は左図のように削除処理などの重要な処理を行う前にユーザーに確認を促すために使われることが多いと思います。このメモではそのアクションシートの実装方法について紹介します。

方法
まず以下のhファイルから見ていきます。ポイントはUIActionSheetDelegateプロトコルの実装を宣言いるところです。このプロトコルのデリゲートメソッドを当該クラスに実装し、アクションシートのデリゲート先をそのクラスに指定すれば、アクションシートのイベントをそのクラスで処理することができます。

@interface TWViewController : UIViewController <UIActionSheetDelegate> {
    // 削除確認用のアクションシート
    UIActionSheet *_deleteConfrimActionSheet;
}

 

次にmファイルを見てみます。アクションシートのインスタンスを作成するときにアクションシートのタイトル、表示するボタンを設定しています。otherButtonTitles引数にカンマ区切りでボタン名を指定すればアクションシート上のボタンを増やしていくことができます。(3行目)

デリゲートメソッドは2つあり、上から1つ目のデリゲートメソッド(8行目)のbuttonIndex引数に押されたボタンの番号が渡されてきます。このボタン番号はアクションシートの上から0, 1, 2, ...と採番されています。なので、一番上のボタンが押されたときは"0"が渡されてくるわけです。このボタン番号を見て処理を分岐することができます。

2つ目のデリゲートメソッド(17行目)はアクションシートのキャンセルボタンが押されたときに呼び出されます。

// 削除ボタンが押下されたときに行われる処理
- (IBAction)deleteComfirm:(id)sender {
    _deleteConfrimActionSheet = [[UIActionSheet alloc] initWithTitle:@"この在庫データを削除してよろしいですか?" delegate:self cancelButtonTitle:@"キャンセル" destructiveButtonTitle:@"削除" otherButtonTitles:nil];
    [_deleteConfrimActionSheet showInView:self.view];
}

// アクションシート上のボタンが押下されたときに呼び出されるデリゲートメソッド
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 0) {
        // 「削除」ボタンが押された場合には削除処理を実行する
        [self deleteItem];
    }
}

// アクションシート上のキャンセルボタンが押下されたときに呼び出されるデリゲートメソッド
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    _deleteConfrimActionSheet = nil;
}

 

ハマったところ
あるときアクションシートの一番下のキャンセルボタンを押しても反応しないという現象に遭遇しました。 調べてみたところ、どうやらタブ(UITabBarController)がある場合にこの現象が発生するみたいです。この場合は、上記のソースコードの4行目を以下のように変えると解決します。

[_deleteConfrimActionSheet showInView:self.view.window];

viewではなく、window上にアクションシートを表示することでタブとの衝突を回避できるみたいです。

 

あー早く冬休みにならないかなぁ。。