반응형

원문 : SwiftUI Essentials-Building Lists and Navigation

Building Lists and Navigation

기본 랜드마크 상세 뷰가 설정하면 사용자가 전체 랜드마크의 목록을 보기위해 각 위치에 대한 세부정보를 볼수 있는 방법을 제공해야 합니다.

여러분은 모든 랜드마크에 대한 정보를 표시할수 있는 뷰를 만들고, 사용자가 랜드마크에 대한 상제 뷰를 보기 위해 탭할수 있는 스크롤 목록을 동적으로 생성할 것입니다. UI를 미세 조정하기 위해서, Xcode의 캔버스를 사용해서 다른 기기 크기로 여러개의 미리보기를 보여줍니다. 

이 프로젝트를 만드는 것을 시작하기 위해 프로젝트 파일을 다운로드하고 아래 단계를 따라합니다.

프로젝트 파일 다운로드

Section 1. 샘플 데이터 확인하기(Get to Know the Sample Data)

첫번째 튜토리얼에서, 모든 사용자정의 뷰의 정보를 하드코딩(hard-coded) 했었습니다. 여기에서, 여러분의 사용자정의 뷰에 데이터를 전달해서 표시하는것을 배우게 될 것입니다.

시작 프로젝트를 다운로드하고 샘플 데이터를 숙지합니다.

1 단계

프로젝트 네비게이터에서 Models > Landmark.swift를 선택합니다. 

Landmark.swift는 보여주는데 필요한 모든 랜드마크 정보를 저장하는 Landmark 구조체를 선언하고, landmarkData.json에서 랜드마크 데이터 배열을 가져옵니다(import).

import SwiftUI
import CoreLocation

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)
    }

    func image(forSize size: Int) -> Image {
        ImageStore.shared.image(name: imageName, size: size)
    }

    enum Category: String, CaseIterable, Codable, Hashable {
        case featured = "Featured"
        case lakes = "Lakes"
        case rivers = "Rivers"
    }
}

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

2 단계

프로젝트 네비게이터에서 Resources > landmarkData.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"
    },
    {
        "name": "Silver Salmon Creek",
        "category": "Lakes",
        "city": "Port Alsworth",
        "state": "Alaska",
        "id": 1002,
        "park": "Lake Clark National Park and Preserve",
        "coordinates": {
            "longitude": -152.665167,
            "latitude": 59.980167
        },
        "imageName": "silversalmoncreek"
    },
    ...
]

3 단계

뷰 만들고 결합하기(Creating and Combining Views)에서의 ContentView 타입이 이제는 LandmarkDetail로 명명된 것을 주의합니다.

여기와 다음에 오는 각 튜토리얼에서 몇 개의 뷰 타입을 만들것입니다.

import SwiftUI

struct LandmarkDetail: View {
    var body: some View {
        VStack {
            MapView()
                .frame(height: 300)

            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)

                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
            .padding()

            Spacer()
        }
    }
}

struct LandmarkDetail_Preview: PreviewProvider {
    static var previews: some View {
        LandmarkDetail()
    }
}

Section 2. 행 뷰 만들기(Create the Row View)

이 튜토리얼에서 만들 첫번째 뷰는 각 랜드마크에 대한 상세내용을 보여주는 행(row)입니다. 이 행 뷰는 랜드마크를 보여주는 정보를 프로퍼티에 저장하며, 여러개의 행을 하나의 랜드마크 목록으로 결합합니다.

1 단계

새로운 SwiftUI 뷰를 만들고, LandmarkRow.swift로 이름 짓습니다.

2 단계

미리보기가 아직 보여지지 않는 경우에, Editor > Editor and Canvas를 선택하고 Get Started를 클릭해서 캔버스를 표시합니다.

3 단계

LandmarkRow에 저장 프로퍼티 landmark를 추가합니다.

landmark프로퍼티를 추가할때, LandmarkRow 타입은 초기화 하는 동안 랜드마크 인스턴스가 필요하기 때문에, 미리보기 동작이 멈춥니다. 

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        Text("Hello World")
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow()
    }
}

미리보기를 고치기 위해서, 미리보기 공급자를 수정해야 합니다.

4 단계

LandmarkRow_Previews의 정적 프로퍼티 previews 에서, LandmarkRow 초기화에 랜드마크 매개변수를 추가하고, landmarkData 배열의 첫번째 요소를 지정합니다. 

미리보기에 Hello World 텍스트를 보여줍니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        Text("Hello World")
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}

이를 수정했으면, 행에 대한 레이아웃을 만들 수 있습니다.

5 단계

HStack에 기존 텍스트 뷰를 포함합니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            Text("Hello World")
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}

6 단계

landmark의 name 프로퍼티의 이름을 사용해서 텍스트 뷰를 수정합니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}

7 단계

텍스트 뷰 앞에 이미지를 추가해서 행을 완성합니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}

Section 3. 행 미리보기 사용자정의하기 (Customize the Row Preview)

Xcode의 캔버스는 현재 편집기에서 Preview Provider 프로토콜을 준수하는 모든 타입을 자동으로 인식하고 보여줍니다. 미리보기는 하나 이상의 뷰를 반환해서 제공하며, 크기와 기기를 구성하는 옵션이 있습니다.

여러분에게 가장 유용한 미리보기를 정확하게 보여주기 위해서 미리보기 제공자로부터 반환된 컨텐츠를 사용자정의 할 수 있습니다.

1 단계

LandmarkRow_Previews에서, landmarkData 배열에 있는 두번째 요소가 되도록 landmark 매개변수를 업데이트합니다.

첫번째 대신에 두번째 샘플 랜드마크를 표시하도록 미리보기가 바로 변경됩니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[1])
    }
}

2 단계

목록의 행과 비슷한 크기를 설정하기 위해 미리보기 Layout(_:) 수식어를 사용합니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[1])
            .previewLayout(.fixed(width: 300, height: 70))
    }
}

미리보기 제공자로부터 여러개의 미리보기를 반환하기 위해 Group를 사용할 수 있습니다.

3 단계

Group에서 반환된 행을 랩핑하고, 다시한번 첫번째 행을 추가합니다.

Group는 뷰 컨텐츠를 그룹화하는 컨테이너입니다. Xcode는 그룹의 자식 뷰를 캔버스에서 별도의 미리보기로 그려줍니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarkData[0])
                .previewLayout(.fixed(width: 300, height: 70))
            LandmarkRow(landmark: landmarkData[1])
                .previewLayout(.fixed(width: 300, height: 70))
        }
    }
}

4 단계

코드를 단순화하기 위해, previewLayer(_:) 호출은 그룹의 자식 선언 바깥쪽으로 이동합니다.

뷰의 자식은 미리보기 환경설정 처럼, 뷰의 컨텍스트 설정을 상속합니다.

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarkData[0])
            LandmarkRow(landmark: landmarkData[1])
        }
        .previewLayout(.fixed(width: 300, height: 70))
    }
}

미리보기 공급자에서 작성한 코드는 Xcode가 캔버스에서 표시되는 것만 변경합니다. #if DEBUG선언에 의해, 컴파일러는 여러분의 앱에 들어가지 않도록 그 코드를 삭제합니다.

Section 4. 랜드마크의 목록 만들기(Create the List of Landmarks)

SwiftUI의 List 타입을 사용할때, 플랫폼별 뷰 목록을 표시할 수 있습니다. 목록의 요소들은 지금까지 작성했던 스택의 자식 뷰와 마찬가지로 정적일수 있거나 동적으로 만들어질 수 있습니다. 정적과 동적으로 생성된 뷰를 혼합할 수 있습니다.

1 단계

SwiftUI 뷰를 만들고 LandmarkList.swift 이름을 짓습니다.

2 단계

기본 텍스트 뷰를 List로 교체하고, 목록의 자식처럼, 처음 두개의 랜드마크로 LandmarkRow 인스턴스를 제공합니다. 

