Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save nkalvi/4cdc746ab92b5da664621d61ac7690e9 to your computer and use it in GitHub Desktop.

Select an option

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"
// 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)
}
@SF-Simon

SF-Simon commented Nov 7, 2024

Copy link
Copy Markdown

Thank you so much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment