【iOS】進捗報告 8 (コミュニティ)
musicLine(iOS)についての進捗。
コミュニティについて報告します。



はじめに
今まで作曲画面の進捗を報告していましたが、一時中断してコミュニティの開発を進めてたので今回はコミュニティの進捗です。
最近全然ブログ更新できてなかったので、今回は大ボリュームです!!画像だけでも見てってください〜😂
では、まず画面構成を確認してから実装状況を見ていきたいと思います。
画面構成
Androidアプリのスクショで画面構成を確認します。
コミュニティの3画面

Square

UserPage

Contest
コミュニティ広場と命名
コミュニティと言えば、新着やランキングカテゴリー等ある画面を指しますが、設計/実装する時にはコミュニティ広場Squareとします。
これはより正確に認識を合わせるためです。
コミュニティ3画面の構成は似たような作りになっており(特にコミュニティ広場とコンテスト)、共通の処理を使い回したりします。その際に、Community表記だとコミュニティ3画面全体を指すのか、コミュニティ広場を指すのかが名前だけでは分からないのが問題になりました。そのため、Community表記だと3画面全体、Squareだとコミュニティ広場と定義し、クラスや関数名だけで関係性がわかるようにしました。
ディスプレイとコントローラー
コミュニティの3画面は似たような画面構成になっており、各画面をディスプレイDisplayとコントローラーController の領域に分けて設計します。

Square

UserPage

Contest
ディスプレイ
ディスプレイDisplayは、画面上部(横持ちだと画面左部)の領域を指します。
広場・コンテストではカテゴリー説明やコンテスト内容が表示され、曲選択でソングビジュアSongVisuaに切り替わります。
ユーザーページのディスプレイはスクロールで隠れるように動作します。

コントローラー
コントローラーControllerは、画面下部(横持ちだと画面右部)の領域を指します。
3画面ともヘッダーには横一列にカテゴリータブが並び、選択できるようになっています。(カテゴリーによりヘッダーの一部が変わります)
カテゴリー選択でリストCategoryListが表示され、項目CategoryListItemをタップして曲やコンテストを選択します。
なお、コントローラーは項目の選択だけではなく、詳細画面に切り替えて内容を確認する役割もあります。

