1

On app launch, I want to get the horizontalSizeClass and based on if it's compact or regular, apply a navigation style to my root navigation view like so:

import SwiftUI

@main
struct MyApp: App {
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some Scene {
        WindowGroup {
            if sizeClass == .compact {
                NavigationView {
                    Text("Compact size class inside stack navigation style")
                }
                .navigationViewStyle(StackNavigationViewStyle())
            } else {
                NavigationView {
                    Text("Regular size class inside default navigation style")
                }
            }
        }
    }
}

However, sizeClass always returns nil in this case.

How do I

  1. determine if the horizontal size class is compact or regular on the root view, and
  2. make the navigation style adapt to the size class any time it changes

My app is targeting iOS 14 for both iPhone and iPad.

Any help or a different approach to adapt for size class changes for the whole app is much appreciated.

Update 1

I tried the suggestions to use a ViewModifier or creating a custom view and adding the navigation in it's body like so:

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            MyRootView()
        }
    }
}

struct MyRootView: View {
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
        if sizeClass == .compact {
            NavigationView {
                Text("Compact size class inside stack navigation style")
            }
            .navigationViewStyle(StackNavigationViewStyle())
        } else {
            NavigationView {
                Text("Regular size class inside default navigation style")
            }
        }
    }
}

However, the navigation stack pops to the root view every time the sizeClass changes. Is there a way to preserve the stack? For example: If the user is 5 levels deep in navigation, and sizeClass changes, change the navigation style while keeping the visible screen?

Thank you!

Update 2

I was able to find a WWDC session explaining exactly what I want, but it's in UIKit.

See 18:35 here: https://developer.apple.com/wwdc20/10105

I'm trying to achieve the same goal in SwiftUI (keep the screen the user selected while changing the size class to compact).

According to the session, UISplitViewController supports this because there's the concept of Restorable and Restore in the detail view. I can't find a way to do this in SwiftUI.

alobaili
  • 761
  • 9
  • 23
  • 1
    This should help: https://stackoverflow.com/a/64771576/14351818 – aheze Jul 16 '21 at 20:49
  • @aheze Thank you! I tried this solution but it doesn't preserve the screen I'm currently on. For example if I'm 5 levels deep in the navigation stack, changing orientation on iPhone 12 Pro Max causes the whole navigation to reset to the root view. Is there a solution for this? – alobaili Jul 16 '21 at 20:58

2 Answers2

0

this setup works for me. I read somewhere in the docs that Environment are updated before a view is rendered. I guess App is not a view.

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
struct ContentView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        if horizontalSizeClass == .compact {
            Text("Compact")
        } else {
            Text("Regular")
        }
    }
}
  • Thank you for the answer. However, you removed the NavigationView completely. I a solution where there is a `NavigationView` with `StackNavigationViewStyle` on compact and `DefaultNavigationViewStyle` on regular. without the navigation view popping to root every time the size class changes. I appreciate any suggestion. – alobaili Jul 17 '21 at 11:57
0

Yeah, forgot about the NavigationViews. You could try something like this (using the code from "https://matteo-puccinelli.medium.com/conditionally-apply-modifiers-in-swiftui-51c1cf7f61d1")

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

extension View {
    @ViewBuilder
    func ifCondition<TrueContent: View, FalseContent: View>(_ condition: Bool, then trueContent: (Self) -> TrueContent, else falseContent: (Self) -> FalseContent) -> some View {
        if condition {
            trueContent(self)
        } else {
            falseContent(self)
        }
    }
}

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var sizeClass
    
    var body: some View {
        NavigationView {
            if sizeClass == .compact {
                Text("Compact size class inside stack navigation style")
            } else {
                Text("Regular size class inside default navigation style")
            }
        }
        .ifCondition(sizeClass == .compact) { nv in
            nv.navigationViewStyle(StackNavigationViewStyle())
        } else: { nv in
            nv.navigationViewStyle(DefaultNavigationViewStyle())
        }
    }
}
  • I really appreciate your help here, but again, the navigation state is lost and the start over from the root view every time the size class changes. Please see Update 1 and Update 2 in my question to know the problem I face with this approach. Thank you! – alobaili Jul 17 '21 at 14:41