アイテムの主要なリストを作成する
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)
}
}