画面構成の意義
画面構成を考えることは複数の利点があります。
例えば、画面構成を分析することでコミュニティ3画面で似たような部分がわかり、共通化につながります。また画面構成の名前を決めることで、変数や関数名等の表記ブレを無くし、勘違いによる不具合を防ぎます。さらに画面構成を決めることで進捗報告等の認識合わせする時に、どの部分について言及しているのかが正確にわかります。
ただ、あまり正確に決めすぎると面倒くさくなるので、外枠の大事なところだけに絞る方がいいと思います。やっぱり追加実装や開発を進めていくと設計通りにはいかず、設計書の内容と異なっていきます。その違いを修正するにも労力がいるので、バランスは考えないといけないですね。
実装状況
| カテゴリ | タイトル | 補足 | |
| View | |||
|
Community (共通) UI配置
| コミュニティ | ディスプレイとコントローラーを表示。縦持ちと横持ちのレイアウト。どのディスプレイ、コントローラーを使用するかは各コミュニティ画面で派生 | |
| カテゴリーリスト群 | カテゴリー選択、カテゴリーリストを表示。どのカテゴリーリストを表示するかは各コミュニティ画面で派生 | ||
| ページスクロール | リストを表示。スワイプでリスト項目を取得。どのリスト項目を使用するかは各カテゴリーで派生 | ||
| 曲リスト項目 | 各コミュニティ画面で派生系を用意する | ||
| リレー曲リスト項目 | 左右にスワイプで前後走者の曲を表示 | ||
| その他リスト項目 | 曲ID:検索で曲ID一覧を表示、ユーザー:ユーザー検索やいいねリスト等で表示 | ||
| 曲詳細 | 曲の情報と貰ったコメントを表示、送るコメントを編集する | ||
| 曲の情報 | 曲のタイトル、作者、ランキング順位、タグ、ユーザーの反応等を表示 | ||
| 貰ったコメント | テキストとOGP | ||
| ソングビジュア | 曲再生位置の変更と可視化。曲情報でトロフィー変更。リスト/詳細ボタンで切り替え。シンプルのみ実装でリリース後に追加予定。 | ||
| カテゴリー選択 | カテゴリータブ表示。スクロール可能。選択表示。 | ||
| ユーザーアイコン | ユーザー画像、プレミアムリングを表示。タップでユーザーページへ | ||
| プレイヤー | 曲の再生、いいね・お気に入り等のボタン。現在の状況を反映 | ||
| 設定パネル | レイアウト設定、ソングビジュア設定、楽器フィルタ リリース後に実装予定 | ||
|
Community Square コミュニティ広場
UI配置 | ディスプレイ | 状況によりカテゴリー説明/ソングビジュアを切り替え。 | |
| カテゴリー説明 | 広場カテゴリーの説明 | ||
| コントローラー | カテゴリーリスト群/曲詳細を切り替え | ||
| カテゴリーリスト群 | Squareのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | カテゴリー別にリスト項目を派生(ランキングなら左にランク情報)。コンテスト項目はタップでコンテスト画面へ | ||
| イベント詳細 | コンテストの詳細情報の表示 | ||
| 絞り込み検索 | |||
|
Community UserPage ユーザーページ
UI配置 | ディスプレイ | ユーザープロフィールを表示。下へスワイプした時に隠れてヘッダーが表示されるように | |
| ユーザープロフィール | ユーザー情報、紹介文、アクティビティログ等表示 | ||
| ユーザー情報 | 名前、アイコン、リンクリスト、フォロー欄、紹介文表示 | ||
| リンクリスト | 外部リンク(X等)ボタンを並べる | ||
| フォロー欄 | フォロー、ミュート、通知ボタン | ||
| アクティビティログ | 活動レベル、投稿曲数やいいね数など表示 | ||
| ヘッダー | |||
| コントローラー | ディスプレイに追従 | ||
| カテゴリーリスト群 | UserPageのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | 開閉するプレイリスト項目を作成。 | ||
|
Community Contest コンテスト結果
UI配置 | ディスプレイ | 状況によりコンテスト概要/ソングビジュアを切り替え。 | |
| コンテスト概要 | コンテストのテーマ、内容、提案者の表示 | ||
| コントローラー | カテゴリーリスト群/曲詳細を切り替え | ||
| カテゴリーリスト群 | Contestのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | コンテストの順位や自分の投票数を表示するリスト項目に派生 | ||
|
Community ダイアログ | リストダイアログ | いいね・お気に入りをしたユーザーリスト表示、曲を含む再生リストの表示 | |
| メッセージアラート | 通知するテキストを知らせる(DLした時や削除確認など) | ||
| プロフィール編集 | 名前、アイコン、紹介文等を編集 | ||
| プレイリスト詳細 | プレイリストのタイトル、説明、作者、コメント等を表示 | ||
| プレイリスト編集 | タイトル、アイコン、曲リスト等を編集 | ||
| コンテスト投票 | 開催コンテスト曲の視聴と投票 | ||
| MIDI詳細 | 曲のテンポ、音符・トラック数、使用楽器等 | ||
| Commnity | |||
|
Common コミュニティ
| データ構造 | カテゴリ等の曲情報、いいねやお気に入り等のリスポンスを保持 | |
| CommunitySongPlayer | コミュニティ用にラップした曲プレイヤー | ||
| CategoryLists | カテゴリーリスト群のモデル。カテゴリー別にフェッチ/キャッシュ | ||
| ListInfo | カテゴリーリストのモデル、リストのスクロール位置、フェッチするページ番号等 | ||
| Context | Community共通のCommonContextと各画面のContextがある | ||
| UserActivityManager | ユーザーのアクティビティ(いいね、ミュートユーザー、再生リスト、モチーフ登録、アクセス履歴等)を管理 | ||
|
Service サービスモデル
| ListItemFetcher | カテゴリーリスト項目のMLサーバーから取得する | |
| MidiRepository | MIDIファイルをサーバーから取得して貯める | ||
| UserActivityFetcher | ユーザーの行動をMLサーバーから取得する(再生数、再生リストに含まれているか、コメント等) | ||
| Account | |||
|
Common アカウント
| データ構造 | ログインできる構造、アカウント情報、ログイン情報、MLユーザー情報 | |
| AccountManager | アカウントの管理、ログイン時にMLサーバーと通信 | ||
|
Service サービスモデル
| UserActivityRepository | ユーザーのアクティビティ(いいね、ミュートユーザー、再生リスト、モチーフ登録、アクセス履歴等)を貯める | |
| ServerCommunication | MLサーバーと通信する処理を集約 | ||
全進捗マップを表示
| カテゴリ | タイトル | 補足 | |
| View | |||
|
Composition UI配置
ガイド表示 | ピアノ | ||
| スクロールエリア | |||
| 小節番号 | |||
| 小節線 | |||
| 分割線 | 拡大率によって間隔を変化 | ||
| フレーズ | フレーズがないところはフレーズ作成ボタンを表示 | ||
| ツールボタン | 選択中のツールはツール色にハイライトする | ||
| メロディーライン | ペンツールでの音符作成時の入力線 | ||
| メロディー音符 | 音符の先頭にチョボをつける | ||
| リズム音符 | ベース音符のみ下に表示 | ||
| サンプルデータ | 手入力したサンプルデータの音符を配置 | ||
| 連符 | 連符は数字で表示、拡大率によって省略 | ||
| ツールガイド(ペン) | 長さ編集しているリズム音符、伸ばした時の削る範囲 | ||
| ツールガイド(指) | 移動中の音符影、矩形選択の枠 | ||
| ツールガイド(消しゴム) | 削除範囲、和音のみの削除範囲 | ||
| ツールガイド(フレーズ) | フレーズ移動・伸縮のバー、選択範囲テキスト(iOS独自) | ||
| フレーズボタン | フレーズボタンとサブボタンを配置、ツールに応じてアイコンを変更、画面拡大率に応じて縮小 | ||
| フレーズタブ | ツールに応じてアイコンを変更、拡大率に応じて縮小、スクロール状態に応じて移動 | ||
| 音符描画の高速化 | 音符が多くても描画できる速さへ最適化 | ||
| 音符の編集状態 | 選択や変更した時に枠線の色変更 | ||
| 音符音階 | メロディ音符に音階表示 | ||
| 再生バー | 曲を再生している時に、再生位置をバーで表示 | ||
| 再生ボタン | 曲を再生/停止を監視して、状態を変更、再生準備が遅い時はインジケーターを表示 | ||
| 再生速度の表示 | 曲の再生速度を表示(iOS独自) | ||
| テスト音のガイド | 鳴っている音階に色をつける | ||
|
Composition ダイアログ
| フレーズ | フレーズの作成・設定・挿入、フレーズの長さやリピート回数の設定 | |
|
Community (共通) UI配置
| コミュニティ | ディスプレイとコントローラーを表示。縦持ちと横持ちのレイアウト。どのディスプレイ、コントローラーを使用するかは各コミュニティ画面で派生 | |
| カテゴリーリスト群 | カテゴリー選択、カテゴリーリストを表示。どのカテゴリーリストを表示するかは各コミュニティ画面で派生 | ||
| ページスクロール | リストを表示。スワイプでリスト項目を取得。どのリスト項目を使用するかは各カテゴリーで派生 | ||
| 曲リスト項目 | 各コミュニティ画面で派生系を用意する | ||
| リレー曲リスト項目 | 左右にスワイプで前後走者の曲を表示 | ||
| その他リスト項目 | 曲ID:検索で曲ID一覧を表示、ユーザー:ユーザー検索やいいねリスト等で表示 | ||
| 曲詳細 | 曲の情報と貰ったコメントを表示、送るコメントを編集する | ||
| 曲の情報 | 曲のタイトル、作者、ランキング順位、タグ、ユーザーの反応等を表示 | ||
| 貰ったコメント | テキストとOGP | ||
| ソングビジュア | 曲再生位置の変更と可視化。曲情報でトロフィー変更。リスト/詳細ボタンで切り替え。シンプルのみ実装でリリース後に追加予定。 | ||
| カテゴリー選択 | カテゴリータブ表示。スクロール可能。選択表示。 | ||
| ユーザーアイコン | ユーザー画像、プレミアムリングを表示。タップでユーザーページへ | ||
| プレイヤー | 曲の再生、いいね・お気に入り等のボタン。現在の状況を反映 | ||
| 設定パネル | レイアウト設定、ソングビジュア設定、楽器フィルタ リリース後に実装予定 | ||
|
Community Square コミュニティ広場
UI配置 | ディスプレイ | 状況によりカテゴリー説明/ソングビジュアを切り替え。 | |
| カテゴリー説明 | 広場カテゴリーの説明 | ||
| コントローラー | カテゴリーリスト群/曲詳細を切り替え | ||
| カテゴリーリスト群 | Squareのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | カテゴリー別にリスト項目を派生(ランキングなら左にランク情報)。コンテスト項目はタップでコンテスト画面へ | ||
| イベント詳細 | コンテストの詳細情報の表示 | ||
| 絞り込み検索 | |||
|
Community UserPage ユーザーページ
UI配置 | ディスプレイ | ユーザープロフィールを表示。下へスワイプした時に隠れてヘッダーが表示されるように | |
| ユーザープロフィール | ユーザー情報、紹介文、アクティビティログ等表示 | ||
| ユーザー情報 | 名前、アイコン、リンクリスト、フォロー欄、紹介文表示 | ||
| リンクリスト | 外部リンク(X等)ボタンを並べる | ||
| フォロー欄 | フォロー、ミュート、通知ボタン | ||
| アクティビティログ | 活動レベル、投稿曲数やいいね数など表示 | ||
| ヘッダー | |||
| コントローラー | ディスプレイに追従 | ||
| カテゴリーリスト群 | UserPageのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | 開閉するプレイリスト項目を作成。 | ||
|
Community Contest コンテスト結果
UI配置 | ディスプレイ | 状況によりコンテスト概要/ソングビジュアを切り替え。 | |
| コンテスト概要 | コンテストのテーマ、内容、提案者の表示 | ||
| コントローラー | カテゴリーリスト群/曲詳細を切り替え | ||
| カテゴリーリスト群 | Contestのカテゴリーリストを切り替えるように派生 | ||
| リスト項目 | コンテストの順位や自分の投票数を表示するリスト項目に派生 | ||
|
Community ダイアログ | リストダイアログ | いいね・お気に入りをしたユーザーリスト表示、曲を含む再生リストの表示 | |
| メッセージアラート | 通知するテキストを知らせる(DLした時や削除確認など) | ||
| プロフィール編集 | 名前、アイコン、紹介文等を編集 | ||
| プレイリスト詳細 | プレイリストのタイトル、説明、作者、コメント等を表示 | ||
| プレイリスト編集 | タイトル、アイコン、曲リスト等を編集 | ||
| コンテスト投票 | 開催コンテスト曲の視聴と投票 | ||
| MIDI詳細 | 曲のテンポ、音符・トラック数、使用楽器等 | ||
| Composition | |||
|
Common 作曲共通
| データ構造 | 座標とサイズ、状態を保持する。レンダラーやコライダー、通知等のロジック | |
| コンバーター | Domainのデータ構造へ変換・逆変換 | ||
| 通知機構 | Modelに変更があった時に、Viewへ通知して再描画。変更状況を監視してキャッシュ | ||
| FingerTool 指ツール
音符の編集 | データ構造 | フレーズの音符を取得、基点と移動ベクトルを保持して移動する | |
| 音符の移動 | 和音や連符は上下のみに制御 | ||
| 音符の影 | 移動中に操作できているかわかりやすいように(iOS独自) | ||
| 内外判定 | 音符の移動をフレーズ内に留める | ||
| 衝突判定 | 移動する音符が他の音符に重なったときの挙動 | ||
| タップ選択 | タップした音符の子音符も選択・解除 | ||
| 矩形選択 | 囲った音符を選択 | ||
| 音符の入れ替え | リズム音符のスライドで重なった音符と入れ替える(iOS独自) | ||
| 和音の削除 | 和音を移動した時に重なる時に削除する | ||
| 連符に切り替え | リズム音符タップで切り替え | ||
| PenTool ペンツール
音符の作成 | データ構造 | 音符を作成・分割・統合、フレーズへ音符を追加する | |
| 音符の作成 | タップで音符を作成。分割線に合わせて長さを決定 | ||
| 音符列の作成 | スワイプに沿って複数の音符を作成。音階変わる時に音符分割 | ||
| 有無判定 | タップやスワイプした縦ラインに既に音符があるか判定 | ||
| 音符の移動 | 音符が既にある場合は、作成ではなく移動。一定距離進むと移動終了 | ||
| 音符の分割 | リズム音符をタップした時、音符を分割、メロディ線に沿ってY位置を動かす | ||
| 音符の統合 | 2つのリズム音符の間をタップした場合、音符を統合 | ||
| 音符の伸縮 | リズム音符を左右にスワイプすることで音符の長さを伸縮、始点で分割なし(仕様変更) | ||
| 音符の消滅 | 音符の伸縮をした時に長さが0になると削除する(iOS独自) | ||
| 領域差演算 | 音符伸縮で他の音符に重なる時は差演算して他の音符長さを削る | ||
| サンプリング補間 | 素早くスワイプしても音符が移動できるようにサンプリング間を補間する | ||
| 細かい音符 | 分割線内の細かな音符を移動する挙動 | ||
| EraserTool 消しゴムツール
音符の削除 | データ構造 | 和音と連符の認識、フレーズから音符を削除する | |
| 音符の削除 | タップで音符を削除 | ||
| 和音・連符の削除 | 和音・連符をタップで和音・連符のみを削除(iOS独自) | ||
| 音符種別判定 | タップした音符が和音・連符・ルート音符なのか判定する | ||
| 音符列の削除 | スワイプで指定する削除範囲内の複数の音符を削除。 | ||
| 和音列の削除 | リズム音符をスワイプで複数の和音のみを削除。(iOS独自) | ||
| 休符に切り替え | リズム音符タップで休符に切り替える | ||
| PhraseTool フレーズツール
フレーズの編集 | データ構造 | トラックのフレーズを取得・追加、削除、状態 | |
| フレーズの作成・挿入 | ダイアログで長さとリピートを設定 | ||
| フレーズの選択 | 2点タップで範囲選択、範囲ガイドタップで選択解除(仕様変更) | ||
| フレーズの移動 | 左右スワイプで移動、移動は選択フレーズとスワイプ中のフレーズを含む | ||
| フレーズの伸縮 | サブ編集領域をスワイプした時にフレーズ長さを伸縮、長さ0で削除(iOS独自) | ||
| フレーズの貼り付け | 選択フレーズをコピー・ペースト、サブボタンかフレーズタブタップで挿入、選択解除 | ||
| 周囲有無判定 | 作成する小節の近くにフレーズがあるかの判定、設定できる長さやリピート回数の制御 | ||
| フレーズボタン・タブ | ツールに応じてフレーズボタン、タブの処理を変更 | ||
| フレーズの結合 | 連続する2フレーズの接する小節線をタップで結合(iOS独自) | ||
| フレーズの分割 | フレーズをタップで分割(iOS独自) | ||
| StampTool スタンプツール
モチーフの編集 | |||
| Transform 画面移動
座標変換 | 画面を上下移動 | ピアノのスワイプにより | |
| 画面を左右移動 | スクロールエリアのスワイプにより | ||
| 画面を拡大・縮小 | ピンチアウト・インにより基点を画面中心に設定する | ||
| 画面を拡大・縮小(軸指定) | 長押しからのドラッグ。ピアノでX軸方向、スクロールエリアでY軸方向 | ||
| MIDI | コンバーター | CompositionからMIDIのデータ構造へ変換 | |
| 曲の再生 | 画面左からMIDI再生、Compositionのデータ構造から曲の再生、再生位置の同期 | ||
| 曲の再生(最初から) | 再生ボタン長押しで最初から再生 | ||
| 曲の再生(位置指定) | スクロール領域をダブルタップでその位置から再生(iOS独自) | ||
| 再生速度の変更 | ドラッグで曲の再生速度を変更(iOS独自) | ||
| 再生処理の最適化 | 曲に変更がない時はキャッシュを使う、変更通知とキャッシュの管理 | ||
| Commnity | |||
|
Common コミュニティ
| データ構造 | カテゴリ等の曲情報、いいねやお気に入り等のリスポンスを保持 | |
| CommunitySongPlayer | コミュニティ用にラップした曲プレイヤー | ||
| CategoryLists | カテゴリーリスト群のモデル。カテゴリー別にフェッチ/キャッシュ | ||
| ListInfo | カテゴリーリストのモデル、リストのスクロール位置、フェッチするページ番号等 | ||
| Context | Community共通のCommonContextと各画面のContextがある | ||
| UserActivityManager | ユーザーのアクティビティ(いいね、ミュートユーザー、再生リスト、モチーフ登録、アクセス履歴等)を管理 | ||
|
Service サービスモデル
| ListItemFetcher | カテゴリーリスト項目のMLサーバーから取得する | |
| MidiRepository | MIDIファイルをサーバーから取得して貯める | ||
| UserActivityFetcher | ユーザーの行動をMLサーバーから取得する(再生数、再生リストに含まれているか、コメント等) | ||
| MIDI | |||
|
Common MIDI共通
| データ構造 | MIDIフォーマットへ出力できる構造 | |
| コントローラー | 曲の読み込みや再生、再生位置の監視や音源の変更などMIDIの扱いを管理する | ||
| サンプラー | ピアノや音符生成時に単体のMidiイベントから音を鳴らす | ||
|
Service サービスモデル
| MidiPlayer | MIDIを再生する | |
| MidiReader | MIDIファイルの読み込み | ||
| MidiWriter | MIDIファイル出力 | ||
| Account | |||
|
Common アカウント
| データ構造 | ログインできる構造、アカウント情報、ログイン情報、MLユーザー情報 | |
| AccountManager | アカウントの管理、ログイン時にMLサーバーと通信 | ||
|
Service サービスモデル
| UserActivityRepository | ユーザーのアクティビティ(いいね、ミュートユーザー、再生リスト、モチーフ登録、アクセス履歴等)を貯める | |
| ServerCommunication | MLサーバーと通信する処理を集約 | ||
| Domain | |||
|
データ構造 Json形式 | Melody | MelodyTrack, MelodyPhrase, NoteContainer, NoteBlock, Note | |
| Drum | DrumTrack, DrumPhrase, BeatContainer, Beat | ||
| 共通化 | Original, Repeat, Syncの3種のPhraseをジェネリッククラスとプロトコルで抽象化 | ||
|
Service サービスモデル
| SongReder | Jsonファイルを読み込み、Domainのデータへ変換 | |
| SongWriter | DomainのデータからJsonファイルへ書き出し | ||
| Common | |||
|
Service サービスモデル
| リポジトリ | 保存データを管理 | |
ざっくり言うとコミュニティ広場(コンテストとユーザーページ関連の部分以外)は大体の実装を完了しました。
細かい部分は色々とありますが。。あとソングビジュアや最近のAndroid追加機能等の実装はリリースしてから少しずつ機能拡充していこうと思います...
実装の詳細
コミュニティView
コミュニティViewの関係図です。

