iOS – CoreDataにおける検索条件のエスケープ

CoreDataで以下のコードでtargetNameに該当するデータを検索する場合、targetNameにシングルクォート「'」が含まれていると、シングルクォートの「囲み」の整合性が取れなくなるので、アプリが落ちます。

NSString *searchCondition = [NSString stringWithFormat:@"name = '%@'", targetName];
NSPredicate *predicate = [NSPredicate predicateWithFormat:searchCondition];
[fetchRequest setPredicate:predicate];

このような場合はtargetNameに含まれているシングルクォートを以下のようにして事前にエスケープしておけば大丈夫です。

targetName = [targetName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]

 

 

Objective-Cでよく使うけと忘れてしまいがちなテクニックたち

このメモにはObjective-Cでよく(?)使うけど毎回忘れてしまい、Google検索するところから始めなければならないようなテクニックたちを載せておきます。

 

文字列を置換する

NSStringのstringByReplacingOccurrencesOfStringメソッドを使います。

NSString *result = [targetString stringByReplacingOccurrencesOfString:@"置換対象文字列" withString:"新しく置き換える文字列"];

 

文字列を結合する

いろいろやり方があります。まずは一番単純なものを。

NSString newString = [stringA stringByAppendingString:stringB];

 

double型を文字列に変換する

一度NSNumberにするところがポイントです。

double d = 1.234;
NSString *str = [[NSNumber numberWithDouble:d] stringValue];

 

NSFetchedResultsControllerによってフェッチされたNSManagedObjectを取得する

NSArray *objects = [self.fetchedResultsController fetchedObjects];

 

配列の最後の要素を取得する

NSArray *objects = @[@"A", @"B", @"C"];
NSString *last = [objects lastObject];

 

 

 

iOS – カメラデバイスが存在するか判定する方法

iPod touchは機種によってはカメラがないものもあります。そのため、カメラを使うアプリの場合にはカメラが搭載されているか判定する必要があります。

この判定はとても簡単にできます。以下のような分岐を入れるだけです。

if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) {
  // カメラがある場合
}
else {
  // カメラがない場合
}

 

環境

XCode 4.6.2

 

iOS – UITextFieldやUITextViewで入力文字数を制限する方法

入力制限

UITextFieldやUITextViewで入力できる最大文字数を指定する方法を紹介します。

UITextFieldの場合

UITextFieldのshouldChangeCharactersInRangeデリゲートメソッドを利用します。このメソッドはファーストレスポンダになっているUITextFieldに1文字でも入力があれば呼び出されます。このメソッド内において入力された文字が最大文字数を超えていないかチェックします。

尚、お決まりですが、このデリゲートメソッドを使用するためにはUITextFieldDelegateプロトコルを当該ViewControllerで採用し、デリゲート先をそのViewControllerにする必要があります。

実装例は以下の通りです。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{    
    // 最大入力文字数
    int maxInputLength = 200;

    // 入力済みのテキストを取得
    NSMutableString *str = [textField.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:string];

    if ([str length] > maxInputLength) {
        // ※ここに文字数制限を超えたことを通知する処理を追加

        return NO;
    }

    return YES;
}

上記のコードを解説します。まず、7行目でファーストレスポンダになっているUITextFieldから既に入力済みの文字列を取得します。これにはこのデリゲートが呼び出されるきっかけとなった入力は含まれていません。きっかけとなった入力はreplacementStringに格納されています。

10〜12行目の部分では、これら2つの文字列を結合し、その文字列の長さが許容されている範囲を超えていないかチェックして、返す戻り値を変えています。このデリゲートメソッドがYESを返せば直前の入力がUITextFieldに反映されますが、NOを返した場合は反映されません

 

UITextViewの場合

UITextFieldの場合とほとんど同じですが、デリゲートメソッド名や採用するプロトコルが異なります。UITextViewではshouldChangeTextInRangeデリゲートメソッドを利用し、当該ViewControllerではUITextViewDelegateプロトコルを採用します。

以下、実装例です。

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    int maxInputLength = 2000;

    // 入力済みのテキストを取得
    NSMutableString *str = [textView.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:text];

    if ([str length] > maxInputLength) {
        return NO;
    }

    return YES;
}

 

