-
Editing NavigationSplitView 예제에서 모르는 키워드 정리(작성중)공부/iOS 2022. 11. 26. 03:45
배경
새롭게 등장한 NavigationSplitView와 NavigationStack을 공부하며 찾아봤던 내용을 정리했습니다.
예제 코드에서 키워드를 뽑아냈으며 헷갈리거나 모르는 개념을 정리해봤습니다.
목차
- Group
- ViewBuilder
- 앱의 구조 (App, Scene, WindowGroup, ContentView)
- NavigationSplitView
- NavigationStack
- 예제 코드
Group
VStack과 비슷한 역할을 하는 Struct입니다.
다음과 같이 VStack 처럼 사용할 수 있으며, 이 경우 VStack과 가장 큰 차이점은 Group은 11개 이상의 child를 가질 수 있다는 점 입니다.
Group { Text("SwiftUI") Text("Combine") Text("Swift System") } .font(.headline)
View Builder를 사용하여 뷰 그룹을 작성하기 때문에 Group의 이니셜라이저를 사용하여 조건부에서 다른 종류의 뷰를 생성한 다음 선택적으로 수정자를 적용할 수 있습니다.
Group { if isLoggedIn { WelcomeView() } else { LoginView() } } .navigationBarTitle("Start")
ViewBuilder
Closure에서 View를 구성하는 custom parameter attribute. Closure에서 (Child) View를 구성한다.
두가지 방법으로 사용된다.
- @ViewBuiler Prameter
HStack은 생성자에 @ViewBuilder content 파라미터(Closure타입)가 있습니다.
이 처럼 커스텀으로 파라미터를 받는 생성자를 정의할 때 사용할 수 있습니다.
struct CustomHStack<Content>: View where Content: View { let content: () -> Content let color: Color init(color: Color = .clear, @ViewBuilder content: @escaping () -> Content) { self.color = color self.content = content } var body: some View { HStack { content() } .background(color) } }
- @ViewBuilder computed property, Method
computed property 가 너무 길어지거나 Method로 빼고 싶은 경우에 다음과 같이 쓸 수 있다.
struct ContentView: View { var body: some View { VStack{ manyTexts generateTexts() } } @ViewBuilder var manyTexts: some View { Text("computed Property 1") Text("computed Property 2") Text("computed Property 3") Text("computed Property 4") Text("computed Property 5") } @ViewBuilder func generateTexts() -> some View { Text("Method 1") Text("Method 2") Text("Method 3") Text("Method 4") Text("Method 5") } }
예제 코드
// 2022-08-03: updated to work by following "Bringing robust navigation structure to your SwiftUI app" // https://developer.apple.com/documentation/swiftui/bringing_robust_navigation_structure_to_your_swiftui_app // Tested with Xcode Version 14.0 beta 4 (iOS 16.0 beta 4) import SwiftUI import Combine @main struct SplitNav_2022_08_02App: App { @StateObject var navigationModel = NavigationModel(sidebarDestination: .subreddit(subreddit: .games)) var body: some Scene { WindowGroup { MainView() .environmentObject(navigationModel) } } } // Based on https://developer.apple.com/documentation/swiftui/bringing_robust_navigation_structure_to_your_swiftui_app final class NavigationModel: ObservableObject { @Published var sidebarDestination: Destination? @Published var detailPath: [Destination] @Published var detailNavigation: Destination? @Published var columnVisibility: NavigationSplitViewVisibility init(sidebarDestination: Destination? = nil, detailPath: [Destination] = [], detailNavigation: Destination? = nil, columnVisibility: NavigationSplitViewVisibility = .automatic) { self.sidebarDestination = sidebarDestination self.detailPath = detailPath self.detailNavigation = detailNavigation self.columnVisibility = columnVisibility } var selectedDetail: Destination? { get { detailPath.first } set { detailPath = [newValue].compactMap { $0 } } } } enum HomeDestination: String, CaseIterable, Hashable { case hot, best, trending, new, top, rising } enum SubredditDestination: String, CaseIterable, Hashable { case news, diablo, pics, wtf, games, movies } enum UserDestination: String, CaseIterable, Hashable { case profile, inbox, posts, comments, saved } enum Destination: Hashable { case home(home: HomeDestination) case subreddit(subreddit: SubredditDestination) case user(user: UserDestination) case post(post: Post) var caseName: String { switch self { case .home: return "Home" case .subreddit: return "Subreddit" case .user: return "Account" case .post: return "Post" } } } struct MainView: View { @EnvironmentObject var navigationModel: NavigationModel var body: some View { NavigationSplitView( columnVisibility: $navigationModel.columnVisibility ) { SidebarView() } content: { Group { switch navigationModel.sidebarDestination { case .home(let destination): HomeView(destination: destination) .navigationTitle(navigationModel.sidebarDestination!.caseName.capitalized) case .subreddit(let subreddit): SubredditView(subreddit: subreddit) case .user(let destination): AccountView(destination: destination) .navigationTitle(navigationModel.sidebarDestination!.caseName.capitalized) case .post(let post): PostView(post: post) case .none: EmptyView() } } .onDisappear { if navigationModel.selectedDetail == nil { navigationModel.sidebarDestination = nil } } } detail: { NavigationStack { Group { if case .subreddit = navigationModel.sidebarDestination { if let detailNavigation = navigationModel.selectedDetail { if case .post(let post) = detailNavigation { PostView(post: post) } } else { Text("Please select a post") } } else { EmptyView() } }.navigationDestination(for: Destination.self) { destination in switch destination { case .user(let userDestination): AccountView(destination: userDestination) default: Text("Not supported here") } } } } } } struct SidebarView: View { @EnvironmentObject var navigationModel: NavigationModel var body: some View { List(selection: $navigationModel.sidebarDestination) { Section("Home") { ForEach(HomeDestination.allCases, id: \.self) { homeItem in NavigationLink(value: Destination.home(home: homeItem)) { Label(homeItem.rawValue.capitalized, systemImage: "globe") } } } Section("Subreddit") { ForEach(SubredditDestination.allCases, id: \.self) { subreddit in NavigationLink(value: Destination.subreddit(subreddit: subreddit)) { Label(subreddit.rawValue.capitalized, systemImage: "globe") } } } Section("Account") { ForEach(UserDestination.allCases, id: \.self) { userDestination in NavigationLink(value: Destination.user(user: userDestination)) { Label(userDestination.rawValue.capitalized, systemImage: "globe") } } } } .navigationTitle("Categories") } } struct Post: Identifiable, Hashable { let id = UUID() let title = "A post title" let preview = "Some wall of text to represent the preview of a post that nobody will read if the title is not a clickbait" } extension Post { static var posts: [Post] = [Post(), Post(), Post(), Post(), Post(), Post(), Post(), Post()] } struct SubredditView: View { let subreddit: SubredditDestination @EnvironmentObject var navigationModel: NavigationModel var body: some View { List(Post.posts, selection: $navigationModel.selectedDetail) { post in NavigationLink(value: Destination.post(post: post)) { HStack { VStack(alignment: .leading) { Text(post.title) .font(.title3) .fontWeight(.semibold) Text(post.preview) .font(.callout) } } } }.navigationTitle(subreddit.rawValue.capitalized) } } struct PostView: View { let post: Post var body: some View { VStack { Text(post.title) .font(.title) Text(post.preview) NavigationLink(value: Destination.user(user: .comments)) { Text("See some sub navigation") } } } } struct AccountView: View { let destination: UserDestination var body: some View { Text(destination.rawValue.capitalized) } } struct HomeView: View { let destination: HomeDestination var body: some View { Text(destination.rawValue.capitalized) } } struct MainView_Previews: PreviewProvider { static var previews: some View { MainView() } }
출처: Bringing robust navigation structure to your SwiftUI app 를 참고한 코드입니다.
https://gist.github.com/Dimillian/c10ffac65a1a37b337a07b5cf0773cea
'공부 > iOS' 카테고리의 다른 글
self에서 unexpected 에러가 발생하는 이유 (0) 2023.12.01 [Swift] Date formatted, FormatStyle로 날짜 출력하기 (0) 2023.07.19 디자인 챌린지 (Asia Pacific) - Part 1 (0) 2023.03.06 Xcode 아이폰 빌드한 뒤에 LLDB 라고 하면서 빌드 안되는 문제 (0) 2022.11.28 [SwiftUI] Custom Font를 선언형으로 사용해보자 (0) 2022.10.30