ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Editing NavigationSplitView 예제에서 모르는 키워드 정리(작성중)
    공부/iOS 2022. 11. 26. 03:45

    배경

    새롭게 등장한 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를 구성한다.

    두가지 방법으로 사용된다.

    1. @ViewBuiler Prameter

    image

    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)
        }
    }
    1. @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

Designed by Tistory.