From f00303b4d20cb414221107a031ade5a56f513102 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 2 Apr 2025 11:51:49 -0700 Subject: [PATCH 1/3] Add UIBinding deprecation warnings to avoid misuse The `@UIBinding` property wrapper is meant as a substitute for `@State`, where you can introduce local, owned state to a view controller that can be strongly held in another child, but because it is completely unconstrained it was easy to reach for instead of `@UIBindable` when it came to view models, and those view models could be retained too strongly. This PR introduces a couple deprecated overloads to catch this misuse and guide folks towards `@UIBindable`, instead. We might want to consider this subtlety a bit more and rethink or better document the differences. --- Sources/SwiftNavigation/UIBinding.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Sources/SwiftNavigation/UIBinding.swift b/Sources/SwiftNavigation/UIBinding.swift index bc67d619e..c06a82cba 100644 --- a/Sources/SwiftNavigation/UIBinding.swift +++ b/Sources/SwiftNavigation/UIBinding.swift @@ -202,6 +202,31 @@ public struct UIBinding: Sendable { ) } + @available(*, deprecated, message: "Use '@UIBindable', instead.", renamed: "UIBindable.init") + public init(wrappedValue value: Value) where Value: AnyObject & Perceptible { + self.init( + location: _UIBindingAppendKeyPath( + base: _UIBindingStrongRoot(root: _UIBindingWrapper(value)), + keyPath: \.value + ), + transaction: UITransaction() + ) + } + + #if canImport(Observation) + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @available(*, deprecated, message: "Use '@UIBindable', instead.", renamed: "UIBindable.init") + public init(wrappedValue value: Value) where Value: AnyObject & Observable { + self.init( + location: _UIBindingAppendKeyPath( + base: _UIBindingStrongRoot(root: _UIBindingWrapper(value)), + keyPath: \.value + ), + transaction: UITransaction() + ) + } + #endif + /// Creates a binding from the value of another binding. /// /// You don't call this initializer directly. Instead, Swift calls it for you when you use a From e52af65ba8d5bfa3fb039230fb03075e9fd925b7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 2 Apr 2025 13:24:12 -0700 Subject: [PATCH 2/3] Disable Windows CI --- .github/workflows/ci.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08cb0affd..11cb4829a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,19 +91,19 @@ jobs: - name: Run tests run: wasmtime --dir . .build/debug/swift-navigationPackageTests.wasm - windows: - name: Windows - strategy: - matrix: - os: [windows-latest] - config: ['debug', 'release'] - fail-fast: false - runs-on: ${{ matrix.os }} - steps: - - uses: compnerd/gha-setup-swift@main - with: - branch: swift-5.10-release - tag: 5.10-RELEASE - - uses: actions/checkout@v4 - - name: Build - run: swift build -c ${{ matrix.config }} + # windows: + # name: Windows + # strategy: + # matrix: + # os: [windows-latest] + # config: ['debug', 'release'] + # fail-fast: false + # runs-on: ${{ matrix.os }} + # steps: + # - uses: compnerd/gha-setup-swift@main + # with: + # branch: swift-5.10-release + # tag: 5.10-RELEASE + # - uses: actions/checkout@v4 + # - name: Build + # run: swift build -c ${{ matrix.config }} From 72b2ab976bad9f4ba4624cda826ce371bf47b570 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 2 Apr 2025 13:38:22 -0700 Subject: [PATCH 3/3] wip --- Sources/SwiftNavigation/UIBinding.swift | 26 ++++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftNavigation/UIBinding.swift b/Sources/SwiftNavigation/UIBinding.swift index c06a82cba..0961b1826 100644 --- a/Sources/SwiftNavigation/UIBinding.swift +++ b/Sources/SwiftNavigation/UIBinding.swift @@ -202,8 +202,16 @@ public struct UIBinding: Sendable { ) } - @available(*, deprecated, message: "Use '@UIBindable', instead.", renamed: "UIBindable.init") - public init(wrappedValue value: Value) where Value: AnyObject & Perceptible { + @available( + *, + deprecated, + message: + """ + A '@UIBinding' must be initialized with a value, not an observable reference. Use '@UIBindable', instead. + """, + renamed: "UIBindable.init" + ) + public init(wrappedValue value: Value) where Value: AnyObject { self.init( location: _UIBindingAppendKeyPath( base: _UIBindingStrongRoot(root: _UIBindingWrapper(value)), @@ -213,20 +221,6 @@ public struct UIBinding: Sendable { ) } - #if canImport(Observation) - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @available(*, deprecated, message: "Use '@UIBindable', instead.", renamed: "UIBindable.init") - public init(wrappedValue value: Value) where Value: AnyObject & Observable { - self.init( - location: _UIBindingAppendKeyPath( - base: _UIBindingStrongRoot(root: _UIBindingWrapper(value)), - keyPath: \.value - ), - transaction: UITransaction() - ) - } - #endif - /// Creates a binding from the value of another binding. /// /// You don't call this initializer directly. Instead, Swift calls it for you when you use a