iOS

矯正治療 – アプリ開発編2

矯正治療 – アプリ開発編2

本記事は SwiftWednesday Advent Calendar 2023 の20日目の記事です。
昨日の記事は、startaiyoさんの「DiffableDataSourceを具体的にわかりやすく解説してみた」についての記事でした

この記事では2023年10月上旬に歯科矯正の治療で入院していて、それに関連して開発したアプリのことについて書きます
入院や治療のことについて、矯正治療 – 治療・入院編に記載しているので、気になる方は見ていただけると!
またタイトルに2と書いてあるので、1もあるのですが、そちらはまだ開発中なので、実装が終わり次第、記事にします!

アプリ開発の経緯と詳細

今回実装したのは入院中のトイレの回数と種類を記録するアプリです
経緯としては、入院初日の最初の看護師さんからの説明で、入院中に毎日のトイレの回数と種類(大と小)を数えて、毎朝教えてくださいと言われて、覚えて管理するのは無理だと思ったので、アプリで管理しようと思いましたが、いい感じのアプリがありませんでした
具体的には記録の結果が見やすい、また記録を簡単にしたかったので、アプリを開かずにWidgetから、可能ならAppleWatchから入力できるとありがたかったのですが、そのようなものがなかったので、こうなったら入院初日から手術の時までにアプリを開発してしまおうと決意しました
こうして入院してから早々に病院のベッドで1人ハッカソンが始まりました

手術までの時間は2日だったので、とりあえずその時までにミニマムの実装を行い、手術後の期間をメインに使用・運用し、その間のフィードバックをもとに、入院後半の落ち着いたタイミングでリファクタリング・ブラッシュアップをすることにしました
アプリ名は ToiletCheckInにしました
最初の段階で実装しようと予定した機能は以下です

  1. トイレの時刻と種類の記録
    • 時刻はデータ追加した時の時刻を設定
    • データは全てFileManagerのファイルにJSONに変換して、保存
    • Widgetなどともデータを共有できるように、AppGroupsを設定
      • WidgetやWatchOSのアプリは、記録したデータは表示せず、データの追加のみをする
    • トイレの種類は文字ではなく、絵文字(💦・💩)を使って入力できるようにする
  2. 記録結果を日付ごとに一覧で表示
    • 一覧は日付ごとの単位でセクションを作り、セクションのタイトルにその日のトイレの小と大の合計回数を表示する
  3. 設定画面
    • 履歴をリセットできる

アプリのUIは基本全てSwiftUIで実装で、iOSアプリ本体、Widget、WatchOS用の各Moduleで共通で利用するコードを実装するToiletCheckInCoreの用意する方針で実装することにしました
なお時間がなかったので、AdMobなどの広告は諦めました笑

ちなみにこのアプリはすでにリリースされていて、ソースコードも公開しているので、もし入院したり、トイレの記録をしないといけない人はぜひ使ってみてください

AppStoreのダウンロードページ

GitHubのソースコード

アプリの実装

時間もないので、早速実装です
UIについては、Listを使って実装しています(コード)
データ管理用のCodableのstructについては、idやDate以外に以下の2つのenumを持つToiletResultを実装しました

  1. DeviceType: どこからデータを追加したか(phone, widget, watch)
  2. ToiletType: 大か小か(big, small)

AppGroupsについての詳細の説明は省略します
上記で実装したStructをFileManagerに保存しますが、AppGroupsを使ってデータを別のWatchOSなどのModuleからもアクセスできる領域に保存するには以下のように記載します

let fileManager = FileManager.default
guard let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: Self.groupId) else {
    print("Could not get the App Group container URL")
    return []
}
let fileURL = groupURL.appendingPathComponent(Self.fileName)

containerURLにAppGroupで設定したIdを設定して取得できるディレクトリにファイル名をappendしたURLにJSONを読み書きするファイルを作成します
これでResultItemのデータのread, writeはOKです、と思いきや1つ問題が発生します
AppleWatchのOSからAppGroup経由で、このFileに書き込むことができませんでした
ここについての調査は進めましたが、手術の時間が来てしまったので、いったんWidgetからデータを保存するようにしました

Widgetのコードや仕組みについては、以前個人開発や業務で実装したことがあったので、大まかな実装方法は把握してましたが、やったことない方は最初苦戦すると思うので、大まかな仕組みをWWDCの動画なども含めて、調べてから進めるといいと思います

アプリのブラッシュアップ

手術後の大変な期間で本格的にアプリを使って、記録をしていくと以下のような改修点が見えてきました

  1. トイレの記録を看護師さんに伝えるのが朝6時だったので、0時~23時59分の記録ではなく、6時~5時59分の記録の方がよい
    • 汎用的にするなら、1日の開始時刻をユーザーが設定できるようにするのがよさそう
  2. 看護師さんに一覧を見せると、文字が小さく、またどこを見るべきところなのかわかりにくい
    • 全ての日付のデータの一覧とは別に、1日ごとのデータのみを表示する詳細画面があった方がよい
    • 日付について、絶対表記ではなく、今日や昨日など、直近2日ついては相対表記にする
    • ユーザー自身が見るデータの追加画面や設定画面はDynamicTypeのフォントに対応させ、一覧や詳細など、他の人(看護師さんなど)が見る画面は設定画面から別途フォントサイズを指定できるようにする
  3. 看護師さんに大・小のトイレの種類だけでなく、大の便の種類(硬め、柔らかめ、下痢など)も聞かれるので、その区別の記録できた方がよい
    • 各記録にその種類が分かるようにする
  4. データの一括削除だけでなく、1つの記録単位で消せるようにする
    • これはiOSでは一般的な行を横スワイプして、削除導線を表示する機能を追加する