コミュニティを開いた時、エントリーはCommunityRootViewでどの画面CommunitySquareView CommunityUserPageView CommunityContestViewに遷移するかをナビゲートします。各ViewはCommunityViewをベースに派生させており、DisplayView ControllerViewがView構成要素にあります。
例えば、SquareDisplayViewはディスプレイをSquare用に派生させており、コミュニティ広場のカテゴリー説明SquareCategoryDescriptionViewを表示します。曲が選択されている時はSongVisuaViewを表示します。
SquareControllerView はディスプレイをSquare用に派生させており、選択カテゴリーSquareCategoryに応じてリストCategoryListが変わります。新着カテゴリーだと曲カテゴリーリストSongCategoryListに更新され、新着曲項目NewSongCategoryListItemが並びます。
カテゴリーリストCategoryListやカテゴリーリスト項目CategoryListItemは似たようなView構成になるので、全てを1からView構成していくのではなく、同じような部分は共通化して共通Viewを参照するようにしています。
他にも共通のViewはCommonで管理しますが、大きく分けてViewFrameとViewPartsがあります。
ViewFrameは外枠をレイアウトするためのViewで、中身のViewを@ViewBuilderで渡して処理を移譲します。例えば、CategoryListsViewはヘッダーにカテゴリーセレクターCategorySelectorScrollViewがあり、選択カテゴリーに応じてリストCategoryListが表示されます。どのCategoryListを表示するかは派生したViewから処理を移譲されます。また、「横スワイプでカテゴリー変更」や「選択カテゴリータブをタップでリストを一番上まで移動」等どのCategoryListsViewでも使われる共通処理をまとめています。
ViewPartsは詳細/リストボタンやユーザーアイコン、プレイヤーなど各画面固有のViewではなく、共通で使われるViewを集約しています。
なぜ共通化をするのか?
理由はコードを改変しやすいようにするためです。
不具合修正や機能追加するとき、一箇所変更すれば良いのが理想です。同じようなコードが転々とあると、複数の箇所を変えなければならない事態になります。
「この箇所を修正する時はこの箇所も変えなければならない」そんなコードが増えると管理も大変です。そんなコードの関係性を覚えておかないと新たな不具合に繋がることもあります。
そのため、できる限り共通化してコードの再利用性を高めます。
CommunityRootView
ナビゲーション、キーボード表示、通知プレイヤー
ナビゲーションについてはNavigationStackでSquare UserPage Contestのいずれかの画面遷移します。
画面遷移はCommonContextで管理して環境変数に設定するため、どのViewからでもアクセス可能です。
キーボード表示について、コメント編集時に下から迫り上がりますが、デフォルトだとレイアウトが崩れてしまいます。そのため、デフォルト動作は.ignoresSafeArea(.keyboard)でOFFにして、.onChange(of: configEnvironment.keyboardHeight)でキーボードの高さを取得して手動でオフセット.offset(y: screenOffset)しています。
public class ConfigEnvironment: ObservableObject { @Published public var keyboardHeight: CGFloat = 0 private let keyboardWillShow = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) .compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect } .map { $0.height } private let keyboardWillHide = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .map { _ in 0.cgf } public init(){ keyboardWillShow.merge(with: keyboardWillHide).assign(to: &$keyboardHeight) } }
あと、曲が選択された時.onChange(of: context.selectedSong)に通知プレイヤーを更新します。
CommunityView
ディスプレイ+コントローラー+プレイヤー、縦横のレイアウト
CommunityViewはDisplayViewとControllerView、PlayerViewをレイアウトします。
また、縦持ちと横持ちを検出して、レイアウトを変更します。


