Open14

iOS App Dev Tutorials(UIKit編)学習メモ

miruomiruo

Getting started with Today

https://developer.apple.com/tutorials/app-dev-training/getting-started-with-today

このモジュールについて

  • Appleアプリ開発が初めてなプログラミング経験者向けのページ
  • 先にSwift言語の公式ドキュメントで基礎は確認しておくこと

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/

UIKitとは?

  • Appleアプリを作成するためのフレームワーク
  • iOS13より前(※SwiftUIが使えない)をサポートする場合は必要
  • Auto Layoutを使って、あらゆるデバイス向けのUIを作成出来る
  • 公式ドキュメントだけでなく、コミュニティのサポートが手厚い
  • SwiftUIとの統合も簡単に出来る

コースで作るアプリについて

  • リマインダーリスト
    • 作成したリマインダーの一覧が表示
    • doneボタンを押すことでリマインダーを完了できる
    • セグメントコントールで今日、今後、すべての切り替えが出来る
    • +ボタンで追加ページに遷移できる
  • リマインダー詳細ページ
    • タイトル・日付・時間・注釈が見れる
    • editボタンでここは変更できる
  • リマインダー追加ページ
    • 新規でリマインダーを追加できる
    • 追加した後は一覧画面にすぐ反映できるようにする

成功までのTips

  • コードを書き進めることで学習すること
  • 最新バージョンのXcodeがインストールしてからコースを開始すること
miruomiruo

Creating a list view(〜collection view controllerの追加まで)

プロジェクトの作成

  • プロジェクトの作成画面でinterfaceを「Storyboard」を選択すると、UIKitのアプリを作成出来る

collection view controllerの追加

  • View controllerとは?
    • 画面の管理、更新を担う
    • 複数のView controllerで画面間の橋渡しを行う
  • 今回はそのView controllerの1つ、collection view controllerを使う
    • データの集まりを表示するのに使う。
    • グリッド表示、行(列)表示、テーブル表示などいろんな表示の仕方が出来る。

Xcode15でのStoryboardの注意点

2箇所ドキュメントだけでは分からなかった箇所があったのでメモ。

  1. デフォルトで設定されているView controllerの削除

In Main.storyboard, delete View Controller Scene from the document outline.

とあるが、

上記スクショの状態でdeleteボタンを押すと削除出来る。

  1. Libraryアイコンが見当たらない

Click the Library icon in the toolbar, and search for a collection view controller.

とあるが、ドキュメントにはアイコンのスクリーンショットがなく初見ではわからない。

Xcodeの画面右上にいるこのスクショの+アイコンをクリックすると出せる。Shift + Command + LでもOK。

(参考)
https://stackoverflow.com/a/58050484

miruomiruo

Creating a list view(〜ページの終わりまで)

Create a reminder model

  • 画面に表示するリマインダーのモデルの定義
  • #if DEBUGはアプリが開発モードになっているときのみ使える箇所を定義出来る

Configure the collection as a list

Configure the data source

  • リストに表示する内容とUIの設定
  • UICollectionView.CellRegistration
    • リストのセル(=1行)にどのように表示するかを登録します
    • この後に書くDataSourceの登録のために必要
  • DataSource
    • UICollectionViewに表示するデータを提供します
    • ただしここではまだ画面に反映はしておらず、真っ白なままです。
    • データの表示の調整役に近い。

https://developer.apple.com/documentation/uikit/uicollectionview/cellregistration

https://qiita.com/maiyama18/items/28039293b4bbf886ce8e

Apply a snapshot

  • ここでようやく画面にリストが表示されます。
  • snapshot = ある時点での表示するデータのこと
    • NSDiffableDataSourceSnapshotを使って、実際のデータをUICollectionViewに渡す
    • for文とmap文の比較は以下のサイトが詳しい
    • このsnapshotにサンプルデータを登録 -> datasourceにsnapshotを適当 -> UICollectionViewのdatasourceにセットすると画面の表示が出来る

https://softmoco.com/swift-basics/swift-array-loop-map-filter.php

miruomiruo

Displaying cell info(〜Format the date and timeまで)

  • ここではリマインダーのリストの見た目を整えていく
  • Startererフォルダを使ってチュートリアルをやっていく。フォルダは以下アイコンからダウンロード出来る。

