CocoaPodsでAFNetworkingを使おうとしてハマった話

Undefined symbols for architecture i386:
  "_OBJC_CLASS_$_AFHTTPRequestOperationManager", referenced from:
      objc-class-ref in xxx.o

というエラーが出た。

原因はPodsプロジェクトのBuild Settingsで「Build Active Architecture Only」プロパティを"Yes"にしていたため。このプロパティが"Yes"だとarmv7等の実機だけで動くようにしかライブラリがコンパイルされないため、iOS Simulatorでも動くようにこのプロパティを"No"にする。

こんなことに2時間もハマってしまった。。

should_be_bulid_active_architecture_only_no

 

 

iOS7でUISearchDisplayControllerの挙動がちょっと変わった

UISearchDisplayControllerのプロトコルUISearchDisplayDelegateにはwillUnloadSearchResultsTableViewメソッドが定義されています。

 

問題点

検索画面が閉じられるとき、iOS6ではこのメソッドが呼び出されるのですが、iOS7では呼び出されないようになったようです。そのため、このメソッドを起点として検索画面非表示後の処理を記述している場合は不具合が発生します。

 

解決策

このメソッドの代わりにwillHideSearchResultsTableViewメソッドを用いればよいと思います。このメソッドはiOS6でも呼び出されます。

 

環境

XCode 5.0

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

 

バージョンアップしたアプリをApp Storeに申請する方法

既にApp Storeに登録されてるアプリを改良して、新しいバージョンとしてApp Storeに載せたい場合は、バージョンアップしたことを申請する必要があります。申請方法は新たにアプリを登録するよりもはるかに簡単ですが、知らないと戸惑うこともあるので、メモとして残しておきます。

 

iTunes Connectで新バージョンを申請

まず、iTunes Connectにサインインし、[Manage Your Apps]の画面に行き、その画面からバージョンアップしたいアプリを選択します。

アプリを選択すると画面下部に現在のバージョンのアプリの情報と、その右に"Add Version"と表記されたボタンが表示されているので、このAdd Versionボタンを押します。

where_is_add_version

そうすると、いろいろな質問が画面に表示されますので答えていきます。そして以下のようなバージョン番号バージョンアップ内容を記述する画面が出てくるので、それぞれの言語ごとにバージョンアップ内容を記述してください。

new_version_apply

最後まで行くと、バージョンアップ前(つまり現在バージョン)のアプリの情報が引き継がれた新しいバージョンのアプリ情報が作成されます。変更点があればアプリ情報を修正し、同画面の右上にある"Ready To Upload Binary”ボタンを押します。これでiTunes Connectでの作業は完了です。

 

XCodeで新バージョンのアプリをApp Storeに送信する

次はXCode上での作業です。プロジェクト内にある"[ターゲット名]-info.plist"を開きます。このファイルの2つの項目"Bundle version"と"Bundle versions string, short"にそれぞれ先ほどiTunes Connectで申請した新しいバージョン番号を以下のように入力します。

change_app_version

後はアプリ申請時同様、このバージョンのArchiveを作成し、Organizer経由でApp Storeに新しいバージョンのアプリを送信して完了です。このあたりの手順については以前の記事「App Storeへのアプリの登録手順」を参考にしてください。

 

環境

XCode 4.6.2

 

iOS – AppStoreへのアプリの再申請

以前に申請したiOSアプリがrejectされてしまったので、再申請をしました。そのときの手順をこのメモに残しておきます。

iOS Dev CenterからiTune Connectにログインします。「Manage Your Apps」のリンクを開くと以前に申請したアプリのアイコンが表示されるので、そのリンクを開きます。開かれた画面の右下に「View Details」というボタンがあるので、そのボタンのリンクを開きます。

itune_connect_ready_to_upload_binaryそうすると、アプリのタイトルとともに以前申請した内容が表示されます。この画面の右上と右下に「Ready to Upload Binary」というボタンが表示されるので、このボタンを押します。

この操作によって修正後のアプリを再度App Storeにアップロードできるようになります。アプリのアップロードの手順は以前書いたメモ「App Storeへのアプリの登録手順」の「4.アプリのアップロード」と同じです。

後はiTunes ConnectのReview Noteに前回からの変更点などを書いてあげるとレビューアーも助かると思います。

 

 

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 – ZBarライブラリのiPhone5対応

バーコード、QRコード読取りでよく使われるライブラリに「ZBar」があります。

 

問題

このZBarライブラリですが、iPhone5で動かそうとすると問題が発生します。配布されているZBarのlibzbar.aarmv7sアーキテクチャを前提にコンパイルされていないため、iPhone5用にアプリをコンパルする際にリンクエラーが発生してしまいます。

リンクエラーの場合、以下のようなエラーメッセージが出力されるはずです。
ld: file is universal (3 slices) but does not contain a(n) armv7s slice: .../libzbar.a for architecture armv7s

 

解決方法

さてどうしたものかと思い、ネットの海をさまよっていたところ、ZBarライブラリをソースコードからコンパイルしてarmv7sに対応したlibzbar.aを作成するという解決方法がありました。

しかしながら、調べてみるとソースコードからのコンパイルもいろいろと難しそうなので、armv7sに対応したコンパイル済みのものがないかと探したところ、ありました。
http://sourceforge.net/p/zbar/discussion/1072195/thread/ba2844b5

コンパイル済みのZBarはこちらから:http://sourceforge.net/projects/zbar/files/iPhoneSDK/beta/

 

上記のWebページから「ZBarSDK-1.3.1.dmg」をダウンロードして古いZBarと置き換えたところ、無事iPhone5でも動作しました。

 

環境

XCode 4.6