|
// 2024-11-06: Simple test based on "Bringing robust navigation structure to your SwiftUI app" |
|
// https://developer.apple.com/documentation/swiftui/bringing_robust_navigation_structure_to_your_swiftui_app |
|
// Xcode Version 16.1, iOS 17+ |
|
|
|
// Opens to a sheet first instead of NavigationSplitView |
|
|
|
import SwiftUI |
|
|
|
@main |
|
struct TestSplitViewApp: App { |
|
private var navigationModel = NavigationModel(sidebarDestination: .subreddit(subreddit: .games)) |
|
|
|
var body: some Scene { |
|
WindowGroup { |
|
ContentView() |
|
} |
|
.environment(navigationModel) |
|
} |
|
} |
|
|
|
|
|
@Observable |
|
final class NavigationModel { |
|
var sidebarDestination: Destination? |
|
var detailPath: [Destination] |
|
|
|
var detailNavigation: Destination? |
|
var columnVisibility: NavigationSplitViewVisibility |
|
|
|
var showSplitView: Bool = false |
|
var showLoginView: Bool = false |
|
|
|
init(sidebarDestination: Destination? = nil, detailPath: [Destination] = [], detailNavigation: Destination? = nil, columnVisibility: NavigationSplitViewVisibility = .doubleColumn) { |
|
self.sidebarDestination = sidebarDestination |
|
self.detailPath = detailPath |
|
self.detailNavigation = detailNavigation |
|
self.columnVisibility = columnVisibility |
|
} |
|
|
|
var selectedDetail: Destination? { |
|
get { detailPath.first } |
|
set { detailPath = [newValue].compactMap { $0 } } |
|
} |
|
} |
|
|
|
struct LoginView: View { |
|
@Environment(\.dismiss) private var dismiss |
|
@Environment(NavigationModel.self) var navigationModel |
|
|
|
var body: some View { |
|
VStack { |
|
Text("Login") |
|
.padding() |
|
.navigationBarTitle("Login") |
|
Button("Continue") { |
|
navigationModel.showSplitView = true |
|
dismiss() |
|
} |
|
} |
|
} |
|
} |
|
|
|
struct ContentView: View { |
|
@Environment(NavigationModel.self) var navigationModel |
|
|
|
var body: some View { |
|
@Bindable var navigationModel = navigationModel |
|
|
|
Group { |
|
if navigationModel.showSplitView { |
|
MainView() |
|
} else { |
|
EmptyView() |
|
} |
|
|
|
} |
|
.onAppear { |
|
navigationModel.showLoginView = true |
|
} |
|
.sheet(isPresented: $navigationModel.showLoginView) { |
|
LoginView() |
|
} |
|
|
|
|
|
} |
|
} |
|
|
|
struct MainView: View { |
|
@Environment(NavigationModel.self) private var navigationModel |
|
|
|
var body: some View { |
|
@Bindable var navigationModel = navigationModel |
|
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() |
|
} |
|
} |
|
|
|
|
|
} 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") |
|
} |
|
} |
|
.toolbar { |
|
Button("Login") { |
|
navigationModel.showSplitView = false |
|
navigationModel.showLoginView = true |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
struct SidebarView: View { |
|
@Environment(NavigationModel.self) var navigationModel |
|
|
|
var body: some View { |
|
@Bindable var navigationModel = navigationModel |
|
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 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) |
|
} |
|
} |
|
|
|
enum HomeDestination: String, CaseIterable, Hashable { |
|
case hot, best, trending, new, top, rising |
|
} |
|
|
|
enum SubredditDestination: String, CaseIterable, Hashable { |
|
case news, diablo, pics, 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: |
|
"Home" |
|
case .subreddit: |
|
"Subreddit" |
|
case .user: |
|
"Account" |
|
case .post: |
|
"Post" |
|
} |
|
} |
|
} |
|
|
|
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 |
|
@Environment(NavigationModel.self) var navigationModel |
|
|
|
var body: some View { |
|
@Bindable var navigationModel = navigationModel |
|
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) |
|
} |
|
} |
|
|
|
#Preview("LoginView") { |
|
@Previewable var navigationModel = NavigationModel(sidebarDestination: .subreddit(subreddit: .games)) |
|
LoginView() |
|
.environment(navigationModel) |
|
} |
|
|
|
#Preview("SidebarView") { |
|
@Previewable var navigationModel = NavigationModel(sidebarDestination: .subreddit(subreddit: .games)) |
|
SidebarView() |
|
.environment(navigationModel) |
|
} |
|
|
|
#Preview("PostView") { |
|
@Previewable var navigationModel = NavigationModel(sidebarDestination: .subreddit(subreddit: .games)) |
|
PostView(post: .posts.first!) |
|
.environment(navigationModel) |
|
} |
Hello, @nkalvi , thank you for providing such a good example. I have encountered a difficult problem now, and I am not sure if it is a bug.
Our application only has three columns after logging in, so I made some changes to the code.
I used NavigationStack when logging in, and the adverse reaction I experienced was that the click on Text ("See some sub navigation") became unresponsive.
Can you tell me where the code went wrong? thank you