미리보기는 iOS에 적합한 목록 스타일로 그려졌던 두개의 랜드마크를 보여줍니다. 

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()
    }
}

Section 5. 동적 목록 만들기(Make the List Dynamic)

목록의 요소들을 개별적으로 지정하는 대신에, 컬렉션으로 부터 행을 직접 생성할 수 있습니다.

컬렉션의 데이터를 전달하고 컬렉션의 각 요소에 대한 뷰를 제공하는 클로져를 전달해서 컬렉션의 요소를 보여주는 목록을 만들 수 있습니다. 그 목록은 제공된 클로져를 사용해서 컬렉션에 있는 각 요소를 자식 뷰로 변환합니다.

1 단계

두개의 정적 랜드마크 행을 제거하고, List 초기화에 landmarkData를 전달합니다.

목록은 식별가능한(identifiable) 데이터로 작업합니다. 두 가지 방법중 하나로 데이터 식별이 가능하도록 만들수 있습니다 : 각 요소를 고유하게 식별하는 프로퍼티에 대한 키 경로(key path)를 가지는 identified(by:) 메소드를 호출하거나 데이터 타입이 Identifiable 프로토콜을 준수하도록 만듭니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        List(landmarkData.identified(by: \.id)) { landmark in

        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

2 단계

클로져로부터 반환되는 LandmarkRow로 동적으로 생성된 목록을 완료합니다.

landmarkData 배열의 각 요소에 대해 하나의 LandmarkRow를 생성합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        List(landmarkData.identified(by: \.id)) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

다음으로, Landmark타입이 Identifiable를 준수하도록 추가해서 List 코드를 단순화 할 것입니다.

3 단계

Landmark.swift로 전환하고 Identifiable 프로토콜을 준수하도록 선언합니다.

Landmark 타입은 Identifiable 프로토콜의 필수항목인 id 프로퍼티를 이미 가지고 있어서, 더 이상 할 일이 없습니다.

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)
    }

    func image(forSize size: Int) -> Image {
        ImageStore.shared.image(name: imageName, size: size)
    }

    enum Category: String, CaseIterable, Codable, Hashable {
        case featured = "Featured"
        case lakes = "Lakes"
        case rivers = "Rivers"
    }
}

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

4 단계

LandmarkList.swift로 돌아가서 identified(by:) 호출을 제거합니다.

지금부터, Landmark 요소 컬렉션을 직접 사용할 수 있을 것입니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        List(landmarkData) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

Section 6. 목록과 상세화면간에 네비게이션 설정하기(Set Up Navigation Between List and Detail)

목록이 제대로 그려지지만, 아직은 랜드마크의 상세화면을 보기위해 개별 랜드마크를 탭 할 수는 없습니다.

NavigationView를 포함해서 목록에 네비게이션 기능을 추가하고나서 대상 뷰로 전환하는 설정을 하기 위해서 NavigationButton에 있는 각 행을 중첩합니다.

1 단계

NavigationView에 동적으로 생성된 랜드마크의 목록을 포함합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                LandmarkRow(landmark: landmark)
            }
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

2 단계

목록을 보여줄때 네비게이션 바의 타이틀을 설정하기 위해 navigationBarTitle(_:) 수식어 메소드를 호출합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                LandmarkRow(landmark: landmark)
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

3 단계

목록의 클로져 안쪽에, NavigationButton에 반환된 행을 랩핑하고, LandmarkDetail 뷰를 대상으로 지정합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(destination: LandmarkDetail()) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

4 단계

실시간 모드로 미리보기를 전환해서 네비게이션을 직접 시도해볼수 있습니다. 실시간 미리보기 버튼을 클릭하고 상세페이지를 방문하기 위해 랜드마크를 탭합니다.

Section 7. 자식뷰에 데이터 전달하기(Pass Data into Child Views)

LandmarkDetail 뷰는 랜드마크를 보여주기 위한 상세정보를 여전히 하드코딩된것을 사용합니다. LandmarkRow와 마찬가지로 LandmarkDetail 타입과 뷰는 데이터 소스로 랜드마크 프로퍼티를 사용하도록 구성해야 합니다.

