본문 바로가기

개발/iOS

[SwiftUI Essentials] Building Lists and Navigation - 4

지난 작업에서는 저장된 데이터 베이스를 활용하여 LandmarkList.swift를 구현하였다.

이번 튜토리얼에서는 구성한 list view를 눌렀을 때 세부 정보를 보여주는 LandmarkDetail.swift를 작성하고, 리스트를 눌렀을 때 화면을 연결하는 Navigation에 대한 내용을 다루고 있다.

 

우선, LandmarkDetail.swift 파일을 새로 생성하고 기존에 ContentView에 있는 내용을 복사-붙여넣기 한다. 그리고 ContentView에는 LandmarkList()를 넣어준다.

이제 우리의 앱은 초기 화면인 LandmarkList와 각 리스트에 있는 랜드마크를 눌렀을 때 각 랜드마크에 해당하는 디테일한 정보를 볼 수 있는 LandmarkDetail의 두 가지 화면으로 구성되게 된다.

 

// ContentView.swift
struct ContentView: View {
    var body: some View {
        LandmarkList()
    }
}

// LandmarkDetail.swift
struct LandmarkDetail: View {
    var body: some View {
        VStack {
            MapView()
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

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

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

                HStack {
                    Text("Joshua Tree National Park")
                    Spacer()
                    Text("California")
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About Turtle Rock")
                    .font(.title2)
                Text("Descriptive text goes here.")
            }
            .padding()

            Spacer()
        }
    }
}

 

이제 LandmarkList와 LandmarkDetail을 연결해야 한다. 연결은 NavigationViewNavigationLink를 사용하여 가능하다. 들어가기 전에 사담을 하나 달자면, 이전에 iOS 앱을 만들 때 스토리보드 기반의 navigation이 오히려 더 어렵게 느껴졌었는데 SwiftUI에서는 굉장히 편하게 할 수 있었다.

LandmarkList로 가서 NavigationView와 NavigationLink를 추가한다.

 

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarks) { landmark in
                NavigationLink(destination: LandmarkDetail()) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationTitle("Landmarks")
        }
    }
}

 

NavigationView를 통해 내부에 있는 view에서 화면 간 이동을 할 수 있게 되고, NavigationLink를 통해서 도착 지점(destination)으로 이동할 수 있다. 이 코드에서는 List에 있는 LandmarkRow를 누를 경우 LandmarkDetail로 이동할 수 있다.

 

여기까지 튜토리얼을 진행할 경우 다른 landmark를 누르더라도 turtlerock의 정보가 저장된 detail 화면으로 이동하게 된다. 이제 한 가지 작업을 더 해줘야 한다. 바로 landmark 정보를 LandmarkDetail로 넘겨 화면을 표시할 수 있도록 하는 작업이다.

 

그를 위해 우선 LandmarkDetail을 구성하고 있는 view들을 수정해야 한다. 현재는 각 view들에서 표시되는 정보가 하드코딩되어 있기 때문에 이 부분을 수정해주어야 한다.

 

우선, 사진을 표현하는 CircleImage부터 수정해보도록 하겠다. 넘겨받은 이미지를 사용하기 위해 구조체 내부에 image를 선언하여 사용하도록 하였다. 이 때, Preview를 표시하기 위해서는 image를 넘겨주어야 한다. 이 때, 빌드 에러가 발생할 수 있다. 왜냐하면 LandmarkDetail에서 사용되는 CircleImage에 대한 인자를 전달받지 못하기 때문이다. 이 부분은 일단 무시하고 넘어가도록 한다.

 

// CircleImage.swift
import SwiftUI

struct CircleImage: View {
    var image: Image

    var body: some View {
        image
            .clipShape(Circle())
            .overlay(Circle().stroke(Color.white, lineWidth: 4))
            .shadow(radius: 7)
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage(image: Image("turtlerock"))
    }
}

 

그러고 나서는 이제 위치 정보를 표현하는 MapView를 수정해야 한다. MapView에서는 저장된 위-경도 좌표를 받아서 사용할 수 있도록 내부에 coordinate를 선언하고, 표시하고자 하는 부분을 지도에 나타내기 위한 setRegion 함수를 선언해준다.

 

// MapView.swift
import SwiftUI
import MapKit

struct MapView: View {
    var coordinate: CLLocationCoordinate2D
    @State private var region = MKCoordinateRegion()

    var body: some View {
        Map(coordinateRegion: $region)
            .onAppear {
                setRegion(coordinate)
            }
    }

    private func setRegion(_ coordinate: CLLocationCoordinate2D) {
        region = MKCoordinateRegion(
            center: coordinate,
            span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
        )
    }
}

 

coordinate는 전달 받아서 사용하고 전달 받은 coordinate를 사용하여 지역을 설정한다. 이 때, onAppear modifier를 사용하여 렌더링 되는 순간 지역을 설정하도록 코드를 수정한다.

 

이제 남은 작업은 랜드마크의 위치, 정보 등을 받아서 사용할 수 있도록, CircleImage와 MapView에서 인자를 받지 않아 빌드 에러가 나는 부분을 해결해주면 된다.

 

// LandmarkDetail.swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

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

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)
                    .foregroundColor(.primary)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

 

랜드마크를 받아서 사용할 수 있도록 landmark를 선언해주었고, 각 정보들을 받아온 데이터를 사용하여 표현할 수 있도록 수정하였다. 그리고 랜드마크의 세부 정보가 길어 화면이 잘리게 되므로 VStack을 ScrollView로 바꾸어 스크롤을 통해 모든 내용을 확인할 수 있도록 수정한다. 이 과정에서 Spacer()를 제거한다.

 

 

이 과정을 거치게 되면 이런 화면을 얻을 수 있다. 리스트에서 랜드마크를 누르게 되면 랜드마크의 세부정보로 이동할 수 있게 된다.

 


 

이번 튜토리얼에서 주로 다루는 리스트 생성과 네비게이션을 하는 부분을 다 다루었다. 튜토리얼을 따라하며 느낀 점은 스토리보드 기반의 개발보다 훨씬 편리하다는 점 정도가 있겠다.

반응형