リスト表示
チュートリアルより
まず静的なJSONのデータ
[ { "name": "Turtle Rock", "category": "Featured", "city": "Twentynine Palms", "state": "California", "id": 1001, "park": "Joshua Tree National Park", "coordinates": { "longitude": -116.166868, "latitude": 34.011286 }, "imageName": "turtlerock" }, {...} ]
がある。これをモデルに読み込む。モデルは
import SwiftUI import CoreLocation struct Landmark: Hashable, Codable, Identifiable { var id: Int var name: String fileprivate var imageName: String fileprivate var coordinates: Coordinates var state: String var park: String var category: Category var locationCoordinate: CLLocationCoordinate2D { CLLocationCoordinate2D( latitude: coordinates.latitude, longitude: coordinates.longitude) } enum Category: String, CaseIterable, Codable, Hashable { case featured = "Featured" case lakes = "Lakes" case rivers = "Rivers" } } extension Landmark { var image: Image { ImageStore.shared.image(name: imageName) } } struct Coordinates: Hashable, Codable { var latitude: Double var longitude: Double }
でCodableに対応しているのでそのまま読み込める。 読み込むところはJSONDecoderというのを用いる。
import UIKit import SwiftUI import CoreLocation let landmarkData: [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)") } } final class ImageStore { typealias _ImageDictionary = [String: CGImage] fileprivate var images: _ImageDictionary = [:] fileprivate static var scale = 2 static var shared = ImageStore() func image(name: String) -> Image { let index = _guaranteeImage(name: name) return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(name)) } static func loadImage(name: String) -> CGImage { guard let url = Bundle.main.url(forResource: name, withExtension: "jpg"), let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else { fatalError("Couldn't load image \(name).jpg from main bundle.") } return image } fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index { if let index = images.index(forKey: name) { return index } images[name] = ImageStore.loadImage(name: name) return images.index(forKey: name)! } }
リストに表示するセルに対応するものは
import SwiftUI 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 { Group { LandmarkRow(landmark: landmarkData[0]) LandmarkRow(landmark: landmarkData[1]) } .previewLayout(.fixed(width: 300, height: 70)) } }
のように簡単につくってプレビューもみることができる。 プレビューでは2つのセルを表示していてGroupでまとめてある。 これをリストに表示する。
import SwiftUI struct LandmarkList: View { var body: some View { NavigationView { List(landmarkData) {landmark in NavigationLink(destination: LandmarkDetail(landmark: landmark)) { LandmarkRow(landmark:landmark) } } .navigationBarTitle(Text("Landmarks")) } } } struct LandmarkList_Previews: PreviewProvider { static var previews: some View { ForEach(["iPhone SE","iPhone XS Max"], id: \.self) { deviceName in LandmarkList() .previewDevice(PreviewDevice(rawValue: deviceName)) .previewDisplayName(deviceName) } } }
ここで、プレビューを色々なデバイスで同時にプレビューできるようにしている。 また、NavigationLinkに詳細画面に飛ばしている。その際にデータを渡している点に注目。
詳細画面は
import SwiftUI struct LandmarkDetail: View { var landmark:Landmark var body: some View { VStack { MapView(coordinate: landmark.locationCoordinate) .edgesIgnoringSafeArea(.top) .frame(height: 300) CircleImage(image: landmark.image) .offset(x: 0, y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text(landmark.name) .font(.title) HStack(alignment: .top) { Text(landmark.park) .font(.subheadline) Spacer() Text(landmark.state) .font(.subheadline) } } .padding() Spacer() } .navigationBarTitle(Text(landmark.name), displayMode: .inline) } } struct LandmarkDetail_Previews: PreviewProvider { static var previews: some View { LandmarkDetail(landmark:landmarkData[0]) } }