자식 뷰로 시작해서, 각 행을 하드 코딩된것 대신에 전달된 데이터를 보여주기 위해 CircleImage, MapView, LandmarkDetail을 변환 할 것입니다.

1 단계

CircleImage.swift에서, CircleImage에 image 저장 프로퍼티를 추가합니다.

이는 SwiftUI를 사용해서 뷰를 만들때 공통 패턴입니다. 여러분의 사용자정의 뷰는 종종 특정 뷰에 대한 수식어(modifiers)로 랩핑하고 캡슐화 합니다.

import SwiftUI

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_Preview: PreviewProvider {
    static var previews: some View {
        CircleImage()
    }
}

2 단계

Turtle Rock의 이미지를 전달하기 위해 미리보기 제공자를 업데이트 합니다.

import SwiftUI

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_Preview: PreviewProvider {
    static var previews: some View {
        CircleImage(image: Image("turtlerock"))
    }
}

3 단계

MapView.swift에서 MapView에 coordinate 프로퍼티를 추가하고 위도와 경도를 하드코딩하는 대신에 해당 프로퍼티를 사용하도록 코드를 변환합니다.

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    var coordinate: CLLocationCoordinate2D

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {

        let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

struct MapView_Preview: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

4 단계

데이터 배열의 첫번째 랜드마크의 좌표(coordinate)를 전달하기위해 미리보기 공급자를 업데이트 합니다.

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    var coordinate: CLLocationCoordinate2D

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

struct MapView_Preview: PreviewProvider {
    static var previews: some View {
        MapView(coordinate: landmarkData[0].locationCoordinate)
    }
}

5 단계

LandmarkDetail.swift에서, LandmarkDetail 타입에 Landmark 프로퍼티를 추가합니다.

import SwiftUI

struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        VStack {
            MapView()
                .frame(height: 300)

            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)

                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
            .padding()

            Spacer()
        }
    }
}

struct LandmarkDetail_Preview: PreviewProvider {
    static var previews: some View {
        LandmarkDetail()
    }
}

6 단계

landmarkData에서 첫번째 랜드마크를 사용하기 위해 미리보기를 업데이트 합니다.

import SwiftUI

struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        VStack {
            MapView()
                .frame(height: 300)

            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)

                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
            .padding()

            Spacer()
        }
    }
}

struct LandmarkDetail_Preview: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: landmarkData[0])
    }
}

7 단계

필요한 데이터를 사용자정의 타입에 전달합니다.

import SwiftUI

struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        VStack {
            MapView(coordinate: landmark.locationCoordinate)
                .frame(height: 300)

            CircleImage(image: landmark.image(forSize: 250))
                .offset(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()
        }
    }
}

struct LandmarkDetail_Preview: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: landmarkData[0])
    }
}

8 단계

마지막으로, 상세 뷰가 보여질때 네비게이션바에 타이틀을 넣기위해 navigationBarTitle(_:displayMode:) 수식어를 호출합니다. 

import SwiftUI

struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        VStack {
            MapView(coordinate: landmark.locationCoordinate)
                .frame(height: 300)

            CircleImage(image: landmark.image(forSize: 250))
                .offset(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_Preview: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: landmarkData[0])
    }
}

9 단계

SceneDelegate.swift에서, 앱의 루트 뷰를 LandmarkList가 되도록 교체합니다.

미리보기 대신에 시뮬레이터로 시작할때 앱은 SceneDelegate에 정의된 루트 뷰로 시작됩니다.

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Use a UIHostingController as window root view controller
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UIHostingController(rootView: LandmarkList())
        self.window = window
        window.makeKeyAndVisible()
    }

    // ...
}

10 단계

LandmarkList.swift에서, LandmarkDetail 대상에 현재 랜드마크를 전달합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

11 단계

목록에서 이동할때 올바른 랜드마크 상세 뷰를 보기 위해서 실시간 미리보기로 전환합니다.