Format the date and time

  • リストに出す日付と時間の表示を整える
  • extension文を使って、Swift標準のDate型を拡張させて、表示を整えてくれるメソッドを定義し、使えるようにする
    https://www.swiftlangjp.com/language-guide/extensions.html
  • リマインダーの日付今日だった場合、“Today at 3:00 PM” それ以外は“Oct 22 at 3:00 PM”みたいな表記になる

文法メモ

isDateInToday(_:) メソッド

与えられた日付が今日かを判定します

NSLocalizedString(_:comment:)メソッド

調べたけど、出てこなかった。こっちのメソッドと同一ということでいいのかな?
https://developer.apple.com/documentation/foundation/1418095-nslocalizedstring

今は別のメソッドが推奨されているぽいが、Apple公式からアンサーがないので真意不明。
https://qiita.com/uhooi/items/41ead3ad632dc911a4f9

formatted(date:time:) メソッド

iOS15から使えるようになった指定した方式の時間表記を返してくれるメソッド。ここの公式ドキュメントが詳しいです。
https://developer.apple.com/documentation/foundation/date/3766588-formatted/#discussion

formatted(_:)メソッド

2回目に出てくるformattedメソッドはこっち。同じくiOS15から使えるようになった。こちらは日付・時間に限らず細かい時間表記が設定できるのが特徴。
https://developer.apple.com/documentation/foundation/date/3766587-formatted

String(format:)メソッド

テンプレート文字列に指定した値を埋め込めた文字列を返すメソッドです。ここの記事が詳しいです。
https://www.kodeco.com/books/swift-cookbook/v1.0/chapters/6-use-string-formatting-in-swift

Organize view controllers

  • 前チャプターで作成したUICollectionView.CellRegistrationのリファクタリングがメイン
  • リストに前節で作成したdayAndTimeTextプロパティを使った時間表記を追加

(備忘録)Xcode15だと出たエラーメモ

  • Starterフォルダに入っているUIColor+Today.swiftでエラーが出る
    追加したところこんな感じのエラーが出た。どうやらextensionでの定義がなくてもAssetsに入っていればそのまま呼び出せるようになったみたい。
miruomiruo

Displaying cell info(最後まで)

Change the cell background color

UIBackgroundConfigurationを使いリスト項目の背景色を設定します。
https://developer.apple.com/documentation/uikit/uibackgroundconfiguration

Display the reminder complete status

文法メモ

UICellAccessory

リストの項目にボタンやアイコンといったaccessoryを置くことが出来ます。accessoryの配置はいじることが出来ず、右端に置かれます。配置や見た目をカスタマイズしたい場合は後述のcustomViewで定義する必要があります。
https://developer.apple.com/documentation/uikit/uicellaccessory

なおdisclosureIndicatorはこんな感じのアイコンを返します。ページ遷移で使うアイコンです。

customView(configuration:)

UICellAccessory.CustomViewConfigurationで定義されたカスタムなaccessoryを返します。

https://developer.apple.com/documentation/uikit/uicellaccessory/3600868-customview

UICellAccessory.CustomViewConfigurationを使ったaccessoryの作り方についてはここの記事が詳しいです。
https://www.donnywals.com/how-to-add-a-custom-accessory-to-a-uicollectionviewlistcell/

UIImage

画像を表示出来ます。概要はここのサイトが詳しいです(objective-cで書かれておりちょっと古いですが、おおよその掴めると思う)。
https://iphone-tora.sakura.ne.jp/uiimage.html

今回はiOS13から使用出来るinit(systemName:withConfiguration:)を使って定義されている。SF Symbolsに定義されたシステムアイコンを設定にそったサイズや色などで表示出来る。
アイコンは

  • リマインダーがdoneがtrueだった場合は内側が塗りつぶされたアイコン
  • リマインダーが逆にdoneがfalseだった場合は塗りつぶされていないアイコン

https://developer.apple.com/documentation/uikit/uiimage/3294234-init

UIButton

ボタンを表示します。概要はここのサイトが詳しいです。setImageに関する解説も掲載されています。
https://iphone-tora.sakura.ne.jp/uibutton.html

