1. Create a landmark model
먼저 튜토리얼 페이지에서 제공하는 데이터 파일들을 프로젝트에 다운 받은 후 데이터를 담을 구조체를 만든다.
연산 프로퍼티인 image 와 locationCoordinate도 추후에 용이하게 써먹기 위해 만들어 놓는다.
import Foundation
import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable {
var id: Int
var name: String
var park: String
var state: String
var description: String
private var imageName: String
var image: Image {
Image (imageName)
}
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
당연하겠지만 이는 데이터 형식을 반영한 것.
다음으로 데이터 파일을 읽어오는 기능을 구현한다.
import Foundation
var landmarks: [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)")
}
}
2. Create the row view
Landmark 모델 하나를 담을 Row view를 만든다.
import SwiftUI
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)
Spacer()
}
}
}
#Preview {
LandmarkRow(landmark: landmarks[0])
}
3. Customize the row preview
preview를 customize 하는 방법이다.
여러 개의 preview 를 띄울 수 있고 각각 이름을 붙일 수 있다.
#Preview("Turtle Rock") {
LandmarkRow(landmark: landmarks[0])
}
#Preview("Salmon") {
LandmarkRow(landmark: landmarks[1])
}
아니면 하나의 preview에 두 view를 side by side 보이게 할 수 있다.
#Preview() {
Group {
LandmarkRow(landmark: landmarks[0])
LandmarkRow(landmark: landmarks[1])
}
}
4. Create the list of landmarks
List 뷰를 사용해보자
struct LandmarkList: View {
var body: some View {
List {
LandmarkRow(landmark: landmarks[0])
LandmarkRow(landmark: landmarks[1])
}
}
}
#Preview {
LandmarkList()
}
5. Make the list dynamic
동적으로 landmarks 변수의 데이터를 가져와 그려주는 List 를 구현한다.
import SwiftUI
struct LandmarkList: View {
var body: some View {
List(landmarks, id: \.id) { landmark in
}
}
}
여기서 나는 \.id 라는 문법이 생소해서 알아보니 key path expression 이라고..
위 코드의 List 생성자의 파라미터 타입을 보면 아래와 같이 되어 있다.
첫번째 파라미터로 입력된 Data가 있으면 두번째 파라미터로 KeyPath<Data.Element, ID> 를 받는다.
만약 이러한 id 할당이 번거롭다면 모델 구조체에 Identifiable 을 추가하자.
그러면 간단하게 아래와 같이 구현할 수 있다.
List(landmarks) { landmark in
LandmarkRow(landmark: landmark)
}
6. Set up navigation between list and detail
List 를 NavigationSplitView 로 감싼 후 row 하나 하나를 NaavigationLink 로 감싼 후 LandmarkDetail로 이동하도록 설정
struct LandmarkList: View {
var body: some View {
NavigationSplitView {
List(landmarks) { landmark in
NavigationLink {
LandmarkDetail()
} label: {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
} detail: {
Text("Select a Landmark")
}
}
}
LandmarkDetail 은 이전에 ContentView 로 작성했던 코드를 그대로 가져온 것
7. Pass data int child views
이제 List 초기화 때 입력된 데이터를 각 Row 별로 데이터를 전달하도록 한다
struct CircleImage: View {
var image: Image
var body: some View {
image
.clipShape(Circle())
.overlay {
Circle().stroke(.white, lineWidth: 4)
}
.shadow(radius: 7)
}
}
struct MapView: View {
var coordinate: CLLocationCoordinate2D
var body: some View {
Map(position: .constant(.region(region)))
}
private var region: MKCoordinateRegion {
MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
}
}
이제 List 에서 Row 별로 데이터를 넘겨주고
struct LandmarkList: View {
var body: some View {
NavigationSplitView {
List(landmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
} detail: {
Text("Select a Landmark")
}
}
}
LandmarkDetail 에서 하위 뷰에 필요한 데이터를 넘겨준다 (전체를 감싸는 Vstack을 ScrollView 로 교체하는 건 덤)
struct LandmarkDetail: View {
var landmark: Landmark
var body: some View {
ScrollView {
MapView(coordinate: landmark.locationCoordinate)
.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)
Spacer()
Text(landmark.state)
}
.font(.subheadline)
.foregroundStyle(.secondary)
Divider()
Text("About \(landmark.name)")
.font(.title2)
Text(landmark.description)
}
.padding()
Spacer()
}
}
}
8. Generate previews dynamically
Device setting 에 따라 NavigationSplitView가 동적으로 변하는 것을 체크
'iOS > SwiftUI Tutorial' 카테고리의 다른 글
SwiftUI Tutorial 5- Drawing paths and shapes (0) | 2024.07.07 |
---|---|
SwiftUI Tutorial 4 - Handling user input (0) | 2024.07.06 |
SwiftUI Tutorial 2 - Creating and combining views 2 (0) | 2024.07.04 |
SwiftUI Tutorial 2 - Creating and combining views 1 (0) | 2024.07.03 |
SwiftUI Tutorial 1 - SwiftUI overview (0) | 2024.07.02 |