複数のUITextFieldがある場合はどうするか tag_value_on_attribute_inspector

UITextFieldが複数あり、それぞれ最大入力文字数が異なる場合はUITextFieldに異なるtag値を設定し、そのtag値でどのUITextFieldなのかを判定して処理を行うのが簡単だと思います。tag値は右図のようにStoryboard上のAttribute Inspectorで編集できます。

以下に実装例を載せます。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    // 最大入力文字数
    int maxInputLength = 50;
    switch (textField.tag) {
        case 1: // 名前
            maxInputLength = 50;
            break;
        case 2: // 住所
            maxInputLength = 200;
            break;
        case 3: // 年齢
            maxInputLength = 3;
            break;

        default:
            break;
    }

    // 入力済みのテキストを取得
    NSMutableString *str = [textField.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:string];

    if ([str length] > maxInputLength) {
        return NO;
    }

    return YES;
}

上記のコードは「名前」「住所」「年齢」の3つのUITextFieldにそれぞれtag値として1, 2, 3を割り振った場合の実装例です。

 

環境

XCode 4.6.1

 

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

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

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

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

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

- (void)viewDidLoad
{
    [super viewDidLoad];

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

 

 

 

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

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

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

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

 

 

iOS – UIPickerViewを下から動的に表示する方法

UIPickerViewの実装方法はすでにネット上の様々な記事で紹介されていると思いますが、このメモではStoryboardとARCを使用している環境でのUIPickerViewの実装方法をサンプルプログラムを通して紹介します。

サンプルプログラムの動作イメージは以下の動画をご覧ください。

 

このメモに登場するサンプルプログラムのソースは以下からダウンロードできます。
[サンプルプログラムのソース]

 

1.Picker View用のView Controllerを用意する
pickerviewcontrollerStoryboard上でView Controllerを新たに追加し、左図のように、Picker View(参照名:picker)とボタン(参照名:closeButton)を追加します。

そしてPickerViewControllerというクラスファイルを新規作成し、このView Controllerに関連付けます。関連付けはStoryboard上でView Controllerを選択し、右に表示されているIdentity inspectorのCustom ClassにPickerViewControllerと入力することで行えます。

このPickerViewControllerはモーダルで表示します。このとき、closeButtonが配置されている画面上部を透明化して呼び出し元の親画面が見えるようにしたいと思います。そのために以下の2つの設定を行います。

1.closeButtonのTypeを"Custom"にする

2.このViewのBackgroundを"Default"(透明)にする

これらはどちらもStoryboard上のinspectorの部分で行えます。注意点は背景を透明化する場合、ViewのAlphaでなく、Backgroundに設定する値を変えるという点です。Alpha値を小さくするとそのViewに載っているcloseButtonやpickerまでも透明化されてしまいます。

set_storyboard_id最後にPickerViewControllerにStoryboard IDを設定します。これも右図のように、Identity inspectorから設定できます。値は"PickerViewController"にしておきましょう。このIDは後でこの画面の呼び出し元からPickerViewControllerの参照を取得する際に使用します。

これでPickerViewControllerに対するStoryboard上での作業は完了です。

 

2.PickerViewControllerを実装する
PickerViewController.hをソースを以下のようにします。

#import <UIKit/UIKit.h>

@protocol PickerViewControllerDelegate;

@interface PickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource>

@property (weak, nonatomic) IBOutlet UIPickerView *picker;

// 空の領域にある透明なボタン
@property (weak, nonatomic) IBOutlet UIButton *closeButton;

// 処理のデリゲート先の参照
@property (weak, nonatomic) id<PickerViewControllerDelegate> delegate;

// PickerViewを閉じる処理を行うメソッド。closeButtonが押下されたときに呼び出される
- (IBAction)closePickerView:(id)sender;

@end

@protocol PickerViewControllerDelegate <NSObject>
// 選択された文字列を適用するためのデリゲートメソッド
-(void)applySelectedString:(NSString *)str;
// 当該PickerViewを閉じるためのデリゲートメソッド
-(void)closePickerView:(PickerViewController *)controller;
@end

上記のソースを簡単に解説します。まず、PickerViewControllerクラスにUIPickerViewDelegateプロトコルUIPickerViewDataSourceプロトコルを採用しています。このプロトコルに定義されているデリゲートメソッドを本クラスで実装することで、Pickerから送られるイベントを本クラスで処理できるようになります。これらについてはPickerViewController.mファイルで詳しく解説します。

またPickerViewControllerDelegateプロトコルを定義しました。このプロトコルを後述する呼び出し元のMainViewControllerに採用させ、PickerViewで選択した値の反映や、PickerViewを非表示にする処理を行わせます。

そして、closePickerViewメソッドを定義しました。closeButtonが押下されたときにこのメソッドが呼び出されるようにしてください。

 

次が PickerViewController.mファイルです。

#import "PickerViewController.h"

@interface PickerViewController ()

@end

@implementation PickerViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // PickerViewのデリゲート先とデータソースをこのクラスに設定
    self.picker.delegate = self;
    self.picker.dataSource = self;
}

// PickerViewで要素が選択されたときに呼び出されるメソッド
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    // デリゲート先の処理を呼び出し、選択された文字列を親Viewに表示させる
    [self.delegate applySelectedString:[NSString stringWithFormat:@"%d", row]];
}

// PickerViewの列数を指定するメソッド
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView {
    return 1;
}

// PickerViewに表示する行数を指定するメソッド
-(NSInteger)pickerView:(UIPickerView*)pickerView numberOfRowsInComponent:(NSInteger)component {
    return 10;
}

// PickerViewの各行に表示する文字列を指定するメソッド
-(NSString*)pickerView:(UIPickerView*)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    return [NSString stringWithFormat:@"%d", row];
}

// 空の領域にある透明なボタンがタップされたときに呼び出されるメソッド
- (IBAction)closePickerView:(id)sender {
    // PickerViewを閉じるための処理を呼び出す
    [self.delegate closePickerView:self];
}

@end

上記のソースでは、UIPickerViewDelegateプロトコルとUIPickerViewDataSourceプロトコルのデリゲート先をPickerViewControllerクラスに設定し、それらのデリゲートメソッドを実装しています。

また、PickerViewの行が選択されたときには、その文字列を引数としてPickerViewControllerDelegateプロトコルのデリゲートメソッドを実行します。このデリゲートメソッドの実装はこのViewの呼び出し元である親のViewController上に実装します。これについては後述します。

最後に透明なボタンがタップされた場合の処理をclosePickerViewメソッドに記述しています。ここでもこのViewの呼び出し元である親のViewControllerで実装するデリゲートメソッドを呼び出しています。

 

3.PickerViewControllerを呼び出すMainViewControllerを用意する
mainviewcontroller左図のようなアプリ起動後に最初に表示されるViewを制御するMainViewControllerを作成します。

MainViewController上の「選択」ボタンをタップするとPickerViewControllerが呼び出され、PickerViewが表示されるようにします。また、PickerView上で文字列が選択された場合はその文字列をMainViewController上のラベルに表示するようにします。

 

 

4.MainViewControllerを実装する
MainViewController.hのソースです。

#import <UIKit/UIKit.h>
#import "PickerViewController.h"

@interface MainViewController : UIViewController <PickerViewControllerDelegate>

// 「選択」ボタン
@property (weak, nonatomic) IBOutlet UIButton *selectButton;
// PickerViewで選択された文字列を表示するラベル
@property (weak, nonatomic) IBOutlet UILabel *selectedStringLabel;

// 呼び出すPickerViewControllerのポインタ ※strongを指定してポインタを掴んでおかないと解放されてしまう
@property (strong, nonatomic) PickerViewController *pickerViewController;

// 「選択」ボタンがタップされたときに呼び出されるメソッド
- (IBAction)openPickerView:(id)sender;

@end

まずポイントとなるのがPickerViewController.hで宣言したPickerViewControllerDelegateプロトコルを採用しているところです。MainViewControllerクラスでこのプロトコルのデリゲートメソッドを実装し、PickerViewController上のイベント処理するわけです。

