Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 6 concurrency issue with EnvironmentKey

I hav a fairly simple construct, that has worked fine up until I tried to switch Swift language version to 6.0:

private struct SomeHeightKey: EnvironmentKey {
    static var defaultValue: CGFloat { UIScreen.main.bounds.size.height }
}

public extension EnvironmentValues {
    var someHeight: CGFloat {
        get { self[SomeHeightKey.self] }
        set { self[SomeHeightKey.self] = newValue }
    }
}

Now there seems to be an issue about defaultValue: Main actor-isolated class property 'main' can not be referenced from a nonisolated context. And apparently the EnvironmentKey protocol requires it to be nonisolated to uphold protocol conformance.

I have spent hours with ChatGPT and Copilot trying to find a concurrency safe way around this, but no luck so far. What am I missing here? I am both looking for the correct way of doing this in Swift 6 as well as any workaround that will allow me to postpone fixing it, but still migrate the rest of the codebase to Swift 6.

Ultimately the value is used for a ScrollViewWrapper:

public struct ScrollViewWrapper<Content: View>: View {
    let content: () -> Content

    public init(@ViewBuilder _ content: @escaping () -> Content) {
        self.content = content
    }

    public var body: some View {
        GeometryReader { proxy in
            ScrollView {
                content().frame(minHeight: proxy.size.height)
            }
            .frame(height: proxy.size.height)
            .environment(\.someHeight, proxy.size.height)
        }
    }
}

This is inherited code, that I am trying to migrate - not being an expert in SwiftUI, I am not even sure what they were trying to accomplish here. But perhaps the answer is to find a different way of doing this altogether...

like image 970
Sune Riedel Avatar asked Sep 14 '25 06:09

Sune Riedel


1 Answers

If you are always setting the environment value with .environment(\.someHeight, proxy.size.height), the default value doesn't really matter, so you can just set it to some constant value:

static let defaultValue: CGFloat = 0

In Xcode 16, you can also remove SomeHeightKey and use the Entry macro in the EnvironmentValues extension directly:

extension EnvironmentValues {
    @Entry var someHeight: CGFloat = 0
}

If you do need to use a main actor isolated value as the default value of an environment key, you can set that as the environment value at the root of your view hierarchy.

var body: some Scene {
    WindowGroup {
        ContentView()
    }
    .environment(\.someKey, someMainActorIsolatedValue)
}

But I wouldn't do this with UIScreen.main.bounds.height. If you need to know how much space is available for a view, use a GeometryReader like you have shown. And most of the time you don't even need a geometry reader, since there are many other modifiers that help you layout views, like containerRelativeFrame.

like image 146
Sweeper Avatar answered Sep 15 '25 22:09

Sweeper