-
[SwiftUI] 6. Composing Complex InterfacesiOS Dev/SwiftUI 2023. 5. 4. 01:50반응형
https://developer.apple.com/tutorials/swiftui/composing-complex-interfaces
Composing Complex Interfaces | Apple Developer Documentation
The category view for Landmarks shows a vertically scrolling list of horizontally scrolling landmarks. As you build this view and connect it to your existing views, you’ll explore how composed views can adapt to different device sizes and orientations.
developer.apple.com
Section 1. Add a Category View
Views 그룹에 Categories 그룹을 만들고 그 안에 CategoryHome.swift를 추가한다.
코드는 다음 내용으로 채운다.
import SwiftUI struct CategoryHome: View { var body: some View { NavigationStack { Text("Hello, World!") .navigationTitle("Featured") } } } struct CategoryHome_Previews: PreviewProvider { static var previews: some View { CategoryHome() } }
NavigationView 말고 NavigationStack으로 사용했다.
.navigationTitle 모디파이어를 사용하면 해당 뷰가 들어있는 Navigation 요소의 title을 정의할 수 있다.
Section 2. Create a Category List
json파일에서 Category 데이터를 읽어오기 위해 기존 코드를 많이 수정해야 한다.
먼저 Landmark.swift 부터 수정한다.
struct Landmark: Hashable, Codable, Identifiable { var id: Int var name: String var park: String var state: String var description: String var isFavorite: Bool var category: Category // ✅ enum Category: String, CaseIterable, Codable { // ✅ case lakes = "Lakes" case rivers = "Rivers" case mountains = "Mountains" } private var imageName: String var image: Image { return 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 } }
다음은 ModelData.swift이다.
final class ModelData: ObservableObject { @Published var landmarks: [Landmark] = load("landmarkData.json") var hikes: [Hike] = load("hikeData.json") var categories: [String: [Landmark]] { // ✅ Dictionary( grouping: landmarks, by: { $0.category.rawValue } ) } }
Dictionary 부분이 흥미로웠는데, landmarks 데이터를 그룹핑하는데, key를 각각의 데이터의 category의 rawValue로 주었다. 진짜 아는 만큼 보이는 것 같다..
다시 CategoryHome으로 돌아와서 modelData를 받아주고, List를 생성한다.
import SwiftUI struct CategoryHome: View { @EnvironmentObject var modelData: ModelData var body: some View { NavigationStack { List { ForEach(modelData.categories.keys.sorted(), id: \.self) { key in Text(key) } } .navigationTitle("Featured") } } } struct CategoryHome_Previews: PreviewProvider { static var previews: some View { CategoryHome() .environmentObject(ModelData()) } }
Section 3. Create a Category Row
CategoryItem.swift 파일과 CategoryRow.swift 파일을 생성한다.
먼저 CategoryItem.swift코드는 다음과 같이 작성한다.
import SwiftUI struct CategoryItem: View { var landmark: Landmark var body: some View { VStack(alignment: .leading) { landmark.image .resizable() .frame(width: 155, height: 155) .cornerRadius(5) Text(landmark.name) .font(.caption) } .padding(.leading, 15) } } struct CategoryItem_Previews: PreviewProvider { static var previews: some View { CategoryItem(landmark: ModelData().landmarks[0]) } }
다음, CategoryRow.swift 코드는 다음과 같이 작성한다.
import SwiftUI struct CategoryRow: View { var categoryName: String var items: [Landmark] var body: some View { VStack(alignment: .leading) { Text(categoryName) .font(.headline) .padding(.leading, 15) .padding(.top, 5) ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 0) { ForEach(items) { landmark in CategoryItem(landmark: landmark) } } } .frame(height: 185) } } } struct CategoryRow_Previews: PreviewProvider { static var landmarks = ModelData().landmarks static var previews: some View { CategoryRow( categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(4)) ) } }
여기까지 CategoryRow는 이런 모양이다.
Section 4. Complete the Category View
CategoryHome.swift를 다음과 같이 업데이트한다.
import SwiftUI struct CategoryHome: View { @EnvironmentObject var modelData: ModelData var body: some View { NavigationStack { List { ForEach(modelData.categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: modelData.categories[key] ?? []) // ✅ } } .navigationTitle("Featured") } } } struct CategoryHome_Previews: PreviewProvider { static var previews: some View { CategoryHome() .environmentObject(ModelData()) } }
이제 특집이 있는 Landmark도 구분하기 위해 Landmark.swift 모델을 수정해야한다.
import Foundation import SwiftUI import CoreLocation struct Landmark: Hashable, Codable, Identifiable { var id: Int var name: String var park: String var state: String var description: String var isFavorite: Bool var isFeatured: Bool // ✅ var category: Category enum Category: String, CaseIterable, Codable { case lakes = "Lakes" case rivers = "Rivers" case mountains = "Mountains" } private var imageName: String var image: Image { return 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 } }
ModelData.swift도 손봐준다.
import Foundation import Combine final class ModelData: ObservableObject { @Published var landmarks: [Landmark] = load("landmarkData.json") var hikes: [Hike] = load("hikeData.json") var features: [Landmark] { // ✅ landmarks.filter { $0.isFeatured } } var categories: [String: [Landmark]] { Dictionary( grouping: landmarks, by: { $0.category.rawValue } ) } }
다시 CategoryHome.swift로 돌아와서 다음 내용을 수정한다.
import SwiftUI struct CategoryHome: View { @EnvironmentObject var modelData: ModelData var body: some View { NavigationStack { List { modelData.features[0].image // ✅ .resizable() .scaledToFill() .frame(height: 200) .clipped() .listRowInsets(EdgeInsets()) ForEach(modelData.categories.keys.sorted(), id: \.self) { key in CategoryRow(categoryName: key, items: modelData.categories[key] ?? []) } .listRowInsets(EdgeInsets()) // ✅ } .navigationTitle("Featured") } } } struct CategoryHome_Previews: PreviewProvider { static var previews: some View { CategoryHome() .environmentObject(ModelData()) } }
.listRowInsets(EdgeInsets())를 하게 되면 모든 Edge에 대한 Inset이 0으로 초기화가 되면서 요소를 꽉 채울 수 있다.
Section 5. Add Navigation Between Sections
이제 각각 다른 뷰에서 같은 디테일 뷰로 navigate할 수 있도록 할 것이다.
먼저 CategoryRow.swift로 가서 다음을 수정한다.
import SwiftUI struct CategoryRow: View { var categoryName: String var items: [Landmark] var body: some View { VStack(alignment: .leading) { Text(categoryName) .font(.headline) .padding(.leading, 15) .padding(.top, 5) ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 0) { ForEach(items) { landmark in NavigationLink { // ✅ LandmarkDetail(landmark: landmark) } label: { CategoryItem(landmark: landmark) } } } } .frame(height: 185) } } }
다음은 CategoryItem.swift이다.
import SwiftUI struct CategoryItem: View { var landmark: Landmark var body: some View { VStack(alignment: .leading) { landmark.image .renderingMode(.original) // ✅ .resizable() .frame(width: 155, height: 155) .cornerRadius(5) Text(landmark.name) .foregroundColor(.primary) // ✅ .font(.caption) } .padding(.leading, 15) } }
이 두가지를 바꾸는 이유? 먼저 image는 NavigationLink의 Label로 표현될 때 템플릿 이미지로 렌더링되고, text는 accent color로 렌더링 되게 된다. 이를 바꾸려면 renderingMode를 바꾸거나 foregroundColor를 미리 주면 된다.
이제 맨 처음 ContentView.swift로 돌아가서 TabView를 만들어준다.
import SwiftUI struct ContentView: View { @State private var selection: Tab = .featured enum Tab { case featured case list } var body: some View { TabView(selection: $selection) { CategoryHome() .tabItem { Label("Featured", systemImage: "star") } .tag(Tab.featured) LandmarkList() .tabItem { Label("List", systemImage: "list.bullet") } .tag(Tab.list) } } }
잘 되는 것을 확인할 수 있다.
Check Your Understanding
답은 3 3 2
반응형'iOS Dev > SwiftUI' 카테고리의 다른 글
[SwiftUI] 8. Interfacing with UIKit [마지막] (1) 2023.05.11 [SwiftUI] 7. Working with UI Controls (0) 2023.05.06 [SwiftUI] 5. Animating Views and Transitions (0) 2023.05.04 [SwiftUI] 4. Drawing Paths and Shapes (2) 2023.04.30 [SwiftUI] 3. Handling User Input (0) 2023.03.30