iOS

Xcodeで「No signing certificate “iOS Development” found」

Xcodeの新規プロジェクト、または既存プロジェクトを開き、
プロジェクト設定のSigning欄でTeamを選択したら
No signing certificate “iOS Development” found
というエラーが出てしまった場合、
有効な証明書(*.cer)ファイルがキーチェーンに登録されていない
(登録されている証明書が全て期限切れ等)
可能性がある。

Developerサイトの「Certificates, Identifiers & Profiles」にて
新しい証明書を作成し、ダウンロードしたものを
キーチェーンアクセスの証明書のところにドラッグ&ドロップすれば登録され、Xcodeで再読込(Teamを再設定)すればエラーは消える。

Quick Password Maker バージョン1.1 公開

パスワードをより強固に、より安全に!

 これは新しいパスワードを勝手に考えてくれるアプリです。

 昨今、パスワードの使い回しによってアカウントに不正にアクセスされてしまう事故が多く発生しています。ここまで世の中にITサービスが増えてくると、それぞれで異なるパスワードを設定することが必要になってきます。しかし、そのためには、自分の頭で考えたのではバリエーションにも限界がありますし、強度にも不安が残ります。そこでこのアプリを使えば、より強固でより安全なパスワードを代わりに考えてくれます。

スクリーンショット

スクリーンショット

◆ 特徴

  • 文字種として英大文字、英小文字、数字の使用・不使用をそれぞれ指定できます。
  • 記号は13種類の中から使用・不使用を個別に指定できます。
  • 全ての文字種を必ず1回以上使うようにできます。
    (記号はいくつ指定されても1種類と数えます。)
  • 同じ文字を2回以上使わないようにできます。
    (文字数と文字種の設定の兼ね合いで出来ない場合もあります。)

◆ 使い方

  1. パスワードに使う文字種を指定します。
  2. パスワードの長さを指定します。
  3. 「パスワードを生成」ボタンを押します。
  4. 必要に応じてコピーして利用します。

◆ 動作環境

iPhone, iPod Touch(iOS 6.0以上)

◆ 利用条件

  • 本ソフトウェアはフリーウェアとして公開しています。ご利用に当たり料金は発生いたしません。
  • 著作権はクロスラボラトリーにあります。

◆ 免責事項

  • 本ソフトウェアは無保証です。ご利用によって生じたいかなる損害に対しても作者は責任を負わないものとします。ご自身の責任においてご利用ください。

◆ オマケ

  • パスワード生成後に端末をシェイクすると生成したパスワードを消去します。

◆ 更新履歴

  • v1.0 (2013-06-22)
    • 公開しました。
  • v1.1 (2016-06-16)
    • iOS 9 に対応しました。

Quick Password Maker ver1.1 Released!

Get Your Password Stronger and Safer!

This app automatically creates a new password.

Recently, many accidents that are unauthorized access to accounts by the recycling of password have occurred. With the increase in IT service, it becomes necessary to set a different password in each. However, to perform it, when a person thinks with one’s head, there is a limit in both the variation and the strength. If you use this application, the application makes stronger and safer password, instead of you.

Screenshot of Quick Password Maker

Screenshot

Features

  • You can specify each use and non-use uppercase, lowercase, numbers as character types.
  • You can specify individual use / non-use from 13 types as a symbol.
  • You can specify to use one or more times always the character types of all.
    Even if how many symbols are specified, symbol is counted as one kind.
  • You can specify not to use more than once the same character.
    In some cases, you can not specify it in consideration of character types and password length.

Usage

  1. Specify the character types to use for password.
  2. Specify the length of the password.
  3. Press the button “Generate Password”.
  4. Copy and use as needed.

Requirements

iPhone, iPod Touch(iOS 6.0 or later)

License & Copyright

  • This software is FREEWARE. There is NO CHARGE Upon use.
  • CROSS LABORATORY holds the copyright.