DisplayViewとControllerViewを各画面で派生させることで、共通ViewCommunityViewへまとめることができます。



プレイヤーPlayerViewは選択曲に応じて、いいねやお気に入りの数を反映する必要があります。ただし、曲の選択はControllerViewの末端で行うため、状態を共有するために環境変数のCommonContextを介します。

DisplayView
説明ビュー / ソングビジュア、リスト詳細切り替えボタン
DisplayViewは曲選択により、説明ビューとソングビジュアが切り替わります。
また、曲選択時はリスト詳細切り替えボタンListDetailToggleButtonが表示され、ControllerViewを曲詳細SongDetailViewに切り替えることができます。


説明ビューは各画面で派生します。
| Square | カテゴリーの説明 |
| UserPage | ユーザーの紹介文 |
| Contest | コンテストの説明 |

ソングビジュアは曲の再生に応じて再生バーを同期させます。
また、曲の情報から新着やランキング等のバッチを表示します。


ContorollerView
カテゴリーリスト / 曲詳細、カテゴリー選択
ContorollerViewはリスト詳細切り替えボタンListDetailToggleButtonにより、CategoryListsViewとSongDetailViewを切り替えるようになってます。


CategoryListsView
CategoryListsViewはカテゴリー選択CategorySelectorScrollViewの下にカテゴリーリストCategoryListがある構成になっています。


