Sponsor Link
目次
環境&対象
- macOS Big Sur 11.2.2
- Xcode 12.4
- iOS 14.4
.toolbar の肥大化
アプリの機能を充実させていくと、Toolbar が肥大化してきます。
UNDO/REDO, Edit ボタン、・・・ View の本体と同じくらいの長さになることも。
Toolbar の整理方法を説明します。
長い .toolbar の例
以下の例を使います。別記事向けに書いている TODO アプリのビューですが、ビュー本体よりも、toolbar の記述の方が長くなってます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
struct LucidListView: View { var viewModel: LucidMobileViewModel @Binding var todoModel: TODOModel @State private var showNewItemSheet = false var body: some View { NavigationView { VStack { List { ForEach(todoModel.sortedTODOItems, id: \.id) { item in Text(item.title) } } } .navigationTitle("TODO") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { EditButton() } ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewItemSheet.toggle() }, label: { Label(title: { Text("AddItem") }, icon: { Image(systemName: "plus") }) }) } ToolbarItemGroup(placement: .bottomBar) { Button(action: { viewModel.undo() }, label: { Text("UNDO") }) .disabled(!viewModel.canUndo) Button(action: { viewModel.redo() }, label: { Text("REDO") }) .disabled(!viewModel.canRedo) } } .sheet(isPresented: $showNewItemSheet) { NewTODOItem(viewModel: viewModel, showSheet: $showNewItemSheet) } } .padding(.horizontal) } } |
機能的には、NavigationBar の左上に Edit ボタン、右上に 追加の + ボタン。
BottomBar には、UNDO/REDO のボタンを追加しているコードです。
普通に必要になりそうな機能なのですが、それだけで、toolbar は、こんなに長くなってしまいます。
39行の body のうち、21行が toolbar の記述です。toolbar はある意味定型文なので、不必要に長いとコードの見通しが悪くなりメンテナンス性が下がってしまいます。
ToolbarItem の切り出し
どう切り出すかは、その後どのように再利用するかという設計依存ですが、ここでは、「左上に表示される EditButton 」として、切り出してみます。
カスタム ToolbarContent の定義
View を切り出して再利用できるのと同様に、Toolbar もカスタムな ToolbarContent を定義して再利用できます。
1 2 3 4 5 6 7 8 9 10 |
struct NavBarLeadingEdit: ToolbarContent { var navBarLeadingEdit: some ToolbarContent { ToolbarItem(placement: .navigationBarLeading) { EditButton() } } } |
使用する側のコードは、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
struct LucidListView: View { var viewModel: LucidMobileViewModel @Binding var todoModel: TODOModel @State private var showNewItemSheet = false var body: some View { NavigationView { VStack { List { ForEach(todoModel.sortedTODOItems, id: \.id) { item in Text(item.title) } } } .navigationTitle("TODO") .navigationBarTitleDisplayMode(.inline) .toolbar { NavBarLeadingEdit() // <- 1行にまとまりました ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewItemSheet.toggle() }, label: { Label(title: { Text("AddItem") }, icon: { Image(systemName: "plus") }) }) } ToolbarItemGroup(placement: .bottomBar) { Button(action: { viewModel.undo() }, label: { Text("UNDO") }) .disabled(!viewModel.canUndo) Button(action: { viewModel.redo() }, label: { Text("REDO") }) .disabled(!viewModel.canRedo) } } .sheet(isPresented: $showNewItemSheet) { NewTODOItem(viewModel: viewModel, showSheet: $showNewItemSheet) } } .padding(.horizontal) } } |
NavBarLeadingEdit として、別のビューでも簡単に再利用することができます。
struct 内変数として定義する Toolbar
外部に struct として切り出すまでも無い時には、struct の内部変数として定義することもできます。
例えば、現在のビューにしか関連しない 要素追加ボタンは、struct にしてもあまり再利用しない気がします。
このような時には、(Viewの) struct の内部変数にまとめてしまうこともできます。
UNDO/REDO も同様に内部変数にまとめました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var navBarTrailingAdd: some ToolbarContent { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewItemSheet.toggle() }, label: { Label(title: { Text("AddItem") }, icon: { Image(systemName: "plus") }) }) } } var bottombarUndoRedo: some ToolbarContent { var body: some ToolbarContent { ToolbarItemGroup(placement: .bottomBar) { Button(action: { viewModel.undo() }, label: { Text("UNDO") }) .disabled(!viewModel.canUndo) Button(action: { viewModel.redo() }, label: { Text("REDO") }) .disabled(!viewModel.canRedo) } } } |
上記の変数を使う側は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
struct LucidListView: View { var viewModel: LucidMobileViewModel @Binding var todoModel: TODOModel @State private var showNewItemSheet = false var body: some View { NavigationView { VStack { List { ForEach(todoModel.sortedTODOItems, id: \.id) { item in Text(item.title) } } } .navigationTitle("TODO") .navigationBarTitleDisplayMode(.inline) .toolbar { NavBarLeadingEdit() navBarTrailingAdd // <-- それぞれ 変数定義された toolbar が展開されます bottombarUndoRedo } .sheet(isPresented: $showNewItemSheet) { NewTODOItem(viewModel: viewModel, showSheet: $showNewItemSheet) } } .padding(.horizontal) } ... |
struct の内部変数として定義すると、「別ビューでの再利用は難しい」というデメリットがありますが、「必要な情報には、そのままアクセスできる」というメリットもあります。
Toolbar 定義をまとめた最終形
カスタム ToolbarContent 定義と struct 内変数として定義した ToolbarContent を組み合わせたコードは、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
struct LucidListView: View { var viewModel: LucidMobileViewModel @Binding var todoModel: TODOModel @State private var showNewItemSheet = false var body: some View { NavigationView { VStack { List { ForEach(todoModel.sortedTODOItems, id: \.id) { item in Text(item.title) } } } .navigationTitle("TODO") .navigationBarTitleDisplayMode(.inline) .toolbar { NavBarLeadingEdit() navBarTrailingAdd bottombarUndoRedo } .sheet(isPresented: $showNewItemSheet) { NewTODOItem(viewModel: viewModel, showSheet: $showNewItemSheet) } } .padding(.horizontal) } var navBarTrailingAdd: some ToolbarContent { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewItemSheet.toggle() }, label: { Label(title: { Text("AddItem") }, icon: { Image(systemName: "plus") }) }) } } var bottombarUndoRedo: some ToolbarContent { var body: some ToolbarContent { ToolbarItemGroup(placement: .bottomBar) { Button(action: { viewModel.undo() }, label: { Text("UNDO") }) .disabled(!viewModel.canUndo) Button(action: { viewModel.redo() }, label: { Text("REDO") }) .disabled(!viewModel.canRedo) } } } } struct NavBarLeadingEdit: ToolbarContent { var navBarLeadingEdit: some ToolbarContent { ToolbarItem(placement: .navigationBarLeading) { EditButton() } } } |
全体で考えるとコードが少なくなってはいませんが、ビューの body 部分は、39行もあった body が 22行にまで減り、ずいぶん見通しがよくなりました。
ビュー struct 内部に定義されていると、ビューと合わせてアップデートされますが、外部 struct 化すると変更は伝播されないようです。
(2021.3.8 時点)
まとめ:toolbar のまとめ方
- struct MyOwnToolbar: some ToolBarContent として、カスタムツールバーを定義する
- var myownToolbar: some ToolbarContent として、ビュー内で、カスタムツールバーを定義する
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link