セクション 106
カスタムメニューコマンドを追加
以前のセクションでは、組み込みのメニューコマンドセットを追加しました。このセクションでは、現在選択されているランドマークのお気に入りのステータスを切り替えるためのカスタムコマンドを追加します。現在選択されているランドマークがどれかを知るには、フォーカスされた結合を使用して下さい。
ステップ 1
LandmarkCommands 内で、SelectedLandmarkKey と呼ばれるカスタムキーを使用して、FocusedValues 構造体を selectedLandmark の値で拡張します。
フォーカスされた値を定義するためのパターンは、新しい Environment の値を定義するためのパターンに似ています。秘密鍵を使用して、システム定義の FocusedValues 構造体のカスタムプロパティを読み書きします。
LandmarkCommands.swift
- import SwiftUI
- struct LandmarkCommands: Commands {
var body: some Commands {
SidebarCommands()
}
- }
- private struct SelectedLandmarkKey: FocusedValueKey {
typealias Value = Binding<.<Landmark>
- }
- extension FocusedValues {
var selectedLandmark: Binding<Landmark>? {
get { self[SelectedLandmarkKey.self] }
set { self[SelectedLandmarkKey.self] = newValue }
}
- }
ステップ 2
@FocusedBinding プロパティラッパーを使用して現在選択されているランドマークを追跡する MenuContent ビューを作成します。
ここで値を読みましょう。後でリストビュー内で設定し、ユーザが選択します。
LandmarkCommands.swift
- import SwiftUI
- struct LandmarkCommands: Commands {
private struct MenuContent: View {
@FocusedBinding(\.selectedLandmark) var selectedLandmark
var body: some View {
}
- }
var body: some Commands {
SidebarCommands()
ステップ 3
選択したランドマークのお気に入りのステータスを切り替えるボタンをビューに追加します。このボタンは、現在選択されているランドマークとその状態に応じて外観が変わります。
LandmarkCommands.swift
private struct MenuContent: View {
@FocusedBinding(\.selectedLandmark) var selectedLandmark
var body: some View {
Button("\(selectedLandmark?.isFavorite == true ? "Remove" : "Mark") as Favorite") {
selectedLandmark?.isFavorite.toggle()
}
.disabled(selectedLandmark == nil)
}
}
ステップ 4
keyboardShortcut(_:modifiers:) 修飾子を使用してメニュー項目のキーボードショートカットを追加します。
SwiftUI は、メニュー内にキーボードショートカットを自動的に表示します。
LandmarkCommands.swift
Button("\(selectedLandmark?.isFavorite == true ? "Remove" : "Mark") as Favorite") {
selectedLandmark?.isFavorite.toggle()
}
.keyboardShortcut("f", modifiers: [.shift, .option])
.disabled(selectedLandmark == nil)
}
}
ステップ 5
定義したメニューコンテンツを使用する Landmarks と呼ばれるコマンドに新しい CommandMenu を追加します。
LandmarkCommands.swift
var body: some Commands {
SidebarCommands()
CommandMenu("Landmark") {
MenuContent()
}
}
- }
これでメニューに新しいコマンドが含まれますが、それを機能させるには、selectedLandmark をフォーカスバインディングされたように設定する必要があります。
ステップ 6
LandmarkList.swift 内で、選択したランドマークの状態変数と、選択したランドマークのインデックスを示す計算されたプロパティを追加します。
LandmarkList.swift
- import SwiftUI
- struct LandmarkList: View {
@EnvironmentObject var modelData: ModelData
@State private var showFavoritesOnly = false
@State private var filter = FilterCategory.all
@State private var selectedLandmark: Landmark?
enum FilterCategory: String, CaseIterable, Identifiable {
case all = "All"
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
var id: FilterCategory { self }
}
var filteredLandmarks: [Landmark] {
modelData.landmarks.filter { landmark in
(!showFavoritesOnly || landmark.isFavorite)
&& (filter == .all || filter.rawValue == landmark.category.rawValue)
}
}
var title: String {
let title = filter == .all ? "Landmarks" : filter.rawValue
return showFavoritesOnly ? "Favorite \(title)" : title
}
var index: Int? {
modelData.landmarks.firstIndex(where: { $0.id == selectedLandmark?.id })
}
var body: some View {
NavigationView {
List {
ForEach(filteredLandmarks) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.navigationTitle(title)
.frame(minWidth: 300)
.toolbar {
ToolbarItem {
Menu {
Picker("Category", selection: $filter) {
ForEach(FilterCategory.allCases) { category in
Text(category.rawValue).tag(category)
}
}
.pickerStyle(InlinePickerStyle())
Toggle(isOn: $showFavoritesOnly) {
Label("Favorites only", systemImage: "star.fill")
}
} label: {
Label("Filter", systemImage: "slider.horizontal.3")
}
}
}
Text("Select a Landmark")
}
}
- }
- struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
.environmentObject(ModelData())
}
- }
ステップ 7
選択した値に結束して List を初期化し、ナビゲーションリンクにタグを追加します。
タグは、特定のランドマークを ForEach 内の与えられた項目に関連付け、それは選択を促進します。
LandmarkList.swift
var body: some View {
NavigationView {
List(selection: $selectedLandmark) {
ForEach(filteredLandmarks) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.navigationTitle(title)
ステップ 8
landmarks 配列からの値の結束を提供するために、focusedValue(_:_:) 修飾子を NavigationView に追加します。
ここでルックアップを実行して、コピーではなく、モデルに保存されているランドマークを変更していることを確認します。
LandmarkList.swift
Text("Select a Landmark")
}
.focusedValue(\.selectedLandmark, $modelData.landmarks[index ?? 0])
}
- }
- struct LandmarkList_Previews: PreviewProvider {
ステップ 9
macOS アプリを実行して、新しいメニュー項目を試してください。