Disclaimer

  • This software is NO GUARANTEE. We shall not take responsibility for any damage that occurred because of the use. Please use it in the responsibility of own.

Misc

  • After generate passowrd, Shake your device to erase the password.

History

  • v1.0 (2013-06-22)
    • Released.
  • v1.1 (2016-06-16)
    • Adapted to iOS 9.

NSFetchedResultsControllerとUISearchDisplayControllerを使ったUITableView検索機能の実装

NSFetchedResultsControllerとUISearchDisplayControllerを使ったUITableViewの検索機能を実現するというトピックはネット上でも幾つか見つかるのだが、複数のクラスが組み合わさって動作するためになかなか思うような動きをするコードを書くことができなかった。

UITableView(ここではMaster-Detail Appとする)にUISearchDisplayControllerを組み合わせ、UISearchBarに検索文字列を入力するとすぐに絞り込まれる(ようにコードを書く)のだが、その時表示される絞り込まれた表はUITableViewではなくUISearchResultTableViewのインスタンスである。だから、現在見えている表がどのクラスのインスタンスなのかを意識しなければ思うようなコードを書けないということを痛感した。

表のデータソースにCore Dataを使用している場合、NSFetchedResultsControllerとUISearchDisplayControllerの両クラスの連携が鍵になる。

よく見かけるサンプルコードは次のようなものである。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

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

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

だが、検索して絞り込んだ表から選択して更に編集画面でDBを更新した場合、このcontrollerDidChangeContentメソッドでアベンドしてしまう。

1
2
 Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.
(以下省略)

こんなメッセージだ。言いたいことは分かるがどう対応して良いか分からない。

1
[self.tableView endUpdates];

の代わりに

1
[self.tableView reloadData];

とやれば、更新時にアベンドはしなくなるが、検索をキャンセルした場合に表示されるオリジナルの表のレンダリングがおかしくなってしまう。(あと、削除できなくなったり詳細画面に遷移できなくなったり。)散々悩んだ挙句、冒頭の書いたことに思い当たった。そう、分かってしまえば何てことはない。NSFetchedResultsControllerの操作も、どのTableViewに対して行うかを判断すれば良かったのだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    if (self.searchDisplayController.isActive) {
        [self.searchDisplayController.searchResultsTableView beginUpdates];
    }
    else {
        [self.tableView beginUpdates];
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.searchDisplayController.isActive ? self.searchDisplayController.searchResultsTableView : self.tableView;
   
    // (省略)
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    if (self.searchDisplayController.isActive) {
        [self.searchDisplayController.searchResultsTableView endUpdates];
    }
    else {
        [self.tableView endUpdates];
    }
}

UITableViewCellの再利用について

iOS 5.x までは
– (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
メソッド内で

1
2
3
4
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TABLE_CELL_NAME];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TABLE_CELL_NAME];
}

のようなコードを書く必要があったが、iOS 6.0 からは – (void)viewDidLoad メソッドに

1
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:TABLE_CELL_NAME];

というセットアップをすれば

1
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TABLE_CELL_NAME forIndexPath:indexPath];

と書くだけで良いというのがネットで得られる情報なのだが、UISearchDisplayController を使って検索機能を実装しようとすると検索文字列を入力した途端に落ちてしまう。検索結果は UITableView が使われるのではなく、UISearchResultTableView のインスタンスが(しかも都度)生成されるので、適切な場所でセットアップをしてあげる必要がある。ここではupdateFilteredContentForNameメソッドで検索をした直後に記述したら(5行目)うまくいった。

1
2
3
4
5
6
7
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller
shouldReloadTableForSearchString:(NSString *)searchString
{
    [self updateFilteredContentForName:searchString];
    [self.searchDisplayController.searchResultsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:TABLE_CELL_NAME];
    return YES;
}

NSDateComponentsからNSDateを取得するとnilが返ってくる