なおデフォルトでは色が青になってしまうため、tintColorを使って任意の色に変更している。
https://sarunw.com/posts/tintcolor/

miruomiruo

Making reminders identifiable

※ここからいろんな箇所を修正するので、差分を見やすくするためにもgitが使えるなら始める前に一度コミットしたほうがいいかも。
いよいよタップしてリストのアイコンを切り替える処理を入れていきます。

Make the model identifiable

  • ここではReminderモデルをIdentifiableな構造体に変更させる

文法メモ

Identifiable

iOS13から使用できるようになった。SwiftUIでもお馴染みの存在。セットすると一意なIDを持つ構造体となる。
UUID().uuidStringを使ってIDを生成できる。

https://developer.apple.com/documentation/swift/identifiable

Create functions for accessing the model

  • ここではリマインダーを簡単に取得したり、更新出来るメソッドを組む

文法メモ

firstIndex(where:)

配列から条件にあてはまる1番目の要素を返すメソッド。
ただ配列とfirstIndexを繋げずに、firstIndexだけで出来ている理由が調べてもわからなかった。
reminders.indexOfReminderで定義出来るようにするため?もう少しextension周りを学習しないと。
https://developer.apple.com/documentation/swift/dictionary/firstindex(where:)

Create functions for accessing the model

  • ここではリマインダーのボタンクリックイベントを作成する
  • iOSではこのようなクリックなどのボタンイベントをbutton actionと呼ばれる

文法メモ

toggle()

SwiftUIでもよく出てくるBoolean値を逆の値に変更するメソッド。スイッチを押すみたいな感じです。
https://developer.apple.com/documentation/swift/bool/toggle()

@objc

Objectctive-cで書かれた古いAPIを扱うためのメソッド属性。詳しくは次の節で。

Wire a target-action pair

  • ここでは前節で作成したボタンクリックイベントがリマインダーのボタンのクリックで出来るように設定していきます
  • クリックでリスト項目にセットされたReminderクラスは更新されるようになったが、まだ画面の表示に反映されない。反映させるのは次節。

文法メモ

addTarget(_:action:for:)

パーツ(今回はボタン)にイベントを追加出来るメソッドです。

ちなみにiOS14からはaddActionというものでもう少し簡単に記載できるみたい。
https://tech.connehito.com/entry/uicontrol-addaction

今回は#selectorの中に前節で作成したdidPressDoneButtonメソッドを入れています。#selectorとdidPressDoneButtonメソッドを@objcで定義した理由については以下の記事に平易な文章で書かれています。Objective-Cが主流だった時代を知らない人間にとってはこういった記事はとても助かります。

https://swift-ios.keicode.com/ios/target-action.php

Update the snapshot

  • 前節でReminderクラス自体を更新しても画面の表示に反映されないことがわかった
  • これはUICollectionViewのDataSourceにセットしているSnapshotが更新されていないため。更新するための実装を本節で行う

文法メモ

reloadItems(_:)

これを使うと1セル(今回だとリマインダーのリスト項目)にリロードをかけてその範囲だけsnapshotが更新される。
ただしアプリの初期表示時のリストはもちろん空っぽなので、今回はisEmptyで動作しないよう避けている。

https://swiftsenpai.com/development/modern-ways-reload-cells/

ちなみにiOS15以降ではもう少し挙動が改善された方法が使えるらしい。

https://swiftsenpai.com/development/cells-reload-improvements-ios-15/

Make the action accessible

Preview using the Accessibility Inspector

残る2節はアクセシビリティに関する節で、UIKitの本筋からは離れるためここはざっくりとした概要に留める。

わかりづらかった箇所のメモ

Accessibility Inspectorの場所

スクショの場所から開ける。開かれるウィンドウも小さいので初見は気づきにくいかも。

調べている様子はこんな感じになる。

miruomiruo

Displaying reminder details

本ページではリマインダーの詳細画面を作っていく。
https://developer.apple.com/tutorials/app-dev-training/displaying-reminder-details

Create a reminder view

ここではリマインダーの詳細画面ようのViewControllerを作っていく。

文法メモ