どのカテゴリーを使うか、カテゴリーリストに何を表示するかを各画面で派生します。(SquareCategoryListsViewなど)
ジェネリックView
CategorySelectorScrollViewは3画面のカテゴリーに対応できるように型引数があります。
コンストラクタで渡した選択カテゴリーの型を識別してSquareCategory UserPageCategory ContestCategoryのすべてのカテゴリーを並べています。
struct CategorySelectorScrollView<Category: CommunityCategoriable>: View { @Binding var selected: Category public var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 0) { ForEach(Array(Category.allCases), id: \.self) { category in ... } } ...

2種類のコンテキスト
コンテキストはContextとCommonContextがあります。CommonContextはCommunity共通のコンテキストに対して、Contextは各画面ごとのコンテキストになります。
例えば、選択カテゴリーは各画面に状態があるため、Contextにプロパティがあります。
各画面の状態とは、「コミュニティ広場でランキング閲覧中にユーザーページへ推移して再生リストを見た」としたら、選択カテゴリーは複数の状態があるからです。
| 選択カテゴリー | |
|---|---|
| Square | ランキングカテゴリー |
| UserPage | 再生リストカテゴリー |
逆に、プレイヤーの選択曲を管理するコンテキストはCommonContextです。
ユーザーページに推移する場合も、選択曲の再生を止めずに推移したいのでコミュニティ全体で1曲の選択です。ユーザーページで曲を選択して初めて、CommonContextのプレイヤー選択曲を変更します。
CategoryList
CategoryListはリスト表示で上下にスクロールできるViewです。他にも、スクロール時にリスト項目CategoryListItemをサーバーから取得する役割があります。
これらは共通の処理でCategoryPagedScrollViewにまとめています。どのリスト項目を表示するかを各カテゴリーで派生します。


また、カテゴリーによってはヘッダーに特有のリスト項目があったり、特有の機能を持たせたりします。


CategoryListItem
CategoryListItemはリストに表示される項目でカテゴリー毎など様々なViewがあります。
ただ似たようなViewもあるので、できるだけ共通化して派生します。
例えば、以下の4種類の曲リスト項目SongCategoryListItemは中身は一緒なので外枠だけ変えるように作ります。




他にも色々なCategoryListItemがあります。

曲ID

ユーザー

コンテスト
バッチとトロフィー
ちなみに、曲リスト項目に表示しているバッチとトロフィーは少し異なります。
トロフィーの方が小さく表示する想定なのでデザインを簡略化してます。



SongDetailView
SongDetailViewは曲情報SongInfoViewの下に曲コメントリストSongCommentsViewがある構成になっています。


さらに下側にコメントを編集して送るコメントテキストフィールドSongCommentTextFieldViewがあります。

ユーザーアイコン
プレミアムユーザーだとユーザーアイコンにプレミアムリングをつけます。

また、ユーザーアイコンの画像は毎回サーバーから取得するのでは表示が遅れてしまうため、表示した画像はNukeライブラリでキャッシュしてます。
import NukeUI struct AccountIconView: View { ... var body: some View { ... LazyImage(iconUrl: iconUrl, transaction: .init(animation: .easeIn)) { state in ... } .processors([.resize(width: shortSide)]) .priority(.high) .pipeline(pipeline) } }
なお、Nukeライブラリでキャッシュする時の注意点ですが、ImagePipelineは同じインスタンスを使わないと適切に動作しません。
なぜかメモリが溜まり続ける不具合がありましたが、描画するたびにImagePipelineインスタンス作成していることが原因でした。
.pipeline(ImagePipeline(configuration: .withDataCache))
これでは内部のコンフィグが変わるため、同じURLでも同一画像と認識せずに新たなキャッシュが溜まり続けていました。
ListDialog
曲情報SongInfoViewのユーザーアクションボタンUserActionButtonsをタップすることでリストダイアログListDialogを表示します。