日付値に対して加減演算を行い、その結果を日付値として取得したくてNSDateComponentsクラスを使用した。以下のようなコードを書いたら、 result は常に nil が入って来る。

1
2
3
4
5
NSDate* dt = [NSDate date];
NSCalendar* cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* dc = [cal components:NSYearCalendarUnit fromDate:dt];
// dcに対する操作
NSDate* result = [dc date];

そこでNSCalendarオブジェクトを使って以下のように変更したら期待通りの値が取得できた。

1
2
3
4
5
NSDate* dt = [NSDate date];
NSCalendar* cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* dc = [cal components:NSYearCalendarUnit fromDate:dt];
// dcに対する操作
NSDate* result = [cal dateFromComponents:dc];

Master-Detail Appで新規追加画面から戻るとUITableViewに空のセルが追加されてしまう

 Xcodeでプロジェクトを作成する時に選べるテンプレートの中に「Master-Detail Application」というのがある。これはいわゆる一覧画面と詳細画面を行き来する割と代表的なアプリケーションの形であり、頻繁に利用されるパターンでもある。一覧と詳細と言えば、その多くがデータベースのようなものと組み合わせて使うのが一般的だ。ローカルのデータベースの場合、特にこだわりがなければCoreDataを使う事になると思うが、その際にちょっとハマったケースがあるのでメモしておく。

 それは詳細画面というより編集画面を新規追加の局面で開いた場合だ。ところで「Master-Detail Application」ではUITableViewControllerの他にUINavigationControllerが使われているので、編集画面にて保存の操作を行うにはUINavigationBarに保存ボタンを追加し、NSManagedObjectContextオブジェクトのsaveメソッドを呼ぶ事で保存を行うことができる。ただ、編集画面を新規追加で開いた瞬間にNSManagedObjectクラスのインスタンスが生成されるようで、保存せずに戻るボタンが押された場合には一覧画面に空のセルが追加されてしまう。これを防ぐには戻るタイミングで未保存の変更がある場合には、以下のようにロールバックするコードを書けば良い。

1
2
3
4
5
- (void)viewWillDisappear:(BOOL)animated {
    if ([self.managedObjectContext hasChanges]) {
        [self.managedObjectContext rollback];
    }
}

 これで空のセルが追加されるのを防ぐ事ができる。

UITextFieldがキーボードに隠れないようにオフセットする

iPhoneでアプリの画面にたくさんTextFieldを配置した時、画面下の方のTextFieldを選択するとキーボードが下からニョキッと出てきてTextFieldを隠してしまう。もちろんそのままでも入力は可能だが、キーボードを仕舞うまでどう入力されているか分からないので不便である。
そこで、TextFieldを選択したときに隠れてしまう場合は表示をずらして隠れないようにしたい。ネットでサンプルを探したのだが、これというものが見当たらなかったので作ってみた。

InterfaceBuilderでまず全体にUIScrollViewを敷き詰めて、そこにUITextFieldを並べていく。
まず、(隠れてしまう)全てのTextFieldについて「Editing Did Begin」と「Editing Did End」の2つのイベントハンドラを定義しておく。この際、どのTextFieldに対しても同じメソッドを適用する。例えばそれぞれ – (void)textEditingDidBegin および - (void)textEditingDidEnd とする。
この時、InterfaceBuilderを使って普通にイベントハンドラを定義しようとすると両方とも「Editing Did End」イベントに結合してしまうので、ConnectionsInspectorで「Editing Did Begin」イベントから – (void)textEditingDidBegin メソッドに結合した方が良い。
また次のフィールドを宣言しておく。

1
2
3
4
5
6
7
@interface ViewController () {
    float _keyboardOffset; // 表示のオフセット(どれだけずらすか)
    float _offsetMargin; // 実際にオフセットを適用し始めるマージン
}
    :
    :
@end

そしてそれぞれ次のように初期化する。

