// BottomNavigationBarView.swift
// TrexlerLibrary
//
// Created by Emanuel Luna on 2/3/24.
//
import SwiftUI
import UserNotifications
enum ActiveView: String, Hashable {
case home
case search
case settings
}
struct MainView: View {
@Environment(\.horizontalSizeClass) var sizeClass
@State private var activeView: ActiveView? = .home
var body: some View {
Group {
if sizeClass == .compact {
iphoneTabView
} else {
ipadExpansiveView
}
}
.onAppear {
InfoForLibrary().requestNotificationPermission()
}
}
private var iphoneTabView: some View {
TabView(selection: $activeView) {
NavigationView { HomeView() }
.tabItem {
Label("Home", systemImage: "house")
}
.tag(ActiveView.home)
NavigationView { SearchView() }
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
.tag(ActiveView.search)
NavigationView { SettingsView() }
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag(ActiveView.settings)
}
}
private var ipadExpansiveView: some View {
NavigationView {
List {
NavigationLink(destination: HomeView(), tag: ActiveView.home, selection: $activeView) {
Label("Home", systemImage: "house")
}
NavigationLink(destination: SearchView(), tag: ActiveView.search, selection: $activeView) {
Label("Search", systemImage: "magnifyingglass")
}
NavigationLink(destination: SettingsView(), tag: ActiveView.settings, selection: $activeView) {
Label("Settings", systemImage: "gear")
}
}
.listStyle(SidebarListStyle())
.navigationTitle("Menu")
// Render the currently selected view
currentView
}
}
private var currentView: some View {
switch activeView {
case .home:
return AnyView(HomeView())
case .search:
return AnyView(SearchView())
case .settings:
return AnyView(SettingsView())
case .none:
return AnyView(HomeView())
}
}
}
// Preview
#Preview {
MainView().environmentObject(UserSession())
}
This is my main view. When I use my app, it typically opens the HomeView, but the moment I use my iPad with Split View and move the app to a compact size or regular size, the view I was currently on moves back to the HomeView.
How can I make my app stay at the view I was currently on while the size changes?
This behaviour is probably because the type of activeView
is different from the types of the tag
s you have assigned to each tab. ActiveTab?
vs ActiveTab
. That said, the way you wrote ipadExpansiveView
is also rather problematic.
For iOS 17, you should migrate to NavigationStack
and NavigationSplitView
.
First, put the title, image name, and the views for each page into the ActiveView
enum. This makes it more convenient to get these properties from an ActiveView
value. I also made it CaseIterable
so we can loop through its cases with ForEach
later on.
enum ActiveView: String, Hashable, CaseIterable {
case home
case search
case settings
var displayName: String {
switch self {
case .home:
"Home"
case .search:
"Search"
case .settings:
"Settings"
}
}
var systemImageName: String {
switch self {
case .home:
"house"
case .search:
"magnifyingglass"
case .settings:
"gear"
}
}
@ViewBuilder
var view: some View {
switch self {
case .home:
Text("Home")
case .search:
Text("Search")
case .settings:
Text("Settings")
}
}
}
I would also make iPhoneTabView
and iPadExpansiveView
s separate View
structs:
struct iPhoneTabView: View {
@Binding var activeView: ActiveView
var body: some View {
TabView(selection: $activeView) {
ForEach(ActiveView.allCases, id: \.self) { tab in
NavigationStack { tab.view }
.tabItem {
Label(tab.displayName, systemImage: tab.systemImageName)
}
}
}
}
}
struct iPadExpansiveView: View {
@Binding var activeView: ActiveView
var body: some View {
NavigationSplitView {
List(ActiveView.allCases, id: \.self, selection: Binding($activeView)) { tab in
NavigationLink(value: tab) {
Label(tab.displayName, systemImage: tab.systemImageName)
}
}
} detail: {
activeView.view
}
}
}
The NavigationSplitView
is driven by the selection of List
. The selection:
parameter is expecting a Binding<ActiveView?>
, but it doesn't make sense for there to be no active view, does it? Therefore, iPadExpansiveView
takes a non-optional ActiveView
binding, and converts it to Binding<ActiveView?>
just before passing it to selection:
.
MainView
would then look like this, with a non-optional activeView
state.
@Environment(\.horizontalSizeClass) var sizeClass
@State private var activeView: ActiveView = .home
var body: some View {
Group {
if sizeClass == .compact {
iPhoneTabView(activeView: $activeView)
} else {
iPadExpansiveView(activeView: $activeView)
}
}
}