#author("2021-07-29T08:38:30+00:00","default:src128","src128") #author("2021-07-29T08:42:13+00:00","default:src128","src128") &tag(SwiftUI/BuildingListsAndNavigation); *目次 [#ac7c317e] #contents *関連ページ [#j7b818d6] *参考情報 [#q2213c87] -[[Building Lists and Navigation — SwiftUI Tutorials | Apple Developer Documentation:https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation]] -CreatingAndCombiningViewsの最後のLandmarkプロジェクトをコピーして使用。 -Resourcesフォルダはダウンロードした奴を上書きしたほうが良いかも。 *Create a Landmark Model [#zd6e543b] -データを保持するためのモデルを作成する。 -landmarkData.jsonをプロジェクトのResourcesフォルダ以下にコピー。 -New > FileでLandmark.swiftを追加。 -以下のように定義する。 #pre{{ struct Landmark: Hashable, Codable { var id: Int var name: String var park: String var state: String var description: String } }} -画像の関連付け #pre{{ struct Landmark: Hashable, Codable { var id: Int var name: String var park: String var state: String var description: String private var imageName: String var image: Image { Image(imageName) } } }} -coordinatesを追加。最終的に以下のように。 #pre{{ struct Landmark: Hashable, Codable { var id: Int var name: String var park: String var state: String var description: String private var imageName: String var image: Image { Image(imageName) } private var coordinates: Coordinates var locationCoordinate: CLLocationCoordinate2D { CLLocationCoordinate2D( latitude: coordinates.latitude, longitude: coordinates.longitude) } struct Coordinates: Hashable, Codable { var latitude: Double var longitude: Double } } }} -ModelData.swiftを追加。load関数を作成する。 #pre{{ var landmarks: [Landmark] = load("landmarkData.json") func load<T: Decodable>(_ filename: String) -> T { let data: Data guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Couldn't find \(filename) in main bundle.") } do { data = try Data(contentsOf: file) } catch { fatalError("Couldn't load \(filename) from main bundle:\n\(error)") } do { let decoder = JSONDecoder() return try decoder.decode(T.self, from: data) } catch { fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") } }} -ViewsグループにContentView.swift、CircleImage.swift、MapView.swiftを追加。ResourcesグループにlandmarkData.jsonを追加。ModelグループにLandmark.swiftとModelData.swiftを追加。 *Create the Row View [#b33d2799] -LandmarkRow.swiftを追加。以下のように作成。 #pre{{ struct LandmarkRow: View { var landmark: Landmark var body: some View { HStack { landmark.image .resizable() .frame(width: 50, height: 50) Text(landmark.name) Spacer() } } } struct LandmarkRow_Previews: PreviewProvider { static var previews: some View { LandmarkRow(landmark: landmarks[0]); } } }} *Customize the Row Preview [#h9c2b5ac] -プレビューの変更 #pre{{ struct LandmarkRow_Previews: PreviewProvider { static var previews: some View { LandmarkRow(landmark: landmarks[1]).previewLayout(.fixed(width: 300, height: 70)) } } }} -二つ表示 #pre{{ Group { LandmarkRow(landmark: landmarks[0]) .previewLayout(.fixed(width: 300, height: 70)) LandmarkRow(landmark: landmarks[1]) .previewLayout(.fixed(width: 300, height: 70)) } }} *Create the List of Landmarks [#kf86c1ab] リストビューを実装する。 -SwiftUIファイルのLandmarkList.swiftを作成。 #pre{{ struct LandmarkList: View { var body: some View { List { LandmarkRow(landmark: landmarks[0]) LandmarkRow(landmark: landmarks[1]) } } } }} *Make the List Dynamic [#h089389e] Listにコレクションを表示。 -LandmarkListを変更。 #pre{{ struct LandmarkList: View { var body: some View { List(landmarks, id: \.id) { landmark in LandmarkRow(landmark: landmark) } } }} -もしくはLandmarkを変更してIdentifiableを実装。これでプレビュー画面にリストが表示される。 *Set Up Navigation Between List and Detail [#i2b23d8a] リストと詳細をナビゲートできるようにする。 -SwiftUI ViewのLandmarkDetailを追加。 -ContentViewの内容を移植。 #pre{{ struct LandmarkDetail: View { var body: some View { VStack { MapView() .ignoresSafeArea(edges: .top) .frame(height: 300) CircleImage() .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) .foregroundColor(.primary) HStack { Text("Joshua Tree National Park") Spacer() Text("California") } .font(.subheadline) .foregroundColor(.secondary) Divider() Text("About Turtle Rock") .font(.title2) Text("Descriptive text goes here.") } .padding() Spacer() } } } }} -ContentViewでLandmarkListを生成。 #pre{{ struct ContentView: View { var body: some View { LandmarkList() } } }} -LandmarkListの中にNavigationViewを入れる。プレビューの上部に空白ができる。タイトルも追加。 #pre{{ struct LandmarkList: View { var body: some View { NavigationView { List(landmarks) { landmark in NavigationLink(destination: LandmarkDetail()) { LandmarkRow(landmark: landmark) } } .navigationTitle("Landmarks") } } } -ライブプレビューで遷移を確認可能。 }} *Pass Data into Child Views [#oaf4708d] 詳細ビューにデータを引き渡す。 -CircleImageでimageを格納する。 #pre{{ struct CircleImage: View { var image: Image var body: some View { image .clipShape(Circle()) .overlay(Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 7) } } struct CircleImage_Previews: PreviewProvider { static var previews: some View { CircleImage(image: Image("turtlerock")) } } }} -MapViewにcoordinateプロパティを追加。 #pre{{ struct MapView: View { var coordinate: CLLocationCoordinate2D @State private var region = MKCoordinateRegion() 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)) } } }} -LandmarkDetailにlandmarkプロパティを追加。修正。 #pre{{ struct LandmarkDetail: View { var landmark: Landmark var body: some View { ScrollView { MapView(coordinate: landmark.locationCoordinate) .ignoresSafeArea(edges: .top) .frame(height: 300) CircleImage(image: landmark.image) .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text(landmark.name) .font(.title) .foregroundColor(.primary) HStack { Text(landmark.park) Spacer() Text(landmark.state) } .font(.subheadline) .foregroundColor(.secondary) Divider() Text("About \(landmark.name)") .font(.title2) Text(landmark.description) } .padding() } .navigationTitle(landmark.name) .navigationBarTitleDisplayMode(.inline) } } struct LandmarkDetail_Previews: PreviewProvider { static var previews: some View { Group { LandmarkDetail(landmark: landmarks[0]) } } } }} *Generate Previews Dynamically [#ofc48995] プレビューを修正する。 -LandmarkListを修正。まとめてプレビューできる。 #pre{{ struct LandmarkList: View { var body: some View { NavigationView { List(landmarks) { landmark in NavigationLink(destination: LandmarkDetail(landmark: landmark)) { LandmarkRow(landmark: landmark) } } .navigationTitle("Landmarks") } } } struct LandmarkList_Previews: PreviewProvider { static var previews: some View { ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in LandmarkList() .previewDevice(PreviewDevice(rawValue: deviceName)) .previewDisplayName(deviceName) } } } }}