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上にアクションシートを表示することでタブとの衝突を回避できるみたいです。

 

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

iOS – タブを切り替えたときに処理を実行する

iPhoneのタブの切替

UITabBarControllerwを使用することでタブ機能を実現することができると思いますが、このメモではタブを切り替えたタイミングで処理を行う方法について紹介します。

…ただ、この方法がベストプラクティスとは思えないので、もっといい方法があれば是非教えてくださいm(__)m

 

方法
以下の.hファイルのソースのように、UITabBarControllerwを継承した新しいクラスを作成します。また、このクラスにUITabBarControllerDelegateプロトコルに対応していることを宣言します。

// TWTabBarController.h
@interface TWTabBarController : UITabBarController<UITabBarControllerDelegate>@end

.mファイルにはUITabBarControllerDelegateプロトコルのデリゲート先を新しく作成したクラスに指定し、そのデリゲートメソッドを実装します。このデリゲートメソッド内の処理がタブ切替時に呼び出される処理になります。

// TWTabBarController.m
@implementation TWTabBarController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // デリゲート先を本クラスに指定
    self.delegate = self;
}

// タブが切替られたときに呼び出されるデリゲートメソッド
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    // ここに記述された処理がタブの切替時に呼び出される
}

最後に、このTabControllerクラスをStoryboard上のUITabBarControllerwと紐付けます。Storyboardを開いてTab Bar Controllerを選択し、Custom Classに新たに作成したクラス名を設定します。
Custom Classに新たに作成したTabBarControllerクラスを指定

 

まとめ

  • UITabBarControllerDelegateプロトコルを実装し、そのデリゲートメソッド内にタブ切替時に行いたい処理を記述する

 

iOS – ソフトウェアキーボードを非表示にする(応用編)

概要
テキストフィールドをタップしたときに、下からにゅると表示されるソフトウェアキーボード。入力中のテキストフィールド以外の場所がタップされたときにそのソフトウェアキーボードを非表示にする方法を紹介します。そんなに難しくないです。簡単です。

ポイント

  • iOSのソフトウェアキーボードは入力中のテキストフィールドやテキストエリアに対して、resignFirstResponderメソッドを実行することで非表示にすることができます。
  • テキストフィールド以外の場所がタップされたどうかは透明なボタンを使って検知します。

方法
下図のようにテキストフィールドやラベル、ボタンがある場合で解説します。
想定する状況

 

これに加えて、画面と同じ大きさの透明なボタンを用意します。ボタンの透明化はボタンのTypeをCustomにするこで実現できます。
透明なボタンを追加

 

さらにこの透明ボタンが最下層に配置されるように、ボタンを選択した状態でメニューバーから「Editor」→「Arrange」→「Send to Back」を選択して並び替えを行います。こうすることで既存のテキストフィールドや保存ボタンがタップで選択できるようになり、それ以外の場所がタップされたときにこの透明ボタンが反応するようになります。
ボタンを最下層に配置する

 

次にhideKeyboardメソッドを作成し(メソッド名はなんでもいいです)、そのメソッドが透明なボタンがタップされたときに呼び出されるようにイベント登録をします。この作業は下図のようにInterface Builder上でボタンをControlキーを押しながら当該ViewControllerの@interfaceブロックにドラック&ドロップすることで簡単に完了します。
Interface Builderからドラッグ&ドロップでイベント登録

 

最後に追加されたhideKeyboardメソッドを以下のように実装します。ファーストレスポンダに対してresignFirstResponderメソッドを実行する、という基本は変わりません。ただ、どのオブジェクトがFirstResponderになっているかわからないので、当該ビューにあるすべてのオブジェクトにFirstResponderであるか問い合わせをして、FirstResponderであればresignFirstResponderメソッドを実行しています。

- (IBAction)hideKeyboard:(id)sender {

    // ファーストレスポンダになっているものをしらみつぶしに探して、ファーストレスポンダをやめさせることで
    // ソフトウェアキーボードを非表示にする
    for (UIView *view in [self.view subviews]){
		if ([view isFirstResponder] ) {
			[view resignFirstResponder];
		}
	}
}

 

まとめ

  1. 透明なボタンを追加する
  2. 透明なボタンを最下層に配置する
  3. 透明なボタンがタップされたときにメソッドが呼び出されるようにする
  4. 呼び出されたメソッド内でFirstResponderを探し当て、 resignFirstResponderを実行する

環境
iOS5 & iOS6
XCode 4.5.2

関連項目
iOS – ソフトウェアキーボードを非表示にする(基本編) - returnキーや改行キーが押されたときにソフトウェアキーボードを非表示にする方法を解説

iOS – ソフトウェアキーボードを非表示にする(基本編)

概要
テキストフィールドをタップしたときに、下からにゅると表示されるソフトウェアキーボード。このソフトウェアキーボードのreturnキーや改行キーを押したときに非表示にする方法を紹介します。

ポイント
iOSのソフトウェアキーボードは入力中のテキストフィールドやテキストエリアに対して、resignFirstResponderメソッドを実行することで非表示にすることができます。

方法
UITextFieldDelegateプロトコルに対応していることを.hファイルに記述します。

@interface HunaViewController : UIViewController <UITextFieldDelegate>

 

次に、テキストフィールドのアウトレットをtitleTextFieldとして作成します。そしてこの画面のViewControllerのviewDidLoadメソッドにおいて以下のようにtileTextFieldアウトレットのデリゲート先をそのViewControllerに指定します。また、デリゲートメソッドであるtextFieldShouldReturnメソッドを実装し、その中でresignFirstResponderを実行します。

- (void)viewDidLoad
{
    [super viewDidLoad];

    // デリゲート先を設定
    [self.titleTextField setDelegate:self];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    // ソフトウェアキーボードを閉じる
    [textField resignFirstResponder];

    return YES;
}

textFieldShouldReturnメソッドはソフトウェアキーボードのreturnキーや改行キーが押されたときに呼び出されます。

まとめ

  1. テキストフィールドのデリゲート先を指定する
  2. 指定したデリゲート先にtextFieldShouldReturnメソッドを記述し、その中でresignFirstResponderメソッドを呼び出す

環境
iOS5 & iOS6
XCode 4.5.2

関連項目
iOS – ソフトウェアキーボードを非表示にする(応用編) - テキストフィールド以外の場所がタップされたときにソフトウェアキーボードを非表示にする方法を解説