I’m currently using NavigationView
to layout my App for iPhone and iPad depending on the horizontalSizeClass
. Since the functions I’m using for NavigationView
and NavigationLink
are deprecated I wonder if one can achieve something similar with the latest APIs.
Here is the simplified version.
ContentView
struct ContentView: View {
@Environment(.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
if horizontalSizeClass == .compact {
iPhoneEntry()
} else {
iPadEntry()
}
}
}
AppItems
indirect enum AppItem: Hashable, Identifiable {
var id: Int {
hashValue
}
case foobar, profile, welcome, articles, books, websites
case nested(item: AppItem, nested: [AppItem])
@ViewBuilder
var destination: some View {
switch self {
case .foobar:
FoobarOverview()
case .profile:
Profile()
case .welcome:
Welcome()
case .articles:
Overview(navTitle: "Articles")
case .books:
Overview(navTitle: "Books")
case .websites:
Overview(navTitle: "Websites")
case .nested:
EmptyView()
}
}
var name: String {
switch self {
case .foobar:
return "Foobar"
case .profile:
return "Profile"
case .welcome:
return "Welcome"
case .articles:
return "Articles"
case .books:
return "Books"
case .websites:
return "Websites"
case .nested(let item, _):
return item.name
}
}
static var ipad: [AppItem] = [.welcome, .profile, .nested(item: .foobar, nested: overviews)]
static var iphone: [AppItem] = [.welcome, .profile, .foobar]
static var overviews: [AppItem] = [.articles, .books, .websites]
}
iPhone Layout
struct iPhoneEntry: View {
@State var selectedItem: AppItem? = .welcome
@ViewBuilder
var body: some View {
TabView(selection: $selectedItem) {
ForEach(AppItem.iphone) { item in
switch item {
case .foobar:
NavigationView {
FoobarOverview()
}
.tag("Overview")
.tabItem { Label("Overview", systemImage: "circle") }
case .profile:
Profile()
.tag("Profile")
.tabItem { Label("Profile", systemImage: "triangle") }
case .welcome:
Welcome()
.tag("Welcome")
.tabItem { Label("Welcome", systemImage: "square") }
default:
EmptyView()
}
}
}
}
}
iPad-Layout
struct iPadEntry: View {
@State var selectedItem: AppItem? = .welcome
var body: some View {
NavigationView {
List {
ForEach(AppItem.ipad) { item in
if case .nested(let item, let subItems) = item {
Section {
ForEach(subItems, content: ListItem)
} header: {
Text(item.name)
}
} else {
ListItem(item)
}
}
}
.listStyle(.sidebar)
.modifier(ColumnModifier(appItem: selectedItem))
}
.navigationViewStyle(.columns)
}
func ListItem(_ appItem: AppItem) -> some View {
NavigationLink(tag: appItem, selection: $selectedItem) {
appItem.destination
} label: {
Text(appItem.name)
}
}
private struct ColumnModifier: ViewModifier {
let appItem: AppItem?
func body(content: Content) -> some View {
if appItem == .welcome || appItem == .profile {
Group {
content
Text("Select something")
}
} else {
Group {
content
appItem?.destination
Text("Select something")
}
}
}
}
}
Dummy Views
struct Profile: View {
var body: some View {
Text("Profile")
}
}
struct Welcome: View {
var body: some View {
Text("Welcome")
}
}
struct FoobarOverview: View {
var body: some View {
List {
ForEach(AppItem.overviews) { item in
NavigationLink(item.name, destination: item.destination)
}
}
}
}
struct Overview: View {
var navTitle: String
var body: some View {
List {
ForEach(0 ... 10, id: .self) { num in
NavigationLink("(navTitle) number: (num)") {
Detail(title: "(navTitle) number: (num)")
}
}
}
}
}
struct Detail: View {
var title: String
var body: some View {
Text("foobar")
}
}