Instantly share code, notes, and snippets.
Forked from Dimillian/NavigationSplitViewExample.swift
Last active
September 21, 2023 14:14
-
Star
(4)
4
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save nkalvi/4cdc746ab92b5da664621d61ac7690e9 to your computer and use it in GitHub Desktop.
An example on how to use the new NavigationSplitView on iPad with global navigation - updated to work by following "Bringing robust navigation structure to your SwiftUI app"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment