最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swiftui - Xcode 16.2 beta 2 renders `onPreferenceChange` unusable - Stack Overflow

programmeradmin1浏览0评论

The view below works normally in Xcode 16.1. Build in Xcode 16.2 beta 2 and you get the following error on the closure for onPreferenceChange: "Main actor-isolated property 'height' can not be mutated from a Sendable closure". (Swift 6 language mode).

struct HeightKey: PreferenceKey {
  static var defaultValue: CGFloat { .zero }
  
  static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
  }
}

struct ContentView: View {
  @State private var height: CGFloat = 0
  
  var body: some View {
    VStack {
      Text("Title")
      background(
        GeometryReader { proxy in
          Color.clear
            .preference(key: HeightKey.self, value: proxy.size.height)
        }
      )
      .onPreferenceChange(HeightKey.self) { height in
        self.height = height
      }
      
      Text("Height: \(height)")
    }
  }
}

Look at the signature for onPreferenceChange in Xcode 16.1:

@inlinable nonisolated public func onPreferenceChange<K>(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable

And now in Xcode 16.2 beta 2:

@inlinable nonisolated public func onPreferenceChange<K>(_ key: K.Type = K.self, perform action: @escaping @Sendable (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable

The difference is the addition of @Sendable on the closure. Unfortunately they didn't also make it @MainActor so you can't update the containing View's properties. Just for fun I wrapped the assignment in a Task, but this causes a crash ("EXC_BAD_ACCESS code=2"):

.onPreferenceChange(HeightKey.self) { height in
  Task { @MainActor in
    self.height = height
  }
}

How can we use onPreferenceChange without going back to Swift 5 mode?

The view below works normally in Xcode 16.1. Build in Xcode 16.2 beta 2 and you get the following error on the closure for onPreferenceChange: "Main actor-isolated property 'height' can not be mutated from a Sendable closure". (Swift 6 language mode).

struct HeightKey: PreferenceKey {
  static var defaultValue: CGFloat { .zero }
  
  static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
  }
}

struct ContentView: View {
  @State private var height: CGFloat = 0
  
  var body: some View {
    VStack {
      Text("Title")
      background(
        GeometryReader { proxy in
          Color.clear
            .preference(key: HeightKey.self, value: proxy.size.height)
        }
      )
      .onPreferenceChange(HeightKey.self) { height in
        self.height = height
      }
      
      Text("Height: \(height)")
    }
  }
}

Look at the signature for onPreferenceChange in Xcode 16.1:

@inlinable nonisolated public func onPreferenceChange<K>(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable

And now in Xcode 16.2 beta 2:

@inlinable nonisolated public func onPreferenceChange<K>(_ key: K.Type = K.self, perform action: @escaping @Sendable (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable

The difference is the addition of @Sendable on the closure. Unfortunately they didn't also make it @MainActor so you can't update the containing View's properties. Just for fun I wrapped the assignment in a Task, but this causes a crash ("EXC_BAD_ACCESS code=2"):

.onPreferenceChange(HeightKey.self) { height in
  Task { @MainActor in
    self.height = height
  }
}

How can we use onPreferenceChange without going back to Swift 5 mode?

Share Improve this question asked Nov 20, 2024 at 21:53 smrsmr 1,25310 silver badges31 bronze badges 5
  • This is a weird change. Might be worth submitting some feedback to Apple. Does Xcode 16.2 come with Swift 6.1? If so, you can do nonisolated struct ContentView: View { ... }. If not, I think struct ContentView: View, _RemoveGlobalActorIsolation should still work. – Sweeper Commented Nov 20, 2024 at 22:11
  • Nope Xcode 16.2 is still Swift 6.0. Adding _RemoveGlobalActorIsolation causes the same crash as offloading to a Task. Feedback submitted (FB15904338). – smr Commented Nov 21, 2024 at 0:14
  • The size of a view can be measured without using a PreferenceKey at all. I added a new answer to How to get size of child? to show how. – Benzy Neez Commented Nov 28, 2024 at 10:45
  • Works terrific! You should add this as an answer so others don't miss it in the comments. – smr Commented Nov 30, 2024 at 11:39
  • They just fixed it in 18.4 by adding @preconcurrency developer.apple/documentation/ios-ipados-release-notes/… – malhal Commented Mar 4 at 19:59
Add a comment  | 

3 Answers 3

Reset to default 16

Got a response from an Apple engineer on how to correctly handle this:

.onPreferenceChange(HeightKey.self) { [$height] height in
  $height.wrappedValue = height
}

I feel a little sheepish for not realizing this earlier.

The approach that avoids preferences by @Benzy Neez is also a good one.

You can work around it by wrapping everything in MainActor.assumeIsolated:

.onPreferenceChange(HeightKey.self) { height in
    MainActor.assumeIsolated {
        self.height = height
    }
 }

This should be fine as long as it's being called from the main thread.

We went with:

.onPreferenceChange(HeightKey.self) { height in
    Task { @MainActor in
        self.height = height
    }
 }
发布评论

评论列表(0)

  1. 暂无评论