これらの機能を実装するために、

①各データのグループ分けを日付単位に実施していたのを、設定画面で追加した「1日の記録の開始時刻」から24時間の範囲の単位でグループ化するように修正

②詳細画面の実装、表示用の日付のプロパティをstruct内に実装、上記のフォントの設定を実装

③ToiletTypeのbigを、ToiletBigTypeという以下のenumを持つAssociatedValueを持つものに変更し、大の種類を記録できるようにしました

public enum ToiletBigType: String, Codable, CaseIterable, Identifiable {
    case hard = "硬め"
    case soft = "軟かめ"
    case liquid = "下痢"
    public static let `default` = ToiletType.ToiletBigType.hard

    public var id: String {
        return rawValue
    }
}

④はListのviewにonDeleteのmodifierを追加し、対応しました

また手術前に実装できなかったWatchOSからのデータの追加ですが、仕様が変更され、WatchOSからAppGroupsを経由したデータ共有はできなくなっており、代わりにWCSessionDelegateというものを実装して、データを追加することができました
WatchOS側のデータ送信処理は以下のように書きます

func send(toiletType: ToiletType) {
    if WCSession.default.isReachable {
        WCSession.default.sendMessage([
            key: toiletType.isBig
        ], replyHandler: nil)
    }
}

iOSアプリ側のデータ受信処理は以下のように書きます
delegateをセットアップすると、func session(_ session: WCSession, didReceiveMessage message: [String : Any]) が呼びされるようになります

extension WatchDataHandler: WCSessionDelegate {
#if os(iOS)
    public func sessionDidBecomeInactive(_ session: WCSession) {}

    public func sessionDidDeactivate(_ session: WCSession) {}
#endif

    public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {}

    public func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        guard let isBigType = message[key] as? Bool else { fatalError() }
        let toiletType: ToiletType = if isBigType {
            .big(type: SharedDefaults.selectedBigType)
        } else {
            .small
        }
        let item = ToiletResultItem(
            toiletType: toiletType,
            date: Date(), deviceType: .widget
        )
        SharedDefaults.add(item: item)
        subject.send(item)
    }
}

これでiOSアプリ内、Widget、WatchOSのアプリ内のどこからでもデータを新規追加し、iOSアプリからその結果を一覧で確認できるようになりました

1つ現状でわからない点があるとすると、WatchOSのアプリの実機ビルド方法です
調べるとペアリングしてあれば、Xcodeに通常のiPhoneのように表示され、実機ビルドができるようなのですが、Xcodeのバグかわからないですが、うまく表示されず、毎回WatchアプリからToiletCheckInのアプリをインストール→アンインストール→インストールを繰り返して、動作確認していました笑
この辺りは正しい手順を確認したいです

またWatchOSのアプリを作るとなると、今度はComplecationからショートカット的にアプリを起動したくなります
なのでそこの対応もがんばりました
とはいえ、この頃は退院直前でAppleに審査を出す時間も考慮すると、Complecationについて詳細に調べる時間もなく、とりあえずComplecationをタップするとアプリを起動するというとこまではなんとか実装できました(雑なコードはこちら)
ただこの実装だとインフォグラフなどテーマカラーを変更できる文字盤の場合、Complecationのアプリアイコンが消えてしまうというバグがあり、ここは改修が必要です(おそらくアプリアイコンの画像のファイルの形式や作りの問題で、テーマカラーが反映されるような設定にする必要がありそう)
ただ少なくとも個人開発の範囲で、個人で使う範囲としてはこの状態でも十分使えるレベルかなとは思います

Appleへの審査提出

ここまで約2週間、実装期間と利用期間を経て、できあがったアプリをAppStoreConnectにアップします
もちろんCIなどイケてるツールは整備する時間はないので、手動でアップします
プライバシーポリシーやアプリのスクショ、アプリアイコンなども突貫で用意し、審査に提出します
なんとスムーズに審査も追加し、リリースしようとしたところ、なぜか「デベロッパによりストアから削除済み」というステータスになり、アプリをリリースできません
なんか手順をミスったのかなと思い、仕方なくipaをv1.0.1で作り直し、アプリを自動リリースにして再度審査に出すも、再度同じ状態に…

これ、結論としてはアプリの配信国・地域を指定していなかったことが原因で、それを日本に指定したらリリース可能な状態になり、リリースできました🎉
おそらく色々な規制やルールの影響で、特定の地域や国でリリースするには対応が必要なことがあるからだと思いますが、それにしてもエラーの表示が分かりにくすぎるので、ここは改善してほしいとこです(初心者なら確実にハマると思う)

何はともあれ、アプリは無事AppStoreからダウンロードできるようになりました🎉

ちなみに今回実装したアプリのUIはこのような感じです

まとめ

今回は入院中に開発したアプリについてまとめました
久々に個人開発でアプリ開発をしましたが、自分なりにどのようなUIにすることで、ユーザーが使いやすいと感じるようになるかを考えることはとても重要なことだなと感じました
特に今回は事実上のユーザーの看護師さんが近くにいたので、そのFBを反映してブラッシュアップしやすかったのも大きかったかもしれません
普段の業務で開発しているアプリではどうしてもユーザーの生の声を聞くことが難しいので、ユーザーインタビューのような機会をもっと重視してもいいのかもしれません

+1

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA