さいしょに
MacOS向けのアプリをSwiftUIで作っていて、グループリスト > コンテンツリスト > 詳細と3セクションに分けることにしました。各セクションのViewはサブViewにして、ObservedObjectとしてデータを渡すことにします。
NavigationViewは非推奨になったので、機能としてsidebar、content、deatilと3セクションに分けられるNavigationSplitViewを使うことにしました。
NavigationSplitViewを使った最初の実装
例として、データモデルは以下の通りで、GroupとContentが1:多、ContentとContentDeailが1:多とします。
- Group
- Content
- ContentDetail
これら3つをNavigationSplitViewに表示させる実装は以下の通りです。
いろいろ省略していますが、List(groupList, $selectedGroup)
でgroupList
に入っているGroupをリスト表示して、ユーザーがリストの項目を選択すると、その選択した項目に対応するGroupインスタンスが$selectedGroup
に代入されます。
同じことをContentViewやContentDetailViewで行いたいので、それぞれのViewにObservedObjectとして渡していきます。
ただ、ここでハマったことがありました。
@State private var selectedGroup: Group? = nil @State private var selectedContent: Content? = nil NavigationSplitView { List(groupList, $selectedGroup) { group in NavigationLink(value: group) { Text(group.name) } } } content: { ContentListView(group: selectedGroup, selected: $selectedContent) // ContentViewのinit // init(group: Group?, selected: Binding<Content?>) { // _selectedGroup = ObservedObject(initialValue: group) // _selectedContent = selected // } // ContentViewの var body: View{}の中身 // List(group.contentList, $selectedContent) { content in // NavigationLink(value: content) { // Text(content.name) // } // } } detail: { ContentsDetailView(selected: selectedContent) // ContentDetailViewのinit // init(content: Content?) { // _selectedContent = ObservedObject(initialValue: content) // } // ContentDetailViewの var body: View{}の中身 // List(content.detailList) { detail in // Text(detail.name) // } }
それは、List(data:, selection:)
のselection:
に渡すBindingであるselectedGroup: Group?
とselectedContent: Content?
がオプショナル型であるために、ObservedObject(initialValue)
で「アンラップしてね」とエラーになるので、その処理をどうするか?ということです。
まず、サブViewのinit()
でif let
を使いnil
だったらダミーインスタンスを表示させようとしましたが、そもそもダミーインスタンスってどこから持って来るの?としばらく悩んでしまいました。
そして「いや落ち着け」と、ふと我に返って以下のように、サブViewに渡す前にif let
するようにしました。なんだかしょうもないことで悩んでいたように思いました。
@State private var selectedGroup: Group? = nil @State private var selectedContent: Content? = nil NavigationSplitView { List(groupList, $selectedGroup) { group in NavigationLink(value: group) { Text(group.name) } } } content: { // 渡す前に確認する if let choice = selectedGroup { ContentListView(group: choice, selected: $selectedContent) } else { // nilだったらContentセクションに指示を表示する Text("グループを選択してください") } } detail: { // 渡す前に確認する if let choice = selectedContent { ContentsDetailView(selected: choice) } else { // nilだったらDetailセクションに指示を表示する Text("コンテンツを選択してください") } }