Section 8. 동적으로 미리보기 생성하기(Generating Previews Dynamically)

다음으로, 다른 기기 크기의 목록 뷰 미리보기를 그려주기 위해서 LandmarkList_Previews 미리보기 공급자에 코드를 추가할 것입니다. 기본적으로, 미리보기는 활성 스키마의 기기 크기로 그려줍니다. previewDeivce(_:) 수식어 메소드를 호출해서 미리보기 기기를 변경 할 수 있습니다.

1 단계

iPhone SE 크기로 그리기 위해서 현재 목록 미리보기를 변경하는 것으로 시작합니다.

Xcode의 스키마(scheme) 메뉴에 보여지는 모든 기기의 이름을 제공할 수 있습니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
            .previewDevice(PreviewDevice(rawValue: "iPhone XS"))
    }
}

2 단계

목록 미리보기 에서, LandmarkList를 ForEach 인스턴스에 포함시키고, 기기 이름 배열을 데이터로 사용합니다.

컬렉션에서 ForEach는 목록과 같은 방법으로 동작하며, 스택, 목록, 그룹 처럼, 어디에서든 자식 뷰를 사용할 수 있는 것을 의미합니다. 데이터의 요소가 단순한 타입값인 경우에-여기에서 사용하는 문자열처럼-식별자에 대한 키 경로로 \.self를 사용할 수 있습니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(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"].identified(by: \.self)) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
        }
    }
}

3 단계

미리보기의 라벨로 기기 이름에 대해 추가하기 위해서 previewDisplayName(_:) 수식어를 사용합니다.

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(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"].identified(by: \.self)) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}

4 단계

캔버스에서 다른 기기들로 뷰의 그리기를 비교할 수 있습니다.

  •  

퀴즈 1. List외에도 컬렉션 중에 동적 뷰 목록을 제공하는 것은 무엇인가요?

  1. Group
  2. ForEach 
  3. UITableView

[정답 : 2]
ForEach 인스턴스를 List 또는 다른 컨테이너 타입에 위치시켜서 동적 목록을 만듭니다.

퀴즈 2. Identifiable 요소들로부터 뷰의 List를 생성할 수 있습니다. Identifiable 프로토콜을 준수하지 않는 요소들의 컬렉션을 적용할때 어떤 메소드는 사용하나요?

  1. func map(_:)
  2. func sorted(by:)
  3. func identified(by:)

[정답 : 3]
identified(by:) 메소드로 컬렉션의 요소에 대해 고유하게 식별되는 키 경로를 전달합니다.

퀴즈 3. 다른 뷰로 이동하기 위해 List의 행을 탭 이동이 가능하도록 하는 타입은 무엇인가요?

  1. NavigationButton
  2. UITableViewDelegate
  3. NavigationView

[정답 : 1]
NavigationButton을 선언할때 대상 뷰와 행의 컨텐츠를 제공합니다.

퀴즈 4. 뷰를 미리보기 하기 위해 기기를 설정하는 방법이 아닌 것은?

  1. 활성 스키마(active scheme)에서 선택된 시뮬레이터를 변경
  2. Xcode의 환경설정에서 캔버스 설정에서 다른 것을 선택
  3. previewDevice(_:) 메소드를 사용해서 하나 이상의 기기를 지정
  4. 개발 기기를 연결하고 Device Preview 버튼을 클릭

[정답 : 2]
활성 스키마, 코드 또는 기기에서 미리보기에서 사용할 기기를 지정할수 있습니다. 환경설정으로 이동할 필요가 없습니다

 

반응형

'SwiftUI > Tutorials' 카테고리의 다른 글

Interfacing with UIKit  (0) 2019.08.13
Working with UI Controls  (0) 2019.08.13
Composing Complex Interfaces  (0) 2019.08.13
Animating Views and Transitions  (0) 2019.08.13
Drawing Paths and Shapes  (0) 2019.08.13
Handling User Input  (0) 2019.08.13
Creating and Combining Views  (0) 2019.07.23
Posted by 까칠코더
,