セクション 107
設定シーンでお好みの設定を追加
ユーザは、標準の [Preference(お好みの設定)] メニュー項目を使用して macOS アプリの設定が調整できるのを期待しています。[Setting(設定)] シーンを追加して、MacLandmarks にお好みの設定を追加して下さい。シーンのビューは、MapView の初期ズームレベルを制御するために使用するお好みの設定ウィンドウのコンテンツを定義します。@AppStorage プロパティラッパーを使用して、値をマップビューに伝達し、それを永続的に保存します。
まず、初期ズームを近、中、遠の 3 つのレベルのいずれか1つに設定するコントロールを MapView に追加します。
ステップ 1
MapView.swift 内で、Zoom 列挙体を追加してズームレベルを特徴付けます。
MapView.swift
- import SwiftUI
- import MapKit
- struct MapView: View {
var coordinate: CLLocationCoordinate2D
@State private var region = MKCoordinateRegion()
enum Zoom: String, CaseIterable, Identifiable {
case near = "Near"
case medium = "Medium"
case far = "Far"
var id: Zoom {
return self
}
}
var body: some View {
Map(coordinateRegion: $region)
.onAppear {
setRegion(coordinate)
}
}
private func setRegion(_ coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
}
- }
- struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868))
}
- }
ステップ 2
デフォルトで medium のズームレベルをとる zoom と呼ばれる @AppStorage プロパティを追加します。
SwiftUI が依存する基本的なメカニズムであるため、UserDefaults にアイテムを保存するときと同じように、パラメータを一意に識別する保存用キーを使用します。
MapView.swift
@State private var region = MKCoordinateRegion()
@AppStorage("MapView.zoom")
private var zoom: Zoom = .medium
enum Zoom: String, CaseIterable, Identifiable {
case near = "Near"
ステップ 3
region プロパティの作成に使用される経度と緯度のデルタを、ズームに依存する値に変更します。
MapView.swift
var id: Zoom {
return self
}
}
var delta: CLLocationDegrees {
switch zoom {
case .near: return 0.02
case .medium: return 0.2
case .far: return 2
}
}
var body: some View {
Map(coordinateRegion: $region)
.onAppear {
setRegion(coordinate)
}
}
private func setRegion(_ coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: delta, longitudeDelta: delta)
)
}
- }
Delta が変更されるたびに SwiftUI がマップを更新するようにするには、region の計算方法と適用方法を変更しなければなりません。
ステップ 4
region 状態変数、setRegion メソッド、および map の onAppear 修飾子を、定数拘束として Map イニシャライザに渡す計算された region プロパティに置き換えます。
MapView.swift
var body: some View {
Map(coordinateRegion: .constant(region))
}
var region: MKCoordinateRegion {
MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: delta, longitudeDelta: delta)
)
}
- }
次に、保存されている zoom の値を制御する Setting のシーンを作成します。
ステップ 5
macOS アプリのみをターゲットとする LandmarkSettings という新しい SwiftUI ビューを作成します。
LandmarkSettings.swift
- import SwiftUI
- struct LandmarkSettings: View {
var body: some View {
Text("Hello, World!")
}
- }
- struct LandmarkSettings_Previews: PreviewProvider {
static var previews: some View {
LandmarkSettings()
}
- }
ステップ 6
マップビューで使用したものと同じキーを使用する @AppStorage プロパティを追加します。
LandmarkSettings.swift
- import SwiftUI
- struct LandmarkSettings: View {
@AppStorage("MapView.zoom")
private var zoom: MapView.Zoom = .medium
var body: some View {
Text("Hello, World!")
}
- }
ステップ 7
結束を介して zoom の値をコントロールする Picker を追加します。
通常、Form を使用して、設定ビューにコントロールを配置して下さい。
プレビューその 1
LandmarkSettings.swift
private var zoom: MapView.Zoom = .medium
var body: some View {
Form {
Picker("Map Zoom:", selection: $zoom) {
ForEach(MapView.Zoom.allCases) { level in
Text(level.rawValue)
}
}
.pickerStyle(InlinePickerStyle())
}
.frame(width: 300)
.navigationTitle("Landmark Settings")
.padding(80)
}
- }
ステップ 8
LandmarksApp.swift 内で、Setting のシーンをアプリに macOS の場合に限り追加します。
LandmarksApp.swift
- import SwiftUI
- @main
- struct LandmarksApp: App {
@StateObject private var modelData = ModelData()
var body: some Scene {
let mainWindow = WindowGroup {
ContentView()
.environmentObject(modelData)
}
#if os(macOS)
mainWindow
.commands {
LandmarkCommands()
}
#else
mainWindow
#endif
#if os(watchOS)
WKNotificationScene(controller: NotificationController.self, category: "LandmarkNear")
#endif
#if os(macOS)
Settings {
LandmarkSettings()
}
#endif
}
- }
ステップ 9
アプリを実行し、お好みの設定を設定してみてください。
zoom のレベルを変更するたびにマップが更新されることに注意してください。
あなたの理解度をチェックしてください
質問 1
ランドマークのアプリがモデル内ではなくビュー内で filteredLandmarks 配列を定義するのはなぜですか?
(訳注: 原典と異なり、選択肢を選ぶとすぐ答えが表示されます)
質問 2
次のコードでは、ピッカー選択型はどのプロトコルに準拠しなければなりませんか?
Picker("Choose a Value", selection: $selection) {
ForEach(Choices.allCases) { choice in
Text(choice.name).tag(choice)
}
}
質問 3
次の例のうち、selectedLandmark の結束をリストの現在選択されているアイテムに正しく接続しているのはどれですか?
質問 4
macOS アプリのトップレベルメニューにアイテムを追加するにはどうすればよいですか?