画面に出すUICollectionViewCompositionalLayoutの組み方はこれまでのコースでやってきたリスト画面のときと同じです。

UICollectionLayoutListConfiguration.Appearance.insetGrouped

UITableViewのものですが、表示の設定についてはここが参考になります。
https://qiita.com/am10/items/9bbbe794e88a96e5420e#table-style

Create an enumeration for rows

ここではenum文を使ってリマインダーの詳細画面の内容を1行ずつ表示できるように行ごとにどのデータを埋め込むか決めていきます。

文法メモ

Hashable

これまでIdentifiableという似た構造体が出てきましたが、Identifiableがidを持った方であるのに対して、Hashableは構造体そのものにハッシュ値を持ちます。
そのため今回のようにリスト形式で表示させたい場合に有効です。

https://shuhey-hashimoto.com/swift/hashableとは?-swiftを理解する/

Set up the data source

ここではリマインダーの詳細画面のdatasourceのセットを行います。やり方はリスト画面と同じで、セットするのが前節まで作成したenumを使う程度の違いです。

Set up a snapshot

リマインダーの詳細画面のsnapshotのセットを行います。ここもやり方はリスト画面と同じです。
snapshot.appendItemsのところでenumを使う程度の違いです。

Display the detail view

いよいよリスト画面から詳細画面の遷移を実装し、詳細画面を見れるように実装していきます。

文法メモ

collectionView(_:shouldSelectItemAt:)

リストのセルを選択したときの挙動を記載できるみたいだが、delegateはまだ理解できてないので以下の記事を見てもよく理解できなかった。。。
https://qiita.com/takehilo/items/06a9ec7ec634d86bd734

他画面の遷移を可能にするController。View Controllerを囲うことで使用できる。
https://qiita.com/am10/items/191a103053f118dcb1ec

Style the navigation bar

遷移は出来るようになったので、今度は戻るボタンがあるナビゲーションバーのスタイル指定をしていく。

スタイル指定で使用しているAppearanceについてはこちらの記事がわかりやすかったです。
https://llcc.hatenablog.com/entry/2022/01/22/115103

スタイル指定メモ

コースだとダークモードでスクショが置かれていて、実際どんな感じにスタイリングされていなかったのかよくわかなかったため、少しずつやったスクショをメモっておきます。

Step5まで


最後まで

miruomiruo

Getting ready for editing

※ここから少し難しくなっていく印象を受けました。ゆっくりでいいので出来る時に少しずつ進めることをオススメします。またコード差分を見るためにセクションごとにgitコミットを打っておくのもいいでしょう。

ここでは前回作成したリスト詳細ページにedit(編集)モードを追加し、リマインダーを編集できるように実装していきます。

Create sections for an editing mode

まずeditモード用のセクションを作成していきます。

  • モードの切り替えのためSectionというenumを作成
    • .view: 詳細ページが閲覧モードのときのセクション。なにも文字は置かないので空文字になっていうr
    • .title / .date / .notes: 逆にeditモードのとき出すセクション。それぞれタイトル・日付・注釈のセクションのタイトルをセットします。

文法メモ

isEditing

画面がeditモードか返します。UICollectionControllerはUIViewControllerに属しているので、そのまま使用できます。
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621498-isediting

init(rawValue:)

enum型のインスタンスを作成します。rawValueに探したい要素を指定することでその要素のインスタンスを引っ張ってくることが出来ます。SectionがInt型なので、今回はIndexPathから取っているのかな?
https://developer.apple.com/documentation/swift/rawrepresentable/init(rawvalue:)

Configure the view and editing modes

ここではモード切り替えのためのリファクタリングを行います。具体的には以下のことを行っています。

  • snapshot更新メソッドを閲覧モード・編集モードの2つに分離
  • cellRegistrationHandlerにモード切り替えで表示を切り替えるロジックを入れる(ここではまだ既存の閲覧モードだけ)

Add an edit button

いよいよ詳細画面の右上にeditボタンを設置しておきます。
最終的にeditボタンを押すことでdatasource/snapshotを切り替え編集モードに画面が表示されるようになります。

  • iOSでは画面右上のEditボタンを押すとDone(完了)ボタンが自動で表示されます。逆のパターンも一緒です
  • ただしボタンを切り替えても画面の切り替えはまだ起こりません

