アイテムの主要なリストを作成する
Building a primary list of items
このアプリでは、Appleのメールアプリやメモアプリと同じように、2つのビューを並べて表示します。
アプリのメインビューを作成することからプロジェクトを開始します。これには、すべてのスキーリゾートのリスト、スキー場の出身国、スキー場の数、スキー場の数が表示されます。 「トレイル」または単に「スロープ」と呼ばれることもあります。
// // Resort.swift // SnowSeeker import Foundation struct Resort: Codable, Identifiable { let id: String let name: String let country: String let description: String let imageCredit: String let price: Int let size: Int let snowDepth: Int let elevation: Int let runs: Int let facilities: [String] //Bundle-Decodable.swift関連 static let allResorts: [Resort] = Bundle.main.decode("resorts.json") static let example = allResorts[0] //以下も有り //static let example = (Bundle.main.decode("resorts.json") as [Resort])[0] }
// // Bundle-Decodable.swift // SnowSeeker import Foundation extension Bundle { func decode<T: Decodable>(_ file: String) -> T { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else { fatalError("Failed to load \(file) from bundle.") } let decoder = JSONDecoder() guard let loaded = try? decoder.decode(T.self, from: data) else { fatalError("Failed to decode \(file) from bundle.") } return loaded } }
import SwiftUI struct ContentView: View { let resorts: [Resort] = Bundle.main.decode("resorts.json") var body: some View { NavigationView { List(resorts) { resort in NavigationLink(destination: Text(resort.name)) { Image(resort.country) .resizable() .scaledToFill() .frame(width: 40, height: 25) .clipShape( RoundedRectangle(cornerRadius: 5) ) .overlay( RoundedRectangle(cornerRadius: 5) .stroke(Color.black, lineWidth: 1) ) VStack(alignment: .leading) { Text(resort.name) .font(.headline) Text("\(resort.runs) runs") .foregroundColor(.secondary) } } } .navigationBarTitle("Resorts") } } }
NavigationViewを横向きで機能させる
Making NavigationView work in landscape
NavigationViewのセカンダリビューの作成
Creating a secondary view for NavigationView
// // SkiDetailsView.swift // SnowSeeker import SwiftUI struct SkiDetailsView: View { let resort: Resort var body: some View { VStack { Text("Elevation: \(resort.elevation)m") Text("Snow: \(resort.snowDepth)cm") } } } struct SkiDetailsView_Previews: PreviewProvider { static var previews: some View { SkiDetailsView(resort: Resort.example) } }
// // ResortDetailsView.swift // SnowSeeker // // Created by Naoki Abe on 2020/09/20. // Copyright © 2020 Naoki Abe. All rights reserved. // import SwiftUI struct ResortDetailsView: View { let resort: Resort // var size: String { // ["Small", "Average", "Large"][resort.size - 1] // } var size: String { switch resort.size { case 1: return "Small" case 2: return "Average" default: return "Large" } } var price: String { String(repeating: "$", count: resort.price) } var body: some View { VStack { Text("Size: \(size)") Text("Price: \(price)") } } } struct ResortDetailsView_Previews: PreviewProvider { static var previews: some View { ResortDetailsView(resort: Resort.example) } }
// // ResortView.swift // SnowSeeker import SwiftUI struct ResortView: View { let resort: Resort var body: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { Image(decorative: resort.id) .resizable() .scaledToFit() Group { HStack { Spacer() ResortDetailsView(resort: resort) SkiDetailsView(resort: resort) Spacer() } .font(.headline) .foregroundColor(.secondary) .padding(.top) Text(resort.description) .padding(.vertical) Text("Facilities") .font(.headline) // Text(resort.facilities.joined(separator: ", ")) // .padding(.vertical) Text(ListFormatter.localizedString(byJoining: resort.facilities)) .padding(.vertical) } .padding(.horizontal) } } .navigationBarTitle(Text("\(resort.name), \(resort.country)"), displayMode: .inline) } } struct ResortView_Previews: PreviewProvider { static var previews: some View { ResortView(resort: Resort.example) } }