作ったアプリの主な機能
(画像は開発中のものなのでリリース版と差異があります)
メモ追加
(メモのFieldでenterを押すと上部のCollectionViewに追加されて一度に登録できる)
メモ表示
長押しでメモをチェックor編集or検索
設定変更
webViewでの検索or公式アプリでの検索(safari, twitter, youtube)
なぜ作ったか
アプリ作ってリリースするという目標を立ててしまった
自分の好きなデザインのメモ帳を作ってみたかった。
学生時代に作成したメモした単語を検索するテストアプリが家族に使われていたのでリメイクしてみた。(よくあるtabeleViewのTodoリスト)
勉強になったところ
CollectionView
ほとんど使ったことがなかったのでいろいろ試してみたかった
CollectionViewの用途
横に並べてスクロールさせる
CustomCellを複数使う
CellのなかにCollectionViewを入れているのもCustomCell
TableViewっぽく使う
コンテンツサイズに合わせるCollectionView
長くなるので別途まとめる
欲を言えば縦方向の余白をつめた表示で実装したかった
PopupView
吹き出しタイプ
カスタムタイプ
吹き出しと同様にmodalPresentationStyleをcustomに設定して、必要なパラメータを設定すれば実装できる思っていたが、専用のカスタムクラスを生成して適応させる必要があった。
参考にさせてもらった記事
(タップしたセルで左右反転します)
いい感じのUIにする
メモをまとめて登録したいので、改行で追加、タップで選択、もう一回タップで選択を外す、空欄で改行で削除など当たり前にありそうな機能を実現するのに、ロジックを組み合わせる必要があって地味に大変だった。
ダークモード対応
ver1.3以降
ColorAssetのみでUIの色を構成してダークモード対応できる予定だった。
xib(おそらくstoryboardも)で色を定義していた場合アプリ起動中でも動的に切り替わる。
しかしViewDidLoadの内部などでコードで色を指定する場合、アプリ起動中にモードを変更すると反映されない。
このアプリでは角丸の枠線はコードで生成しているため枠だけ色が変わらないという状態になった。
下記のようにモードが変更されたときの通知を受け取って対応する。
参考にさせてもらった記事
ViewDidLoad内で色を設定しているだけならself.viewDidLoad()だけでOK
今回はCollectionViewを再描画したかったのでself.loadView()も呼んでいる。
(ViewDidLoadだとセルが使いまわされてしまう?)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
self.loadView()
self.viewDidLoad()
}
便利なExtension
下記公開されているものにとてもお世話になりました。
使うと手放せなくなるSwift Extension集 (Swift 4版)
Rectの中心をとるextension
Realm
タグ名でプライマリーキーで登録しようとしていたが同じ値を再び登録できないことに気づかずハマった。
プライマリーキーの使い方で下記記事がとても参考になった。
参考にさせてもらった記事
final class TagData: Object {
@objc dynamic var id: String = NSUUID().uuidString
override static func primaryKey() -> String? {
"id"
}
}
Realmに限らないと思われるが、Xcode12でpod installした時にシミュレータでビルドできなくなって困った。実機ではビルドできる。
参考にした回答
DebugBuild時だけ解決。Releaseはエラーが出てうまくいかない(心が折れて調査してない)
fastlane
いろんなサイトを見て導入。increment_build_numberとuploadだけしか実施できてない。。
Archive~UploadまでするときにXcodeを占領しないので便利。
Upload時に正しくモジュールが生成できたか確認したいときはGUIで見たかったので、アーキテクチャに変更を加える際は、Xcodeで手動で行う方が良さそう。
アーキテクチャについて
ざっくりとした役割
view: 表示、遷移
presenter: viewからの通知を受け取ってModel層から値を受け取る中間的な役割
Domain: ビジネスロジック担当。
Infra: Realmからの値取得などコアロジック担当
Utility: 共通で使いたい型、extensionを持っている。
Domain <- Infraという依存関係にすることによってInfraの実態を知らなくてもPresenterに値を返せるのでテスト時にモックに置き換えできたりする。
Embedded Frameworkのuploadについて
上記のような構成でframeworkを分けて開発していたのだが、モジュールupload時にエラーが出てしまった。
エラーについて
うまくまとめられなかった実装
popupViewのカスタムクラスに値を渡す
popupViewのカスタムクラスに座標を渡したかったのだか、delegateでカスタムクラスを返しているため、セル長押し時に座標を直接渡すという微妙な実装になった。
//長押し処理の中
let customPresentationController = buttonViewController.presentationController as! CustomPresentationController
customPresentationController.rect = memoCollectionView.nowPosition(cell: cell)
delegateまみれになったview
後先考えずに画面を増やした結果delegateでfatになったVCになってしまった。
図のViewのdelegateに加えてcollectionView、presenterのdelegateもある。
コードを見るモチベが下がりクソコードがそのままになっているという悪循環が生まれている。
Model内の依存関係
DomainとInfraでメモとタグに関わるロジックを分けていたが、
タグを削除する際にタグに紐づくメモを削除することとなり、DomainのタグにメモのInfraを外部注入することとなった。。presenter側でタグに紐づくメモを削除する関係を作るのは好ましくないと思ったのでこうなったが、もっといい方法がないか...
statusbarの高さを取る処理でdeprecatedの処理を使ってしまった
presentedViewFrame.center.y = rect.center.y + UIApplication.shared.statusBarFrame.height
感想
初めてアプリ作成でリリースまでを行ないました。AppleStoreに載ってしまうのもあってUIの方に注力して手一杯になってしまいました。(本来はAPIとかRx、Combineとかも勉強しないといけなかった。。)
その分UI周りの実装で理解が深まったのとUpload時にエラーが出たときなどBuildSettingとか弄る機会があったので、アプリの設定周りも少しだけ理解が深まった気がします。
開発時間が基本深夜なのもあってスペルミスとか結構出てしまった。swift lintとかスペルチェックするツールを導入しておけばよかったなと思います。。