文法メモ

rightBarButtonItem

画面上のナビゲーションバー(今回だと詳細画面にいる大きくReviewと書かれた欄です)の右端に設置するボタンを指定します。今回は後述のeditButtonItemを置きます。
https://developer.apple.com/documentation/uikit/uinavigationitem/1624957-rightbarbuttonitem

editButtonItem

Editボタン(押すとDoneボタンになる)そのものになります。
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621471-editbuttonitem

setEditing(_:animated:)

ググるとUITableViewの方がヒットしますが、今回はUICollectionViewControllerなのでUIViewControllerの方を使います。
EditもしくはDoneボタンを押したときのロジックを記載します。
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621378-setediting

Show headers in editing mode

いよいよeditモードのとき画面が切り替わるように実装していきます。まずはヘッダー部分から実装していきます。

文法メモ

firstItemInSection

画面にある各セクションにそれぞれ登録されているitemの最初の要素をヘッダーとして表示させることが出来ます。
https://developer.apple.com/documentation/uikit/uicollectionlayoutlistconfiguration/headermode/firstiteminsection

miruomiruo

Using content views

いよいよ編集モードのパーツを設置していきます。前コースで触れられていたUIContentConfigurationを使って編集欄を作っていきます。

Extract configuration methods

これから編集欄を作っていくのに備えて、まず既存のUIContentConfiguration定義のリファクタリングを行っていきます。

Create a reusable layout function

編集欄それぞれに共通で使用するlayoutを組んでいきます。
※まだ編集欄を画面に表示させていないためAutoLayoutに慣れていない初心者にはコードの意図がわかりづらいと思います。ここは画面に表示させることが出来た後で再度見返してもいいと思います。

Create a custom view with a text field

タイトルのテキストフィールドのレイアウトを組んでいきます。

文法メモ

UIView

画面表示を管理する最も基本的なクラスです。UIKitで使用するUIなんとかの名前のクラスはこれがベースになっています。
https://iphone-tora.sakura.ne.jp/uiview.html

  • intrinsicContentSize

画面に出す最低のサイズとなります。画面パーツごとにサイズは決まっているのですが、こちらの記事が詳しいです。
https://qiita.com/shtnkgm/items/f0b189e4184fe6c90707

UITextField

1行のテキスト表示・入力が出来るテキストフィールドの画面パーツです。
※複数行を扱う場合はUITextViewというまた別のパーツを使うようになります。

https://iphone-tora.sakura.ne.jp/uitextfield.html

  • clearButtonMode

テキストフィールドの右端に出来るクリアボタン(丸い❎ボタンです)の表示を指定できます。今回は.whileEditingとなっておりその名の通りテキストの入力中だけボタンが出るように指定しされています。

Conform to the content view protocol

テキストフィールドにUIContentConfigurationをセットできるように下準備を勧めていきます。

ヘッダーやリスト画面のときはcell.defaultContentConfiguration()として標準で使用できるUIContentConfigurationを使って済ませていましたが、ここから追加している画面パーツは独自のConfigurationをセットしていきます。

前回でも貼り付けましたが、見返しやすいようにこちらにも貼り付けています。
https://www.toyship.org/2022/02/17/095834

Complete the content view

テキストフィールド用のUIContentConfigurationをいよいよセットしていきます。

Conform to the content view protocol

いよいよeditモードに変更すると、タイトル欄のテキストフィールドに該当リマインダーのタイトルが入っているように実装していきます。

  • ここまで作成してきたtitleConfigurationを使い、タイトル欄にテキストフィールドをセットします
  • やり方は、これまでセクション欄や一覧画面の作成でやってきたcell.contentConfigurationのセットと同じです

Create content views for the date and notes

editモードでもタイトル欄を表示させることが出来たので、同様の実装で日付(date)・注釈(notes)欄も表示できるようにしていきます。

  • 日付(date)・注釈(notes)欄の追加の仕方は前セクションと同じです
  • ただしまだeditモードで値を編集してDoneボタンを押してもリマインダーの内容は全く更新されません。ここは次回で実装することになります。

文法メモ

