みなさんこんにちは。Yuta Fujii(@HofuCamera)です。
今回からは応用編の「ToDoリストiPhoneアプリ」を開発していきたいと思います。
応用編とはいえ基本的な機能の組み合わせで開発することができます。
では開発をしていきましょう。
↓Xcodeの環境構築がまだの方はこちら↓
ToDoリストアプリの基本的な構成
まずは基本的な構成をご紹介します。
今回は「TodoList」としてプロジェクト名を決めてあげましょう。
プロジェクトの作成方法はこちらに記載されています。
Todoリストをどのように作っていくか
まず、今回のTodoリストをどのように開発していくかをはじめに説明します。
前回までの流れで、NSUserDefaultsの使い方や、配列の使い方、画面遷移の方法、画面とクラスが対であることなどを学びました。また、それらの要素を使って構築していきます。
今回のTodoリストには、2つの画面が存在します。
Todoリストを表示する画面
1つ目は「Todoリストを表示する画面」、2つ目は「Todoリストを登録する画面」です。
はじめに「Todoリストを表示する画面」について説明します。
この画面に表示する内容は主に、3つあります。
1つは、「Todoリストを登録する画面」で登録された日時を表示する箇所、2つ目は登録されたタイトル、そして本文(内容)です。これからの内容を「表示する」ということを行います。
それでは、どのように表示するのでしょうか。それは、「Todoリストを登録する画面」で登録された情報を取り出すことで表示します。言い換えると、NSUserDefaultsで登録されたものを取り出して表示するということを行います。
また、右上のプラスボタンでTodoリストを登録する画面への画面遷移を行います。Todoリストを見る際は、次へボタンと前へボタンでそれぞれ閲覧できるようにします。
Todoリストを登録する画面
続いて「Todoリストを登録する画面」について整理していきます。
ここで行うことは、タイトルの登録、本文の登録、現在日時の登録です。これらをタイトルの入った配列、本文の入った配列、現在日時の入った配列へそれぞれ入れていき、その配列をNSUserDefaultsへ登録していきます。
Todoリスト表示画面の構築
それでは画面を構築していきましょう。
まず、今回は縦のみに対応したいと思うので、下記の赤い部分のチェックを外します。
そして、iPhone6にシミュレーターをセットします。
左側のMain.storyboardを選択し、画面の赤い部分をクリックしたあと右上への赤い部分を選択します。
Sizeと書かれたところを選択してiPhone4.7-Inchを選択します。
続いて、ToolBarを画面にセットします。ここではTodoリストを登録する画面へ画面遷移させるためのボタンを設定します。
まず左側のItemをクリックした後、右側の赤い部分を選択します。
また、下記のようにAddを選択します。
すると+マークにボタンが変更されたのがわかるかと思います。
次にボタンを右端に設定したいのですが、Toolbarの場合、下記のパーツを置くことで簡単に右へよせることができます。
また、ViewContollerの色をお好きな色へ変更してください。
次に登録した日時を表示するラベルを設定していきます。
図のようにViewを設定したあと、
ラベルを設定してください。また、ここでのラベルは何も変更の起こらないラベルになります。
さらにその右隣に日時が反映されるラベルを置きます。これは、値を取り出して反映していくラベルになるので、プログラムに認識させてあげる必要があります。
続いて、本文を表示するUITextViewを設定します。
こちらも表示を行うのでプログラムに認識させてあげる必要があります。
さらにこのUITextViewをクリックして、右側の文字をすべて消去します。
最後にタイトルを表示するラベルを、登録した日時を設定するラベルと同じように設定します。
下部にはTodoを見ていくボタンをおいておきます。
右上の赤い部分をクリックしたあと、プログラムで変更を加えなければならないUILabelなどをつなげていきます。
ラベルをクリックしたあとcommandを押しながらプログラムの方へドラッグ&ドロップしていきます。
はじめのラベルはdateLable、
タイトルを表示するラベルはtitleLable、
本文を表示するUITextViewはtoDoTextViewと名づけてそれぞれプログラムに反映していきます。
Todoリスト登録画面の構築
次にTodoリスト表示画面の構築を行っていきます。
右側のViewControllerを選択して、Storyboard内にドラッグ&ドロップをしてください。
また、以前の画面と同じようにSizeをiPhone4.7-inchにします。
今回は、+ボタンを押したら遷移したいので、赤い部分からcommandを押しながらドラッグ&ド
ロップをして遷移先として設定します。
Present Modallyを選択します。
また、こちらも同様にToolbarとラベルを設定します。
このラベルに関してはプログラムで変更する必要がないので、そのまま「Todoを登録してください」とします。また左のItemは消去します。
また、登録する際にどこが登録する場所なのかを明記しておきたいので、ラベルをドラッグ&ドロップして「タイトル」、「本文」と名付けます。
そして、データを入力するため、UITextFieldをそのラベルの横に置いておきます。
本文も入力するので、下記のように設定したあと、
はじめから入っている文字を消去します。
また、今回もViewControllerの色を少し変更しておきます。
これで完成です。
Todoリスト登録画面のファイル設定
次に、ファイルを設定していきます。
現在作成したViewController(画面)にはプログラムファイルがありません。
なのでそれを作成していきたいと思います。
下記のように、File→New→Fileと進んでください。
次にCocoa Touch Classを選択してNextをクリックします。
ファイルにInputViewControllerという名前をつけてNextをおします。
これでファイルの作成は完了です。
次に、画面とファイルを一体化させます。
下記のように、画面を選択したあと、右側のClassという箇所にInputViwControllerと入力します。もし、うまくいっている場合予測で出てきます。
次に、この画面のプログラムファイルを見てみます。
画面の赤い部分をクリックしたあと、右上のマークを選択してください。
すると、下記のようにプログラムが出現したかと思います。
それでは、プログラムで制御するパーツのみ読み込んでいきます。
まずは、画面上のUITextfieldを選択した状態でキーボードでcommandを押しながらプログラム側へドラッグ&ドロップをします。
この場合、inputTitleと名づけました。
本文も同様にします。
この場合、inputTextと名づけました。
以上で、全ての設定は完了です。
ToDoの情報をアプリ内に保存する
では今回のアプリのブレインとなる機能を実装していきます。
InputViewControllerを操作する
それでは始めていきたいと思います。
現在▶ボタンを押してアプリを起動すると、はじめに出てくる画面の右上に+ボタンがあるかと思います。
この+ボタンを押した先でデータをアプリ内に保存していくことになるので、InputViewController.mを編集していくことになります。
それではMain.storyboardを開いて、画面をクリックしたあと、右上の赤い部分を選択し、プログラムを見れるようにします。
宣言
まず、それぞれ必要な要素を宣言します。
上からアプリ内に保存する際に使用するもの、タイトルを格納する配列(タンス)、本文を格納する配列(タンス)、日付を格納する配列(タンス)になります。
[objc]
NSUserDefaults *ud;
NSMutableArray *titleArray;
NSMutableArray *sentenceArray;
NSMutableArray *dateArray;
[/objc]
配列の保存について
次に、保存ボタンを作成しましょう。
右側からいつものように、Buttonをドラッグ&ドロップして画面へのせ、ボタンを選択した状態でキーボードのcommandを押しながら、プログラム側へドラッグ&ドロップします。
そして、ここではこのボタンが押されたときに反応する場所のメソッド名をsaveとします。
さらに、先ほど宣言したもののうち、dateArray以外を初期化します。
[objc]
titleArray = [[NSMutableArray alloc] init];
sentenceArray = [[NSMutableArray alloc] init];
ud = [NSUserDefaults standardUserDefaults];
[/objc]
次に、それぞれタイトルと本文に記載されたときの条件分岐を書いていきましょう。
ここでは、タイトルまたは本文が空であれば、何もしないということをまず先に記載します。
[objc]
if ([inputTitle.text isEqualToString:@””]||[inputText.text isEqualToString:@””]) {
//空の場合は何もしない
}
[/objc]
保存されてあるものを取り出す
次に、そのすぐ下に「そうでなければ」の部分を記載していきます。
手順としては、まずアプリ内に保存されているタイトル(title)、本文(sentence)がそれぞれ入っている配列を取り出します。
そして、 配列に入っている文字列の数だけ全てobjectという文字列を入れる変数へ入れて、同時にtitleArray(タイトルを入れておく配列)とsentenceArray(本文を入れる配列)へいれてあげます。
[objc]
//取り出し
NSArray* array1 = [ud arrayForKey:@”title”];
for ( NSString* object in array1 ) {
[titleArray addObject:object];
}
NSArray* array2 = [ud arrayForKey:@”sentence”];
for ( NSString* object in array2 ) {
[sentenceArray addObject:object];
}
[/objc]
そして、アプリ内に入っていた文字列の入った配列に新しく今から入力する文字列を入れるという作業を行います。
[objc]
//保存
[titleArray addObject:inputTitle.text];
[sentenceArray addObject:inputText.text];
[/objc]
最後に、タイトルのキー値をtitle、本文のキー値をsentenceとして配列をアプリ内へ保存します。
同時にログを出して、中へちゃんと保存されているかを確認してみます。
[objc]
//保存
[ud setObject:titleArray forKey:@”title”];
[ud setObject:sentenceArray forKey:@”sentence”];
[/objc]
[objc]
NSLog(@”タイトル %@”,[ud arrayForKey:@”title”]);
NSLog(@”本文 %@”,[ud arrayForKey:@”sentence”]);
[/objc]
ログは下部に出現し、主に変数の中身などを確認することができます。
今回はtitleというキー値とsentenceというキー値で保存されているものの中身を見ています。
また、処理が終わったらこの画面自体を閉じたいので、
[objc]
[self dismissViewControllerAnimated:YES completion:nil];
[/objc]
と記述します。
キーボードを閉じる
最後に、画面タッチをするとキーボードが閉じるようにしたいので、以下のように書きます。
[objc]
// タッチイベントを取る
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[inputTitle resignFirstResponder];
[inputText resignFirstResponder];
}
[/objc]
以上で保存ができているか確認したいと思います。
シミュレーターで確認してみよう
それでは、左上の▶ボタンを押して、シミュレーターで確認して見ましょう。
まず、+ボタンを押します。
次にタイトルと本文へ適当な文字を入れて登録ボタンを押してみます。
すると、画面下部の赤い部分に今登録した文字列がログとして確認できることがわかると思います。
さらに文字を入れてみます。
保存されている文字列が増えたことがわかるかと思います。
アプリ内にインプットしたデータを「取り出して」、「表示する」
機能が実装できたところで、データを「取り出して」、「表示」できるようにしましょう。
ライフサイクルについて【-(void)viewWillAppear:(BOOL)animated】
まず必ずプロジェクトのはじめに記載されているのが、「viewDidLoad」だったと思います。これは、画面が表示されたときにはじめに呼ばれるが1回しか呼ばれないというルールがあったと思います。
今回扱う「viewWillAppear」というメソッドは、画面が表示される度に呼ばれます。
これ以外にもいろいろとあるのですが、呼ばれる順番はviewDidLoad→viewWillAppearとなります。
このように決まって呼ばれる順番のことを「ライフサイクル」と呼びます。
さまざまあるので、興味のある方は「iOS,ライフサイクル」と検索してみてください。
アプリ内から配列を取り出す【NSMutableArrayの初期化】
それでは、続きを行っていきましょう。
まずはじめに、アプリ内に保存されたデータを出す準備をします。
NSUserDefaultsを宣言したあと、viewDidLoad内に初期化します。
[objc]
ud = [NSUserDefaults standardUserDefaults];
[/objc]
次に、下記のようにタイトルが入る配列、本文が入る配列、またページ番号が入るint型の箱を準備します。
[objc]
NSMutableArray *title_array;
NSMutableArray *sentence_array;
int pageCount;
[/objc]
そして、viewWillAppear内で、それぞれの配列を初期化します。
[objc]
title_array = [[NSMutableArray alloc] init];
sentence_array = [[NSMutableArray alloc] init];
[/objc]
最後に、取り出すということを行います。
まず下記を記述してください。
[objc]
//取り出し
NSArray* array1 = [ud arrayForKey:@”title”];
for ( NSString* object in array1 ) {
[title_array addObject:object];
}
NSArray* array2 = [ud arrayForKey:@”sentence”];
for ( NSString* object in array2 ) {
[sentence_array addObject:object];
}
[/objc]
順を追って説明します。
まず、
NSArray* array1 = [ud arrayForKey:@”title”];
は前回titleというキー値で保存した配列をarray1という配列へ取り出しています。
次に
for ( NSString* object in array1 )
は日本語に訳すと、array1の中に入っている物の「数の」回数だけNSString型のobjectという変数名の中にarray1の中に入っているテキスト(この場合はタイトル)をいれてくださいという意味になります。
[title_array addObject:object];
次にobjectというテキストを入れる箱を先ほど初期化したtitle_arrayという配列の後ろに順にいれていきます。
array1の中に入っている物の数の回数だけどんどん入っていくイメージです。
同じように
[objc]
NSArray* array2 = [ud arrayForKey:@”sentence”];
for ( NSString* object in array2 ) {
[sentence_array addObject:object];
}
[/objc]
本文も取り出して配列にどんどんいれていきます。
ボタン動作
次に下記のように次へボタンを選択したあと、commandを押しながらドラッグ&ドロップしてプログラムにくっつけます。
nextという名前にしました。
nextボタンの役割
このnextボタンを押すとページが次々とめくられていき、最後まで来たらはじめに戻すということを行いたいと思います。
次の順で書いていきたいと思います。
まず、ToDoリストになにも登録がない場合、ToDoが登録されていないので登録してくださいとアラートをだします。
次に、ToDoが登録されている数だけ次へを押すと順に表示されていくということを行います。
その中で、最後までToDoが順に表示されたら、はじめに戻すということを行います。
アラートを出す
まず、以下のように記述してください。
[objc]
if (title_array.count == 0) {
// コントローラを生成
UIAlertController *ac =
[UIAlertController alertControllerWithTitle:@”ToDoが登録されていません”
message:@”ToDoを登録してください”
preferredStyle:UIAlertControllerStyleAlert];
//キャンセル用のボタンとアクションを生成
UIAlertAction *cancel =
[UIAlertAction actionWithTitle:@”キャンセル”
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
// ボタンタップ時の処理
}];
[ac addAction:cancel];
// アラートを呼び出す
[self presentViewController:ac animated:YES completion:nil];
}
[/objc]
以前の記事にも書いたように、ここでは条件分岐とUIAlertControllerを使用しています。
まず、
[objc]
if (title_array.count == 0) {
[/objc]
は、ToDoが登録されていない場合、言い換えると「viewWillAppearが呼ばれた際、title_arrayにアプリ内のデータを入れていった時に何も入っていなかったら」という意味になります。
つまりtitle_array.countはtitle_arrayのカウントが0だったらということです。
[objc]
UIAlertController *ac =
[UIAlertController alertControllerWithTitle:@”ToDoが登録されていません”
message:@”ToDoを登録してください”
preferredStyle:UIAlertControllerStyleAlert];
[/objc]
でアラート画面を初期化しています。同時にタイトルなどを決めています。
次に
[objc]
//キャンセル用のボタンとアクションを生成
UIAlertAction *cancel =
[UIAlertAction actionWithTitle:@”キャンセル”
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
// ボタンタップ時の処理
}];
[/objc]
でボタンが押されたときの処理と、ボタンのタイプを設定しています。
大切な箇所はスタイルをキャンセルボタンにしておくという箇所です。
style:UIAlertActionStyleCancel
そして、ボタンを画面(ac)へはります。
[objc]
[ac addAction:cancel];
[/objc]
最後にアラートを表示します。
[objc]
// アラートを呼び出す
[self presentViewController:ac animated:YES completion:nil];
[/objc]
順にToDoを表示していく
次にこちらをみていきましょう。
[objc]
if (title_array.count>=pageCount){
//常にインクリメント
pageCount = pageCount + 1;
if (title_array.count<pageCount){
pageCount = 1;
titleLable.text = [title_array objectAtIndex:0];
toDoTextView.text = [sentence_array objectAtIndex:0];
}else{
titleLable.text = [title_array objectAtIndex:pageCount-1];
toDoTextView.text = [sentence_array objectAtIndex:pageCount-1];
}
}
[/objc]
まず、
[objc]
if (title_array.count>=pageCount){
[/objc]
は「次へボタンを押していったときに、title_arrayに入っている数のほうが次へボタンを押されたときに順々に増えていくpageCountの数よりも大きかったら」という意味になります。
次に、
[objc]
pageCount = pageCount + 1;
[/objc]
pageCountの値を1つずつ増やしていきます。これは、取り出す配列の位置を指定するために用います。
その中で
[objc]
if (title_array.count<pageCount){
pageCount = 1;
titleLable.text = [title_array objectAtIndex:0];
toDoTextView.text = [sentence_array objectAtIndex:0];
}
[/objc]
は、まず、
[objc]
if (title_array.count<pageCount)
[/objc]
もしボタンを押したときに次々増加していうpageCountの数が配列(title_array)の数よりも大きくなってしまったら
[objc]
pageCount = 1;
[/objc]
pageCountの値を1に戻します。
[objc]
titleLable.text = [title_array objectAtIndex:0];
toDoTextView.text = [sentence_array objectAtIndex:0];
[/objc]
さらに、titleLableのテキストをtitle_arrayの0番目(1番はじめの文字)にしてあげます。
ここのobjectAtIndexというのは、配列の番号を指定するときに使います。
toDoTextViewも同様のことを行います。
最後に
[objc]
else{
titleLable.text = [title_array objectAtIndex:pageCount-1];
toDoTextView.text = [sentence_array objectAtIndex:pageCount-1];
}
[/objc]
は、そうではなくて、つまり増えていっているときはtitle_arrayの配列の:pageCount番目-1をとりだして、titleLable.textにいれてくださいという処理をしています。
なぜ-1をしているかというと、1回目に次へボタンを押したときにすでにpageCountの値は0から1増えています。なので、-1をしてあげないと、title_arrayの配列の0番目が取得することができないからです。
配列は1から始めるのではなく、0からはじまります。
シミュレーターで確認してみよう
それでは、シミュレーターで確認してみたいと思います。
まず、最初の画面です。
何もToDoが登録されていない状態になります。
右上の+ボタンをおして、ToDoを登録していきます。
そして、登録が終わったら次へボタンを押してみてください。
次々と変わっていくのがわかり、また循環していっているのがわかるかと思います。
新たに登録した日付、時間をアプリ内にいれて取り出してみる
ではデータが取り出せるようになったところで、日付、時間を入れてそれをも取り出せるようにしていきましょう。
日付を取得する|配列を準備する
それでははじめていきましょう!
前回の続きで行っていきますので、InputViewController.mからはじめていきたと思います。
まず、現在時刻を入れる配列を準備します。
[objc]
NSMutableArray *timeArray;
[/objc]
次に取り出すことを行います。
この場合キー値はtimeです。
[objc]
NSArray* array3 = [ud arrayForKey:@”time”];
for ( NSString* object in array3 ) {
[timeArray addObject:object];
}
[/objc]
NSDateを使用する
新しく、現在時刻を取得するメソッドを作成していきましょう。
getDataというメソッドを作成します。
[objc]
-(void)getData{
}
[/objc]
この中に現在時刻を取得するという記述を行い、このgetDataを適切な箇所で呼びます。
[objc]
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComps = [calendar components:NSYearCalendarUnit |NSMonthCalendarUnit |NSDayCalendarUnit |NSHourCalendarUnit |NSMinuteCalendarUnit |NSSecondCalendarUnit
fromDate:date];
[/objc]
このテンプレートで現在時刻を取得できます。
NSDateというクラスで取得することができるようになります。
さらに、このNSDateでとってきた値をラベルに反映させるために、文字列に変換します。
[objc]
NSString *year_string = [NSString stringWithFormat:@”%ld年”,(long)[dateComps year]];
NSString *month_string = [NSString stringWithFormat:@”%ld月”,(long)[dateComps month]];
NSString *day_string = [NSString stringWithFormat:@”%ld日”,(long)[dateComps day]];
NSString *hour_string = [NSString stringWithFormat:@”%ld時”,(long)[dateComps hour]];
NSString *minute_string = [NSString stringWithFormat:@”%ld分”,(long)[dateComps minute]];
[/objc]
上から年、月、日、時、日、分になります。
1つを例にみていきましょう。
NSString *year_string = [NSString stringWithFormat:@”%ld年”,(long)[dateComps year]];
このNSString *year_stringは文字列型の箱を準備しています。
そして、[NSString stringWithFormat:で何らかの値を文字列に変換するということを行っています。使い方は以下になります。
[objc]
NSString *str = [NSString stringWithFormat:@“%@”,何らかの変数];
[/objc]
ここでいう何らかの変数というのが
[objc]
(long)[dateComps year]
[/objc]
になります。また[dateComps year]で年数を取得することを行っています。
さらに、これらは独立して存在しているので文字列を結合するということを行っていきたいと思います。
その結合された文字列を入れる変数を宣言しておきます。
[objc]
NSString *timeResult;
[/objc]
それでは結合します。
[objc]
NSString *val1 = [year_string stringByAppendingString:month_string];
NSString *val2 = [val1 stringByAppendingString:day_string];
NSString *val3 = [val2 stringByAppendingString:hour_string];
NSString *val4 = [val3 stringByAppendingString:minute_string];
timeResult = val4;
[/objc]
ここでどういう処理を行っているのかを説明します。
日本語にすると
NSSting *箱 = [結合元の文字列 stringByAppendingString:結合したい文字列の変数];
となります。
stringByAppendingStringは結合する際に用います。
そして、getDataを呼びます。
最後に結合した文字列をtimeArrayという配列の中に入れます。
[objc]
[timeArray addObject:timeResult];
[/objc]
さらに、アプリ内にデータをキー値timeとして保存します。
[objc]
[ud setObject:timeArray forKey:@”time”];
[/objc]
最後にtimeArrayを初期化しておきます。
[objc]
timeArray = [[NSMutableArray alloc]init];
[/objc]
ViewControllerにて日付データを取り出す
ここでViewController.mへ行きます。
このセクションではToDoを保存した際に同時に保存する日付データをアプリ内から取り出してラベルに反映するということを行います。
まず、timeArrayという名前で変数を宣言します。
[objc]
NSMutableArray *timeArray;
[/objc]
次にtimeArrayを初期化します。
[objc]
timeArray = [[NSMutableArray alloc] init];
[/objc]
そして、timeのキー値で保存されている配列をarray3に取り出します。
さらに、その配列に入っている文字列をobjectという変数にいれて、timeArrayに入れていきます。
[objc]
NSArray* array3 = [ud arrayForKey:@”time”];
for ( NSString* object in array3 ) {
[timeArray addObject:object];
}
[/objc]
最後にタイトルや本文と同じように、ラベルへ反映させていきます。
[objc]
dateLable.text = [timeArray objectAtIndex:0];
[/objc]
[objc]
dateLable.text = [timeArray objectAtIndex:pageCount-1];
[/objc]
シミュレーターで確認しよう
それでは、シミュレーターで確認してみます。
+ボタンでToDoを登録したあとに、
次へボタンを押して見ると、
登録した日時の横に登録した時の時間が分単位まで表示されることがわかるかと思います。
まとめ:基本的な組み合わせでToDoリストアプリは開発できる
少し長くなりましたが基本的な組み合わせでToDoリストアプリは開発をすることができます。
ある程度基本的な内容が頭に入っていれば開発をすることができます。
ぜひお試しください。