次にポイントとなるのが、PickerViewControllerのポインタをプロパティとして定義しているところです。プロパティ属性としてweakではなく、strongを指定しています。これはARCを使用しており、PickerViewControllerのインスタンスへの参照をどこかで「掴んで」いないとPickerViewControllerのインスタンスが勝手に解放されてしまうからです。(たぶん。このあたりの理解は実は曖昧です。。)

 

次がMainViewController.mのソースです。

#import "MainViewController.h"
#import "TWAppDelegate.h"

@interface MainViewController ()

@end

@implementation MainViewController

// 「選択」ボタンがタップされたときに呼び出されるメソッド
- (IBAction)openPickerView:(id)sender {
    // PickerViewControllerのインスタンスをStoryboardから取得し
    self.pickerViewController = [[self storyboard] instantiateViewControllerWithIdentifier:@"PickerViewController"];
    self.pickerViewController.delegate = self;

    // PickerViewをサブビューとして表示する
    // 表示するときはアニメーションをつけて下から上にゆっくり表示させる

    // アニメーション完了時のPickerViewの位置を計算
    UIView *pickerView = self.pickerViewController.view;
    CGPoint middleCenter = pickerView.center;

    // アニメーション開始時のPickerViewの位置を計算
    UIWindow* mainWindow = (((TWAppDelegate*) [UIApplication sharedApplication].delegate).window);
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);
    pickerView.center = offScreenCenter;

    [mainWindow addSubview:pickerView];

    // アニメーションを使ってPickerViewをアニメーション完了時の位置に表示されるようにする
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5];
    pickerView.center = middleCenter;
    [UIView commitAnimations];
}

// PickerViewのある行が選択されたときに呼び出されるPickerViewControllerDelegateプロトコルのデリゲートメソッド
- (void)applySelectedString:(NSString *)str
{
    self.selectedStringLabel.text = str;
}

// PickerViewController上にある透明ボタンがタップされたときに呼び出されるPickerViewControllerDelegateプロトコルのデリゲートメソッド
- (void)closePickerView:(PickerViewController *)controller
{
    // PickerViewをアニメーションを使ってゆっくり非表示にする
    UIView *pickerView = controller.view;

    // アニメーション完了時のPickerViewの位置を計算
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);

    [UIView beginAnimations:nil context:(void *)pickerView];
    [UIView setAnimationDuration:0.3];
    [UIView setAnimationDelegate:self];
    // アニメーション終了時に呼び出す処理を設定
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
    pickerView.center = offScreenCenter;
    [UIView commitAnimations];
}

// 単位のPickerViewを閉じるアニメーションが終了したときに呼び出されるメソッド
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
    // PickerViewをサブビューから削除
    UIView *pickerView = (__bridge UIView *)context;
    [pickerView removeFromSuperview];
}

@end

まずopenPickerViewメソッドですが、一番のポイントはinstantiateViewControllerWithIdentifierメソッドを使用してStoryboard IDからPickerViewControllerのインスタンスへの参照を取得しているところです。ここで、[[PickerViewController alloc] init]などとしてしまうとPickerViewやボタンがない画面が表示されてしまうのでご注意を。
そして、PickerViewControllerのViewをメインスクリーンのサブビューとし、アニメーションを使って下から上に表示されるようにしています。

また、PickerViewControllerDelegateプロトコルのデリゲートメソッドを実装しています。applySelectedStringメソッドでPickerViewから渡された文字列をMainViewController上のラベルに表示しています。closeUnitPickerViewメソッドではアニメーションを使ってPickerViewを非表示にしています。また、アニメーション完了時にremoveFromSuperviewメソッドを呼び出して自身をメインスクリーンのサブビューから外しています。尚、アニメーション完了時に実行するメソッドを@selector(animationDidStop:finished:context:)として指定しましたが、この書式を守らないと正常に動作しないので注意してください。

以上で実装は完了です!ちゃんと動きましたか?

このメモに登場するサンプルプログラムのソースは以下からダウンロードできます。
[サンプルプログラムのソース]

 

いやー今回は割と長編でした。本屋さんで見かけるプログラミングの解説書は300ページを超えるものが多いですが、相当なエネルギーを使って書いてるんでしょうね。頭が下がります。

 

環境:
XCode 4.6
ARCとStoryboardを使用

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;