共通ダイアログ
ダイアログもコンテキスト同様にDialogsとCommonDialogsがあります。CommonDialogsはCommunity共通、Dialogsは各画面ごとに紐づくダイアログを管理しています。
例えば、リストダイアログは各画面に状態があるため、Dialogsにアクセスしてダイアログを表示します。
「曲のいいねユーザーリストを開いて、ユーザーページに推移した」としたら、ユーザーページから曲詳細画面に戻る時には、ユーザーリストを開いた状態をキープしておいてほしいですね。
逆に「ダウンロードのメッセージを表示する」ダイアログはCommonDialogsです。
メッセージはダウンロードした時に遅延して開かれますが、「広場で曲DLして、ユーザーページに推移した」としたら、複数の画面に同じメッセージを表示する必要はないです。

おわりに
正直ちょっと一般ユーザー向けの内容じゃないですね。
内部の認識合わせにも使っているので、妙に詳細な部分とかもあります。
とはいえ公開ブログなので読んで頂いているmL民は、
δ(・ω・`)ん? もしくは ぽかーん( ゚д゚) となっているかと思います!!
ただ、とりあえずリリースに向けてiOS開発頑張っています。
そのことだけでも伝われば嬉しいです🙂
この夏リリース予定でしたが、楽しみにしていたユーザーの皆様本当に申し訳ございません!!
あと1年くらいはかかりそうです🙇
iOSでも存在感のあるアプリに仕上げようと日々磨いていますので、気長に待ってもらえると幸いです😭