UITextView

複数行のテキスト表示・入力をする画面パーツです。

https://iphone-tora.sakura.ne.jp/uitextview.html

UIDatePicker

日付や時間の選択ができる画面パーツです。

概要についてはこちらのサイトが詳しいです。
https://iphone-tora.sakura.ne.jp/uidatepicker.html

今回はiOS13.4から追加されたpreferredDatePickerStyle を指定し、カレンダーを表示させるように設定しています。どんなスタイルが指定できるかは以下の記事が詳しいです。
https://qiita.com/k_torishima/items/a53b0b3f7e1bc7c06106

miruomiruo

Add a working reminder

前回までで編集は出来るようになりましたが、一覧に戻ると編集前の状態に戻されてしまいます。
ここでは編集後の状態で更新されるように実装していきます

Add a working reminder

編集後の状態で更新されるために、現在編集中のリマインダーデータをworkingReminder という変数に残し、編集前と値が違う場合は更新するように実装していきます。

文法メモ

Equatable

構造体のインスタンスを!= / ==で比較できようになります。
構造体が持つすべてのプロパティの値が一致するかどうか を自動的に比較してくれるようになります。
https://qiita.com/imchino/items/793bccb0384c8460d267

Make the text configuration editable

文法メモ

UIControl.Event.editingChanged

UITextFieldの値が変更されたことを検知する方法はいくつか存在するのですが、今回は.editingChangedを使った方法で編集して値が変わったタイミングでonChangeイベントを走らせ、workingReminder にその値を代入する方法を取っています。

他にも方法はあり、以下の記事が詳しいです。
https://xyk.hatenablog.com/entry/2020/10/09/170112

Make the date configuration editable

前セクションと同様に日付欄も実装していきます。

文法メモ

UIControl.Event.valueChanged

UIDatePickerでは選択し終えたタイミングで変更後の値をセットさせています。
この記事が詳しいです。
https://qiita.com/wai21/items/c25740cbf1ce0c031eff

Make the notes configuration editable

注釈欄も同様に実装していくのですが、変更後の値のセットが他の2項目と違いDelegateというやり方で行います。

DelegateはiOS開発初心者にネックになる箇所の一つです。以下の記事はイラスト混じりのわかりやすい内容になっていて読みやすかったです。

https://qiita.com/natya/items/326bd50a78cf34371169

この段階では

  • 2つのクラスを跨ぎことが出来る
  • UITextViewDelegate を使うと、UITextViewでテキストを編集するときに便利なメソッドを呼んでくる

ということだけを覚えれば十分だと思います。

文法メモ

textViewDidChange:

UITextViewでテキスト編集し終えたタイミングで走らせたいロジックを記載します。

https://kawairi.jp/weblog/vita/201311189607

Cancel edits

editモードをキャンセルするcancelボタンを画面左上に追加します。

文法メモ

leftBarButtonItem

ナビゲーションバーの左(=画面左上)に設置するボタンを指定します。閲覧モードのときはnilに指定してボタンを出さなくする必要があるので注意。

https://developer.apple.com/documentation/uikit/uinavigationitem/1624936-leftbarbuttonitem

Observe changes in a view hierarchy

最後は編集した内容がリスト一覧に反映されるように実装していきます。
流れとしては

  1. 既存のupdateReminder(_:)でdata sourceをアップデート
  2. 既存のupdateSnapshot(reloading:)でsnapshotをアップロード
miruomiruo

Adding and deleting reminders

今度はリマインダーの追加と削除が出来るように実装していきます。

Create an add action

まずはリマインダーの追加画面を表示するクリックイベントを追加していきます。

Connect the target-action pair

全セクションで追加したクリックイベントをボタンから実行できるようにしていきます。

Add a new reminder to the model

登録画面は出せるようになりましたが、まだリマインダーでの追加ができません。ここで実際のリマインダーに追加するように修正していきます。

Delete a reminder

今度はリマインダーの削除機能を追加していきます。流れは更新機能と大体同じですが、スワイプを使って削除する必要があるため、それ用の設定が必要となります。

文法メモ

  • makeSwipeActions(for:)
  • UISwipeActionsConfiguration
  • UIContextualAction