Git Product home page Git Product logo

iostraining-todo-app's People

Contributors

ginrou avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iostraining-todo-app's Issues

締め切りを過ぎたTODOのタイトルや日時を赤くする

TODOアプリを作ってみようシリーズの発展課題その1 です。

概要

#5 でTODOに締め切りをつけるようにしました。締め切りを過ぎたTODOはタイトルや日時を赤くしましょう。

イメージは上図の date や title の文字を赤くすることです。

ヒント

  • UILabelには文字色のプロパティがあるのでその値を変更します。プロパティについてはリファレンス UILabel Class Reference を参照してください
  • たくさんのTODOがあるときも正しく動作するか確認してください

入力時にバリデーションを行い、単体テストを書く

TODOアプリを作ってみようシリーズの最終回の演習課題です。

内容

TODO入力時のバリデーションと単体テストを取り扱います。

todo7
↑バリデーションの様子

todo8
↑テスト実行の様子. この例では失敗してます。

目的

  • 入力値のバリデーションを行えるようにする
  • UIAlertControllerを表示する
  • 単体テストが書けるようになる

スタート地点

前回( #6 )のゴール地点であるブランチlocal-notification からスタートしてください。

ゴール地点

ブランチ unit-testを見ると解答例が書いてあります。

教材

アプリの仕様

  • TODOを入力する時に以下のバリデーション(入力値検査)を行うようにしてください
    • TODOの本文がきちんと含まれていること (ゼロ文字や空白のみはダメ)
    • 締め切り時間が現在より先に設定されていること (過去の締め切りはダメ)
  • 入力値が不正な場合は、渓谷するアラートを表示する
  • 入力値検査に関する単体テストを書く

実装の方針

TODO追加時にバリデーションを行い、不十分な場合はアラートを表示する 4a49faf

まず、入力されたTODOが仕様を満たすかどうかをチェックするメソッドを作ります。
仕様を満たす場合は YES を、満たさない場合は NO を返すようにします。

そしてTODOが正しくない場合はアラートを表示します。
アラートの表示にはUIAlertControllerを用います。使い方は差分のコードを見てください。
リファレンスは UIAlertController Class Reference になります。

このUIAlertControllerはiOS8でクラス名が変更になったクラスで、以前はUIAlertViewを利用していました。

単体テストを追加する b4eb18b

次に単体テストを追加していきます。テストフレームワークであるXCTestがプロジェクト作成時に導入されているのでそれを利用します。
今回テストを行いたい対象はAddTodoViewControllerなのでAddTodoViewController.mのターゲットにTodoTestsを追加します。

2015-06-01 3 57 40 pm

次に新規テストファイルを追加します。NewFileからテンプレートは"Test Case Class"を選択し、subclass of はXCTestCaseを選択します。
ファイル名は特に制約はありませんがAddTodoViewControllerTestsなどにすると分かりやすいです。

テストファイルが追加できたらテストターゲットのヘッダファイルをimportしてテストを記述します。
XCTestによるテストの記述方法は教材を参照してください。
テストには、成功するパターンや失敗するパターン、異常な入力などの様々なパターンを網羅するとより安心して開発に望むことができます。
ちなみに 4a49faf では以下のケースに対するケアが漏れていて、テストを実行すると失敗となります。

  • todoの本文が空白文字のみ
  • 締め切り日時にnilを渡したケース

TODOの締切を入力できるようにする

TODOアプリを作ってみようシリーズの第4回目の演習課題その1です。

内容

#4 に引き続き、この回ではTODOの締切日時を入力できるようにし、一覧でも表示できるようにします。

todo5

目的

  • UIDatePickerについて理解する
  • 実務でよくある、既存のレイアウトの修正を行う

教材

スタート地点

前回のゴール地点であるブランチ write-to-userdefauls からスタートします。

ゴール地点

ブランチ add-deadline にチェックアウトすると完成した様子を見ることができます。

アプリの仕様

このissueで取り組む改善では以下の仕様を追加します

  • TODOを追加するときに、TODO本文に加えて、TODOの締切日時を設定することができる
  • TODO一覧に締切日時が表示されている

画面のレギュレーションは以下のようになります。

TODO追加画面
  • TODO本文を入力するUITextViewに加えて、日付を入力するUIDatePickerを加えます
  • 本文欄、日付欄にそれぞれラベルを追加します
  • マージンは下記画像に従ってください

todo-add-deadline-01

TODO一覧画面のセル
  • TODOのタイトルに加えて締切日時をUILabelで表示します。
  • 各マージンは8pxとしています
  • (注意) UILabelを領域をわかりやすくするために背景をグレーにしています

todo-add-deadline-02

実装の方針

今回の実装は、デザイン面での修正はもちろんですが、データ構造にも変化があります。
今までのTODOはTODO本文のみを管理していましたが、今回は本文に加えて締切日時も加わります。
データ構造の変化がある場合は様々な箇所を変更していく必要があります。その点を留意して、デザイン面での修正と、データ構造など内部的な修正に分けて修正していきます。

まず、TODO本文と締切日時の両方を扱う方法について検討しましょう。TODOを扱う箇所は現在のところ

  • 新規作成画面での入力やdelegateでの引き渡し
  • 一覧に表示する
  • NSUserDefaultsへの保存

があります。それに対して、TODOのデータ構造には以下のようなサンプルが挙げられます。

  • (a) : todoの本文、日時をそれぞれ独立して持つ
    • ViewControllerのプロパティ に todoBodyListtodoDateListがあるようなイメージです
  • (b) : todoをNSDictionaryで保持する
    • NSDictionary *todo = @{@"body": 本文, @"date": 締切}; のようなイメージ
  • (c) : todoを新規クラスで持つ
    • このようなクラスのイメージ
@interface Todo : NSObject
@property NSString *body;
@property NSDate *date
@end

(a) のケースでは NSArrayを重複して管理せねばならず、さらにTODOの要素が増えると様々な箇所で引数を追加したりプロパティを追加していく必要があり、将来的に煩雑になりそうです

(b) のケースでは将来的にTODOの要素が増えても引数を各箇所で増やしたりする必要はありませんが、どのキーにどのような型のオブジェクトが入っているかをObjective-Cでは保証できません。このタイプではUserDefaultsにそのまま保存することができます。

(c) 一番堅牢なデータ構造となりますが、新規クラスを作成する必要があるなど、少し修正する量が大きくなってしまいます。またNSUserDefaultsで読み書きできる形式への変換も行う必要があります。

これらのメリット、デメリットを勘案してどのデータ構造を採用するかを決めます。よく使うデータ構造の場合は(c)を採用するケースが多いと思いますが、今回はあまり大きくないアプリなのできちっと型で縛るメリットはあまり享受できそうにありません。そこで比較的簡易にデータを扱うことのできる (b) のNSDictionary で扱うデータ構造を採用したいと思います。

NSDictionary *todo = @{
    @"title": <TODOの本文:NSString>, 
    @"date": <TODOの締切:NSDate>
};
下位バージョンとのマイグレーション

iOSに限らず、Androidでも考慮しないといけない問題なのですが、端末内に保存しているデータ構造を変更する際はマイグレーションについて考えないといけません。
過去バージョンのデータを新バージョンのデータ構造で使えるようにバージョンアップしていくのがマイグレーションとなります。

今回UserDefaultsに保存しているTODOのデータ構造は

  • 旧バージョン
    • todo本文のNSStringをNSArrayで保存
  • 新バージョン
    • todo本文と締切をNSDictionaryで一つのインスタンスとしてNSArrayとして保存

となるので、旧バージョンのデータ構造のまま新バージョンで起動すると以下のようにクラッシュが発生します。

// @[ @"todo1', @"todo2"] のようなNSStringの配列
NSArray *savedTodo = [[NSUserDefaults standardUserDefaults] objectForKey:kSavedToDoUserDefaultsKey];

// 新バージョンではTodoをNSDictionaryで扱おうとするが
// 実態はNSStringの配列. Obj-Cはこの段階ではクラッシュしない
NSDictionary *firstTodo = savedTodo[0];

// unrecognised selector でクラッシュする
NSString *todoBody = firstTodo[@"title"];

このような問題に対処するには、バージョンアップ時に保存しているデータ構造を更新処理を行います。
バージョンアップ時に呼ばれるコールバック関数などはないため、バージョンアップ済みかどうかをチェックして必要ならマイグレーションを行います。
一般的には起動後すぐに実行されるメソッドで記述することが多く、UIApplicationDelegateの- application:didFinishLaunchingWithOptions: などで実行することが多いです。
マイグレーションに時間のかかる場合は、別途マイグレーション専用の画面を用意してやる必要があります。

このissueではマイグレーションを実際には行いませんが、以下のような実装にすると良いでしょう。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    NSArray *savedTodo = [[NSUserDefaults standardUserDefaults] objectForKey:@"TODO"];

    // 旧バージョンのみマイグレーションを行う.
    // その判断は保存されているTODOがNSStringかどうか
    if ([savedTodo.firstObject isKindOfClass:[NSString class]]) {
        NSMutableArray *newTotoList = [NSMutableArray array];
        for (NSString *oldTodo in savedTodo) {

            // 旧バージョンのデータ構造を新バージョンに合わせる
            // 本文を引き継ぎ、締切は不確定だが現在時刻を一応入れておく
            NSDictionary *newTodo = @{@"body": oldTodo,
                                      @"date": [NSDate date]};
            [newTotoList addObject:newTodo];
        }
        // 上書きする
        [[NSUserDefaults standardUserDefaults] setObject:newTotoList forKey:@"TODO"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }

    return YES;
}

実装の解説

AddTodoViewControllerにDatePickerを追加し、レイアウトを調整 d4f750e

まずは新規追加画面で締切日時を追加するためのパーツを追加してレイアウトを調整します。
日付の入力にはUIDatePickerを利用します。 UIDatePickerは時間を指定するためのコンポーネントでUIKitに含まれています。UIDatePickerは時間の選択だけですが親クラスのUIPickerViewを利用すると任意のデータを選択することができます。

このUIDatePickerをstoryboard上に追加し、Autolayoutの調整を行います。このUIDatePickerの高さは固定で162ptとなっています。

配置が完了したらこのUIDatePickerをAddTodoViewControllerのプロパティとして追加します。
キーボードが出てきた時にPickerが隠れないようにすることを忘れないでください。

ToDoのエンティティをNSStringからNSDictionaryに変更 2b2c580

ToDoをNSStringで扱っていた箇所をNSDictionaryで扱うように変更していきます。
以下の該当箇所を直します

  • ToDo追加のコールバック関数
    • 呼ぶ側 (AddTodoViewControllerの- doneButtonTapped:)
    • 呼ばれる側 (ViewControllerの - addTodoViewController:addTodoCompleted)
  • ViewControllerのプロパティ
    • @property (strong, nonatomic) NSMutableArray *todo; は変えなくてもよい
    • todo[index] でアクセスするときに扱う型をNSDictionaryに変える
    • - tableView:heightForRowAtIndexPath:
    • - tableView:cellForRowAtIndexPath:
  • TodoTableViewCellは次のコミットで取り扱います

またここで、先ほど追加したUIDatePickerから締切日時を取り出しますが、これは datePicker.date で取り出すことができます。
戻り値はNSDateで、Objective-Cでよく使われる時刻のデータ型です。

TODOの締切を表示するようにした b39c70d

TodoTableViewCellに締切を表示するようにします。
TodoTableViewCell.xibに時間表示用のラベルを追加し、レギュレーション通りになるようにレイアウトを調整します。

レイアウトの調整が完了したら、その時間表示用のラベルに時間を代入します。
Objective-Cに限らず、時間を文字列にするには、いろいろな表現方法があります。西暦、和暦、日付のみなのか、秒単位まで表示するのか、タイムゾーンはどうするのか、などさまざまな条件があります。
Objective-CではNSDateFormatter というクラスを用いてNSDateから日付の時刻を取り出します。
リファレンス → NSDateFormatter Class Reference

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"MM/dd HH:mm";
NSString *dateString =  [dateFormatter stringFromDate:date]; // "05/14 13:00" のような文字列になります

TODOの締切を通知する

TODOアプリを作ってみようシリーズの第4回目の演習課題その2です。

内容

#5 に引き続き、この回ではTODOの締切日時になったら通知を出すようにします。

todo6

目的

UILocalNotificationについて理解する

スタート地点

前回のゴール地点であるブランチadd-deadlineからスタートします

ゴール地点

ブランチ local-notificationにチェックアウトすると解答を見ることができます

アプリの仕様

TODOの締切日時になったら端末から通知が表示されるようにします。

実装の方針

通知にはサーバーから通知を行うPush通知と、端末内で完結するUILocalNotificatoinの2種類があります。この二つの通知について見た目上の違いはありませんが、通知を送る元がサーバーか、アプリがインストールされている端末か、の違いがあります。

Push通知はシミュレータで利用できず、iOS Developer Programに登録しないと利用できないのと、Push通知を送るバックエンドを別途用意しないといけませ。UILocalNotificationはある位置に入った、あるいは時刻をトリガーとして通知を行います。
今回のケースではUILocalNotificationが適しているので、UILocalNotificationを利用します。

UILocalNotificationについては教材である 5. UILocalNotification に詳しく書いていますが、以下のステップで実装を行います。

  1. 通知をすることができるかを確認し、必要ならば許諾を得る
  2. 通知をスケジュールする

実装の解説

ToDoのローカル通知を行うようにした f852031

まず、通知を出すことについて許諾を得る必要があります。これはアプリ起動時などでも構いませんが、アプリ起動時にいきなり

このようなアラートが出た場合、反射的に "Don't allow" と押してしまうユーザーも多くいます。
ここで許諾を得られない場合、通知を受けるのが非常に難しくなります。
(ユーザー自身が、設定から修正しないといけない)

そのため、通知されることに納得のいくタイミングで表示することが望ましいです。
今回はTODOを追加したタイミングでそのことをユーザーに許諾を得ればよさそうです。

そこで、TODO新規追加のコールバック関数内で以下のような処理を行います。

  • 通知の状態を確認する
    • 通知が許可されている
      • 通知を登録する
    • 通知が許諾されていない
      • 通知の許諾を得る
      • (非同期処理)
      • 通知を登録する

という手順になります。

通知の許諾を得たことを知らせるコールバック関数は appDelegateにあります。
この関数からViewControllerに知らせる必要がありますが、今回はNSNotificationを利用してプロセス内に通知します。

TODOリストを表示する

TODOアプリを作ってみようシリーズの第一回目の演習課題のTODOです

内容

UITableViewを利用して、TODOリストを表示します。TODOはコード内にハードコードしたものを用いて、追�加などは行いません。

目的

UITableViewの使い方を理解する。以下のクラスの関係や基本的な使い方を学習することを目的としています。

  • UITableViewDelegate
  • UITableViewDataSource
  • UITableViewCell

また UITableViewCellをサブクラス化してカスタマイズも行います

アプリの仕様

以下のTODOをUITableViewを利用して表示します。
長いTODOも折り返して、全文が表示されるようにしてください。
(一部TODOらしくないTODOもあるのはそのサンプルです)

NSArray *todo = @[@"牛乳を買ってくる",
                  @"ビールを飲む",
                  @"家賃の振り込み",
                  @"洗剤を買い足す",
                  @"Macのアップデート",
                  @"ルンバの充電",
                  @"結婚式の招待状に返信する",
                  @"犬の散歩",
                  @"雨ニモマケズ 風ニモマケズ 雪ニモ夏ノ暑サニモマケヌ 丈夫ナカラダヲモチ 慾ハナク 決シテ瞋ラズ イツモシヅカニワラッテヰル 一日ニ玄米四合ト 味噌ト少シノ野菜ヲタベ アラユルコトヲ ジブンヲカンジョウニ入レズニ ヨクミキキシワカリ ソシテワスレズ",
                  @"ビールを飲む"
                  ];

またTODOを表示するセルは以下のレギュレーションに従ってください.
2015-03-25 1 38 24 pm

動作イメージ

2015-03-19 9 11 07 pm

実装のステップ

tableViewを表示する 6974b40

UITableViewをメインのViewControllerの上に配置し、Autolayoutを設定します。
Autolayoutのpinの仕方は任意ですが、上下左右のマージンを0にするのがベターです。
さらにViewControllerのプロパティとして追加します。

セルを表示する cac9ebf

  • プロパティとして todo を追加
  • TableViewで利用するセルを登録 (クラスはUITableViewCellを利用)
  • tableViewのdataSourceとしてselfを指定
  • UITableViewDataSourceプロトコルに準拠し、プロトコルのメソッドを実装
    • tableView:numberOfRowsInSection: はtodoの個数を返します
    • tableView:cellForRowAtIndexPath は表示したいセルを返します

カスタムセルの追加 1bd803f a97f4cf

TodoTableViewCellという名前で新しいクラスを追加(xibも同時に作る)
カスタムセルの実装

  • xib上にUILabelを置いて、制約を貼る (とりあえず上下左右にpin)
  • レギュレーションを反映する
  • xib上で、UILabelのLinesを0にする
  • xib上のUILabelをソース上から参照できるようにプロパティを作りIBOutletで接続 (todoLabelという名前)
  • 登録するセルをUITableViewCellからTodoTableViewCellに変更
    • registerNibを使う
    • importを忘れない
  • cellForRowAtIndexPath でdequeするセルの結果をUITableViewCellからTodoTableViewCellに変更
  • todoLabel.textに値を入れる

高さの制御 dc8911b

  • UITableViewはセルの中身よりも先に高さを計算して返す必要がある
    heightForRowAtIndexPathが全部のセルに対して呼ばれ、cellForRowAtIndexPathが表示のタイミングで呼ばれる
  • 高さ計算計算用のセルを予め作っておき、そこでレイアウトさせて高さを算出する

※ iOS8以降の場合はAutolayoutを正しく設定することで自動的に高さを計算してくれる。iOS7以前もサポートする場合は、この高さの計算を行うメソッドを実装する必要があります。

ViewControllerがUITableViewDelegateに準拠するようにして、tableview.delegate = selfにする

プロパティに高さ計算用のセル(offscreenCell)を追加
@interface 
// 中略
@property (strong, nonatomic) TodoTableViewCell *offscreenCell;
// 中略
@end

// viewDidLoadなどの中で
self.offscreenCell = [nib instantiateWithOwner:nil options:nil][0];
offscreenCellのサイズを決定

tableViewがレイアウトされたタイミング(viewDidLayoutSubviews)でセルの横幅が決まる。
ここでoffscreenCellのサイズを決定する。(xibのサイズのままだと画面サイズが変わったときに対応できない)

この時ラベルに preferredMaxLayoutWidth を設定する。これをしないと複数行の表示が正しく行われない

高さを計算する

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath で各セルの高さを計算して返す

手順としては

  • セルにテキストを代入
  • セルにレイアウトを実行させる
  • セルのサイズから高さを求めて返す

その他情報

参照する教材

スタートするブランチ

start
※ Main.storyboard の initial view controller を指定し忘れたので、指定してください 😓

ここまでの完成品のブランチ

show-todo

TODOを削除できるようにする

TODOアプリを作ってみようシリーズの第3回目の演習課題その1です。

内容

#2 に引き続き、この回ではTODOの削除を行います。

todo3

目的

  • UITableViewの編集モードについて

教材

Table View Programming Guide for iOS - Inserting and Deleting Rows and Sections

スタート地点

ブランチ add-todo からブランチを切ってから始めてください。

ゴール地点

ブランチ delete-todo にチェックアウトすると回答を見ることができます。

アプリの仕様

  • tableviewにあるTODOをスワイプすると"Delete"ボタンが出てくる。(あるいは"削除"ボタン)
  • ボタンをタップするとTODOがアニメーション付きで削除される

実装の方針

UITableViewにはrowの削除や順番の入れ替えを行うことのできる編集モード(editing)があります。
この機能の一つにセルをスワイプした時に削除ボタンを出すことができるのでそれを利用します。

実装は比較的容易で、UITableViewDataSourceのメソッドをいくつか実装するだけです。

TODOの削除 172fe3e

実装するメソッドは以下の二つです。

- tableView:canEditRowAtIndexPath: はすべてのセルで削除できるので常にYESを返します。

- tableView:commitEditingStyle:forRowAtIndexPath: では実際の削除動作を行います。
削除は以下のステップで行います。

1. データ構造からの削除

todoを保持しているデータ構造(現在だとViewController.todo)から削除します。
NSArrayでtodoを保持していると削除できないのでNSMutableArrayで持つようにしてください。
248e9a2

2. tableViewから削除

UITableViewからrowを削除するもっとも簡単な方法は [tableView reloadData]; を行うことですが、この場合はアニメーションが追加されません。
動的な更新を行う場合は、[tableView beginUpdates];[tableView endUpdates]; の間で更新処理を行います。
tableViewから行を削除するには - deleteRowsAtIndexPaths:withRowAnimation: を実行します。

新しいToDoを追加できるようにする

TODOアプリを作ってみようシリーズの第2回目の演習課題です

内容

前回 ( #1 )に引き続き、今度は新しくToDoを入力し追加できるようにします。

todo

目的

  • UITextViewとキーボードについて
  • ViewController間の連携を通してDelegateパターンについて理解する

アプリの仕様

todo-spec-2

前回のアプリをさらに発展させて、ToDoを追加できる画面を追加します。

遷移

ToDo一覧画面上部にナビゲーションバーを配置し、ToDo追加画面へ遷移するボタンを配置します。

ToDo追加画面

追加画面は上部にキャンセルと完了ボタンがあり、それぞれタップすると�以下のように遷移します。

  • キャンセル : ToDo一覧画面に戻る
  • 完了 : ToDo一覧画面に戻り、入力したToDoが追加される

追加画面にはToDo用のUITextViewがありToDoを追加できるようにします。
ToDoは複数行入力できるようにしてください。

実装の方針

Navigation Controllerに追加 19a155c

レギュレーション通りの画面を作るためには右上にボタンを設置する必要があります。iOSアプリでよく目にする、画面上部のバーは”UINavigationBar”です。
UINavigationControllerを用いるとこのUINavigationBarを利用することができます。
そのため、UINavigationControllerでメインのViewControllerを管理するようにstoryboardでViewControllerの構成を変更します。

2015-04-03 7 14 47 pm

また右上に+ボタンを追加します。これは”Bar Button Item” を選択してドロップし、設定を変更します。
最後にボタンタップ時のハンドラを追加して完了です。

新しいViewControllerを追加してToDoを追加できるようにする

新規クラスを追加し表示する 35b3164

先ほどの+ボタンをタップしたら新しく作成したViewControllerを表示させます。

UIViewControllerのサブクラスとして、AddTodoControllerのクラスを新規ファイルから作成します。
次にstoryboard上にViewControllerのオブジェクトを新たに配置し, クラスを”AddTodoController”, Storyboard IDを同じく”AddTodoController”とします。
また、先ほどのボタンタップ時のハンドラでこのAddTodoControllerを生成して表示します。
※この時、キャンセルボタンと完了ボタンを置くためにUINavigationControllerで表示を行います。

AddToDoViewControllerのレギュレーション反映 11820a2

レギュレーションを反映するために、各パーツを配置し、Autolayoutを設定します。

テキスト入力エリアに使うクラスは UITextView です。複数行のテキスト入力や表示の際によく使われるクラスです。
このViewを配置して上下左右をpinで止めるAutolayoutを設定します。

キャンセルボタン、doneボタンはstoryboardからではなくコード上から追加します。

self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                                                                      target:self
                                                                                      action:@selector(cancelButtonTapped:)];

このようにすると、NavigationBarの左上にキャンセルボタンが出現し、タップされるとcancelButtonTappedが呼ばれます。
doneボタンも同様にします。

ここまでの状態でも入力はできるのですが、長い文章を入力するとキーボードの下に潜り込んでしまい、テキストを見ることができなくなってしまいます。
この不具合は次のコミットで直します。

キーボード表示時にTextViewのサイズを変更する d6a319e

�キーボードの下にTextViewが潜り込まないようにするためには”キーボードが表示されたタイミングでTextViewのサイズを変える”ことで対応します。

キーボードが出現したことを検知する

キーボードが出現した時には、UIKeyboardWillShowNotification のキー名でNSNotificationが通知されます。

NSNotification とは

NSNotificationとは、アプリ全体に行き渡る通知を行う仕組みです。[NSNotificationCenter defaultCenter]というシングルトンに対してどのオブジェクトがどのキーに対して通知を受け取るかのobserverを登録します。すると通知が発生したときに登録したメソッドが実行されます。

詳しくはこちらをご覧ください → 7.4 NSNotification、NSNotificationCenter を用いた通知 · mixi-inc/iOSTraining Wiki

この通知を受け取れるようにobserverとして登録します。
(�注意: dealloc時にobserverを解除しないとクラッシュを引き起こします)

通知ごとの詳細は通知されるメソッドの引数であるNSNotificationインスタンスのuserInfoというNSDictionaryのプロパティの中に含まれています。
今回キーボードのサイズが必要なのですが、以下のようにして取得できます。

NSDictionary *userInfo = notification.userInfo;
CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
TextViewのサイズを変更する

Auto layoutを用いてViewの配置を行っている時、Viewのサイズをプログラム上で調整したい場合、そのやり方はいくつかありますが、
簡単な方法の一つは”制約の値を変える”ということになります。
今回のケースだと、TextViewとViewControllerのViewとの間のマージンの制約の値を変更します。
この制約の値は初期値では0(つまりマージンなし)ですが、キーボードが出現したときにキーボードの高さの分だけ制約を大きくすれば良いとなります。

そのためには、以下のステップを経る必要があります。

  • storyboard上の制約をIBOutletプロパティとして追加する
  • キーボード出現時に制約の値を変える

となります。実行結果は以下のようになります。

2015-04-03 8 12 47 pm

わかりやすくするため、TextViewの色を緑にしてあります。

ToDoが追加されたら反映する 091a55d

キャンセルボタン/Doneボタンが押された時に、AddTodoViewControllerを閉じ、追加があればToDoに追加を行います。

ToDoが追加された時、元のViewControllerはどのようにして新しく追加されたToDoを知るためにDelegateパターンを用います。
以下の処理を追加します。

  • プロトコルの宣言とdelegateプロパティの追加
  • キャンセル/Doneボタンタップ時にdelegateメソッドを呼ぶ
  • ViewControllerがプロトコルに準拠し、そのメソッドを実装する

その他情報

教材

スタートブランチ

show-todo

ここまでの完成品

add-todo

TODOの永続化

TODOアプリを作ってみようシリーズの第3回目の演習課題その2です。

内容

#3 に引き続き、この回ではTODOの永続化を行います。

todo4

目的

  • NSUserDefaultsについて理解する。
  • アプリケーションのライフサイクルについて理解する。

教材

スタート地点

3のゴール地点である、ブランチ delete-todo からブランチを切ってから始めてください。

ゴール地点

ブランチ write-to-userdefauls にチェックアウトすると回答を見ることができます。

アプリの仕様

  • アプリ内で追加したTODOがアプリのプロセスが終了し、再起動した後も残っている(永続化)
  • 削除したTODOはアプリのプロセスが再起動した後に消えている
  • (補足)TODOは10個ほど初期値を与えていましたが、それは与えないようにします。

実装の方針

永続化の仕組みにはローカルDB、ファイルシステム、UserDefaults、サーバーに保存などのやり方が幾つかあります。それぞれの方法について一長一短ありますが、ここでは最も手軽に利用することのできる、NSUserDefualtsを利用します。

初期値の削除

今まで与えていた初期値を与えないようにします。

TODOを新規追加したときにNSUserDefaultsに保存する 2962ad0

UserDefualtsに保存を行うときは、保存したいオブジェクトとユニークなキーを指定して書き込みを行います。(- setObject:forKey: を用います)

また- setObject:forKey: を行ってもすぐにはファイルへの書き込みが行われないので、- synchronize を読んで書き込む必要があります。

NSUserDefaultsの実体はplistファイルとして端末に保存され、アプリの起動時にメモリ上に展開されます。
シミュレータの場合は以下のディレクトリ

/Users/<ユーザー名>/Library/Developer/CoreSimulator/Devices/<シミュレータのUUID>/data/Containers/Data/Application/<アプリケーションのUUID>/Library/Preferences

以下にある

org.punchdrunker.Todo.plist

というファイルがそれに該当します。MacのQuickLookやplutilコマンドでその中身を覗き見ることができます。

2015-04-21 8 47 19 pm

フォルダが探しにくい場合は

viewDidLoadに以下のようなログを仕込むとログにディレクトリが表示されます。

NSLog(@"%@/Preferences", NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject);
plutilコマンド

property list (plist)を扱うためのユーティリティコマンドです。plistの中身を出力したい場合は

plutil -convert <json|xml1|binary1> -o - <plistのパス>

で出力することができます。

アプリ起動時にUserDefaultsに書き込まれた内容を読み込む c78f1d7

アプリの起動時にデータを読み込んでセットします。
NSUserDefaultsからの読み込みは - objectForKey: を用います。

読み込みのタイミングはViewControllerの初期化時が良いでしょう。

TODO削除時にNSUserDefaultsからデータを削除する c179e87

#3 で実装した削除を行ったときに、永続化しているデータの更新を行います。

UserDefaultsを更新する場合は、上書きを行えば削除することができます。

うっかり実装漏れになってしまいそうな点ですが、実際のアプリケーションを作る際は要注意ポイントの一つです。

TODOをソートできるようにする

TODOアプリを作ってみようシリーズの発展課題その2 です。

概要

TODO一覧の並び順は特に定めてなく、作成順で並んでいます。締め切り順に並べたり、タイトルのあいうえお順に並べた方が良い利用シーンなどもあります。
そこでTODO一覧を

  • 作成順
  • 締め切り順
  • あいうえお順

で並び替えれるようにします。

UIの仕様については特に指定はありませんが、下図のようにUISegmentedControlを使うとよくiOSで見るレイアウトになると思います。
2015-06-18 2 57 03 pm

ヒント
  • UISegmentedControlのリファレンスはこちら → UISegmentedControl Class Reference
  • どのようにデータを保持するかが肝要です。
  • ソート順を切り替えたときも正しく動作するようにしてください。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.