1
2
3
4
5
6
7
8
- (void)viewDidLoad
{
    [super viewDidLoad];
    :
    :
    _keyboardOffset = 0.0f;
    _offsetMargin = 120.0f; // 4インチディスプレイの場合は 210.0f 程度
}

_offsetMargin というのは、例えば画面の上の方にあるTextFieldを選択したときは表示をずらす必要がないので、じゃあY座標がどこまでの範囲内ならオフセットしないかというのを決めておく必要がある。3.5インチの場合はだいたい120ピクセルぐらいまではずらさなくても大丈夫。4インチの場合は3.5インチの場合と90ピクセルくらい差があるのでマージンもそれだけ増やして大丈夫だ。

次に最初に定義したイベントハンドラを実装する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (IBAction)textEditingDidBegin:(UITextField *)sender {
    // オフセットすべきサイズを計算する
    CGRect textFieldRect = [sender frame];
    _keyboardOffset = MAX(0, textFieldRect.origin.y - _offsetMargin);
    // アクティブなTextFieldが表示されるようにオフセットする
    CGRect viewFrame = [self.scrollView frame];
    viewFrame.origin.y -= _keyboardOffset;
    self.scrollView.frame = viewFrame;
}

- (IBAction)textEditingDidEnd:(UITextField *)sender {
    // ScrollViewのオフセットを元の値に戻す
    CGRect viewFrame = [self.scrollView frame];
    viewFrame.origin.y += _keyboardOffset;
    self.scrollView.frame = viewFrame;
    _keyboardOffset = 0.0f;
}

尚、UITextFieldDelegateとかオフセット時のアニメーションとかは本題から外れるので適宜実装してください。

UITableViewの一覧にCora Dataで取得した内容を反映する

 Core Dataのサンプルとか、UITableViewのサンプルとかはネット上でも書籍でもかなり豊富にあるのだが、それらの組み合わせの例は意外と少ない。先日見つけた書籍はかなり詳しく書いてあって「これはいけるか!?」と思われたが、実は肝心なコードが抜けていた。それはMaster-Detail型で言うところのMaster画面でCore Dataから取得したデータの一覧をUITableViewに表示する際、一覧の見出しにデータの内容が反映されないのだ。おそらくその書籍の著者にとってはあまりに自明の事なので記載するのを忘れてしまったのだろう。あるいはそれくらいは自分で考えろという事なのかもしれない。

 まあここで毒づいても仕方が無いので対処方法をメモしておく。

 一覧に内容を反映させるには MasterViewController.m の 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
メソッドにコードを記述する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell;
    // iOSのバージョンによりセルの取得方法が変わる
    if (6.0 <= [[[UIDevice currentDevice] systemVersion] floatValue]) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"AnyId" forIndexPath:indexPath];
        [self configureCell:cell atIndexPath:indexPath];
    }
    else {
        cell = [tableView dequeueReusableCellWithIdentifier:@"AnyId"];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AnyId"];
        }
    }
    // ここでセルの表示を変更
    NSManagedObject* managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = managedObject.anyProperty;
    return cell;
}


 尚、iOSのOSバージョンを判別する方法は以下のサイトを参考にした。
http://syszr.com/s14.html

“The model used to open the store is incompatible with the one used to create the store” に対処する

Core Dataを使った開発をしていたら次のようなエラーが出てシミュレータで起動しなくなったので、対処方法をメモしておく。

The model used to open the store is incompatible with the one used to create the store

プロジェクトをクリーンしてもダメで、このエラーメッセージで検索したところ、sqliteのファイルを削除しないとダメなのだそうだ。
そのsqliteファイルは以下の場所。ターミナルを開いてrmコマンドで削除する。

/Users/{ユーザー名}/Library/Application Support/iPhone Simulator/{iOSバージョン}/Applications/{英数字}/Documents/{プロジェクト名}.sqlite

参考にしたページはこちら
http://u2k772.blog95.fc2.com/blog-entry-167.html

アーカイブ