공부/iOS
Editing NavigationSplitView 예제에서 모르는 키워드 정리(작성중)
안토니1
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