简体   繁体   中英

SwiftUI: UserInterfaceSizeClass for Universal (macOS & iOS) Views

Attempting to reference the @Environment objects horizontalSizeClass and verticalSizeClass on macOS (native, not Catalyst) results in the following errors:

'horizontalSizeClass' is unavailable in macOS

'verticalSizeClass' is unavailable in macOS

I appreciate that these properties aren't really applicable for macOS, but this presents a big barrier to creating SwiftUI Views which work universally (ie cross-platform across macOS, iOS, etc.).

One workaround is to wrap all the size-class-specific code inside conditional compilation, but the result is lots of duplication and redundancy (see below example).

Isn't there a more efficient way to handle this?

Example Universal View:

struct ExampleView: View {

    #if !os(macOS)
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    #endif

    private var item1: some View {
        Text("Example Item 1")
    }
    private var item2: some View {
        Text("Example Item 2")
    }
    private var item3: some View {
        Text("Example Item 3")
    }

    var body: some View {
        VStack {
            #if !os(macOS)
            if horizontalSizeClass == .compact {
                VStack {
                    item1
                    item2
                    item3
                }
            } else {
                HStack {
                    item1
                    item2
                    item3
                }
            }
            #else
            HStack {
                item1
                item2
                item3
            }
            #endif
        }
    }
}

It's true that macOS doesn't support horizontalSizeClass and verticalSizeClass natively, but the good news is it's easy to add them.

You can define your own @Environment objects by making a struct that conforms to EnvironmentKey , then using it to extend EnvironmentValues .

All you need to implement size classes on macOS is the following. They just return .regular at all times, but it's enough to function exactly the same as on iOS.

#if os(macOS)
enum UserInterfaceSizeClass {
    case compact
    case regular
}

struct HorizontalSizeClassEnvironmentKey: EnvironmentKey {
    static let defaultValue: UserInterfaceSizeClass = .regular
}
struct VerticalSizeClassEnvironmentKey: EnvironmentKey {
    static let defaultValue: UserInterfaceSizeClass = .regular
}

extension EnvironmentValues {
    var horizontalSizeClass: UserInterfaceSizeClass {
        get { return self[HorizontalSizeClassEnvironmentKey] }
        set { self[HorizontalSizeClassEnvironmentKey] = newValue }
    }
    var verticalSizeClass: UserInterfaceSizeClass {
        get { return self[VerticalSizeClassEnvironmentKey] }
        set { self[VerticalSizeClassEnvironmentKey] = newValue }
    }
}
#endif

With this in place, you don't need anything special for macOS. For example:

struct ExampleView: View {

    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    private var item1: some View {
        Text("Example Item 1")
    }
    private var item2: some View {
        Text("Example Item 2")
    }
    private var item3: some View {
        Text("Example Item 3")
    }
    
    var body: some View {
        VStack {
            if horizontalSizeClass == .compact {
                VStack {
                    item1
                    item2
                    item3
                }
            } else {
                HStack {
                    item1
                    item2
                    item3
                }
            }
        }
    }
}

You can even put these extensions into a framework for broader use, so long as you define them as public .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM