#author("2022-07-22T08:15:58+00:00","default:src128","src128")
#author("2022-07-22T08:16:56+00:00","default:src128","src128")
&tag(SwiftUI/日本語チュートリアル2);
*目次 [#o4046361]
#contents
*関連ページ [#v37374c7]
*参考情報 [#de50f83f]
-[[【第2回】日本語版SwiftUIチュートリアル【リストとナビゲーション】 | HIRO LAB BLOG:https://hirlab.net/nblog/category/programming/art_1440/]]

*第2回:リストとナビゲーション ~動的なビュー生成~ [#he2a741d]

** Step 1. モデルを作る [#cde9f629]
-Modelsディレクトリを作成。
-そのなかにLandmark.swiftを作る。
#pre{{
import SwiftUI

struct Landmark: Hashable, Codable{
    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, Hashable, Codable{{
        case featured = "Featured"
        case lakes = "Lakes"
        case rivers = "Rivers"
        case mountains= "Mountains"
    }
}
 
extension Landmark {
    var image: Image {
        ImageStore.shared.image(name: imageName)
    }
}

struct Coordinates: Hashable, Codable {
    var latitude: Double
    var longitude: Double
}

}}

** Step 2. データ処理部 [#ub7fa690]

-ModelsディレクトリにData.swiftを作成。内容は以下。
#pre{{
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)!
    }
}
}}
-要はjsonからLandmarkオブジェクトを作成するたに使われている。

**Step 3. リストビューを作る前に構成要素を作る [#q3807f8b]
-ContentView.swiftをLandmarkDetail.swiftに変更。リファクタリング機能を使う。
-LandmarkRowを作成。
#pre{{
import SwiftUI
 
struct LandmarkRow: View {
    var landmark: Landmark
    
    var body: some View {
        Text(landmark.name)
    }
}
 
struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}
}}
-デザインを整える。これでリストに表示可能な見た目となる。
#pre{{
struct LandmarkRow: View {
    var landmark: Landmark
    
    var body: some View {
        HStack{
            landmark.image
                .resizable()
                .frame(width: 50, height: 50)
            Text(landmark.name)
            Spacer()
        }
    }
}
}}
**Step 4. 複数のデータを使ったプレビュー表示 [#k62e76cd]
-プレビューの改善。
#pre{{
struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarkData[0])
            LandmarkRow(landmark: landmarkData[1])
            LandmarkRow(landmark: landmarkData[2])
        }
        .previewLayout(.fixed(width: 300, height: 70))
    }
}
}}

**Step 5. 観光地データをリストとして積み上げる [#l70eca08]
-LandmarkList.swiftを作成。
-以下で2個のデータが表示される。
#pre{{
import SwiftUI
 
struct LandmarkList: View {
    var body: some View {
        List {
            LandmarkRow(landmark: landmarkData[0])
            LandmarkRow(landmark: landmarkData[1])
        }
    }
}
 
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

}}
-動的データを表示する。
#pre{{
struct LandmarkList: View {
    var body: some View {
        List(landmarkData) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}
}}
-これを可能とするためLandmarkにIdentifiableを付与。
 struct Landmark: Hashable, Codable, Identifiable{
-これでリストが表示される。
-初期化のところで使われているのはクロージャ構文。

**Step 6. リスト<=>詳細ビューのナビゲーション [#h5d7ddab]
-LandmarkList.swiftとLandmarkDetail.swiftをつなげる
-LandmarkListナビゲーションビューとしタイトルをつける。
#pre{{
struct LandmarkList: View {
    var body: some View {
        NavigationView{
            List(landmarkData) { landmark in
                LandmarkRow(landmark: landmark)
            }.navigationBarTitle(Text("Landmarks"))
        }
    }
}
}}
-詳細ビューが表示できるようにする。
#pre{{
struct LandmarkList: View {
    var body: some View {
        NavigationView{
            List(landmarkData) { landmark in
                NavigationLink(destination: LandmarkDetail()) {
                    LandmarkRow(landmark: landmark)
                }
            }.navigationBarTitle(Text("Landmarks"))
        }
    }
}
}}
-ただしまだ詳細ビューはTurtle Rockのまま。

**Step 7. 各要素を動的なビューにしていく [#a3d92232]
- CircleImage.swiftを動的にする。
#pre{{
struct CircleImage: View {
    var image: Image
    
    var body: some View {
        image
            .clipShape(Circle())
            .overlay(
                Circle().stroke(Color.white, lineWidth: 4))
            .shadow(radius: 10)
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage(image: Image("turtlerock"))
    }
}
}}
-MapView.swiftを修正。
#pre{{

struct MapView: UIViewRepresentable {
    var coordinate: CLLocationCoordinate2D
    
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        uiView.setRegion(region, animated: true)
    }
}
 
struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView(coordinate: landmarkData[0].locationCoordinate)
    }
}
}}
-LandmarkDetail.swiftを修正。
#pre{{
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(y: -130)
                .padding(.bottom, -130)
            
            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)
                HStack {
                    Text(landmark.park)
                        .font(.subheadline)
                    
                    Spacer()
                    
                    Text(landmark.state)
                        .font(.subheadline)
                }
            }
            .padding()
            
            Spacer()
        }
    }
}
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: landmarkData[0])
    }
}

}}
-LandmarksAppを修正(SceneDelegate.swiftは存在しなかった)。
#pre{{
import SwiftUI

@main
struct LandmarksApp: App {
    var body: some Scene {
        WindowGroup {
            LandmarkList()
        }
    }
}

}}
-LandmarkList.swiftを修正。
#pre{{
struct LandmarkList: View {
    var body: some View {
        NavigationView{
            List(landmarkData) { landmark in
                NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                    LandmarkRow(landmark: landmark)
                }
            }.navigationBarTitle(Text("Landmarks"))
        }
    }
}
}}

**Step 8. プレビュー端末を変える。 [#s3c13948]
-以下のように変更できる。
#pre{{
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
            .previewDevice(PreviewDevice(rawValue: "iPhone X"))
    }
}
}}


トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS