diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ceb45ab..6708955 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -30,7 +30,7 @@ jobs: with: runsonlabels: '["macOS", "self-hosted"]' scheme: SpeziViews-Package - destination: 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)' + destination: 'platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)' resultBundle: SpeziViews-watchOS.xcresult artifactname: SpeziViews-watchOS.xcresult buildandtest_visionos: diff --git a/Sources/SpeziValidation/ValidationRule.swift b/Sources/SpeziValidation/ValidationRule.swift index b34782b..dcbb2e2 100644 --- a/Sources/SpeziValidation/ValidationRule.swift +++ b/Sources/SpeziValidation/ValidationRule.swift @@ -73,9 +73,8 @@ public struct ValidationRule: Identifiable, Sendable, Equatable { let effect: CascadingValidationEffect - // swiftlint:disable:next function_default_parameter_at_end init( - id: UUID = UUID(), + id: UUID = UUID(), // swiftlint:disable:this function_default_parameter_at_end ruleClosure: @escaping @Sendable (String) -> Bool, message: LocalizedStringResource, effect: CascadingValidationEffect = .continue diff --git a/Sources/SpeziViews/Views/Button/AsyncButton.swift b/Sources/SpeziViews/Views/Button/AsyncButton.swift index 38169b6..804d37b 100644 --- a/Sources/SpeziViews/Views/Button/AsyncButton.swift +++ b/Sources/SpeziViews/Views/Button/AsyncButton.swift @@ -122,9 +122,9 @@ public struct AsyncButton: View { /// It may also be used to externally control or observe the button's processing state. /// - action: An asynchronous button action. @_disfavoredOverload - public init( // swiftlint:disable:this function_default_parameter_at_end + public init( _ title: Title, - role: ButtonRole? = nil, + role: ButtonRole? = nil, // swiftlint:disable:this function_default_parameter_at_end state: Binding, action: @MainActor @escaping () async throws -> Void ) where Label == Text { @@ -140,9 +140,9 @@ public struct AsyncButton: View { /// - state: A ``ViewState`` binding that it used to propagate any error caught in the button action. /// It may also be used to externally control or observe the button's processing state. /// - action: An asynchronous button action. - public init( // swiftlint:disable:this function_default_parameter_at_end + public init( _ title: LocalizedStringResource, - role: ButtonRole? = nil, + role: ButtonRole? = nil, // swiftlint:disable:this function_default_parameter_at_end state: Binding, action: @MainActor @escaping () async throws -> Void ) where Label == Text { @@ -158,8 +158,8 @@ public struct AsyncButton: View { /// It may also be used to externally control or observe the button's processing state. /// - action: An asynchronous button action. /// - label: The Button label. - public init( // swiftlint:disable:this function_default_parameter_at_end - role: ButtonRole? = nil, + public init( + role: ButtonRole? = nil, // swiftlint:disable:this function_default_parameter_at_end state: Binding, action: @MainActor @escaping () async throws -> Void, @ViewBuilder label: () -> Label diff --git a/Sources/SpeziViews/Views/Layout/HorizontalGeometryReader.swift b/Sources/SpeziViews/Views/Layout/HorizontalGeometryReader.swift index c2a3011..6f46ee1 100644 --- a/Sources/SpeziViews/Views/Layout/HorizontalGeometryReader.swift +++ b/Sources/SpeziViews/Views/Layout/HorizontalGeometryReader.swift @@ -39,7 +39,19 @@ public struct HorizontalGeometryReader: View { } ) .onPreferenceChange(WidthPreferenceKey.self) { width in - self.width = width + // The `onPreferenceChange` view modfier now takes a `@Sendable` closure, therefore we cannot capture `@MainActor` isolated properties + // on the `View` directly anymore: https://developer.apple.com/documentation/swiftui/view/onpreferencechange(_:perform:)?changes=latest_minor + // However, as the `@Sendable` closure is still run on the MainActor (at least in my testing on 18.2 RC SDKs), we can use `MainActor.assumeIsolated` + // to avoid scheduling a `MainActor` `Task`, which could delay execution and cause unexpected UI behavior. + if Thread.isMainThread { + MainActor.assumeIsolated { + self.width = width + } + } else { + Task { @MainActor in + self.width = width + } + } } } diff --git a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.ipad-XA3.png b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.ipad-XA3.png index 835cb4c..0e66f85 100644 Binary files a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.ipad-XA3.png and b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.ipad-XA3.png differ diff --git a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-XA3.png b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-XA3.png index a634320..ad0a6e3 100644 Binary files a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-XA3.png and b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-XA3.png differ diff --git a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-regular.png b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-regular.png index 5a9b287..ed72d16 100644 Binary files a/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-regular.png and b/Tests/SpeziViewsTests/__Snapshots__/SnapshotTests/testListRow.iphone-regular.png differ diff --git a/Tests/UITests/TestApp/ViewsTests/CanvasTestView.swift b/Tests/UITests/TestApp/ViewsTests/CanvasTestView.swift index 9a9eafc..4ebd6bc 100644 --- a/Tests/UITests/TestApp/ViewsTests/CanvasTestView.swift +++ b/Tests/UITests/TestApp/ViewsTests/CanvasTestView.swift @@ -47,7 +47,16 @@ struct CanvasTestView: View { } .navigationBarTitleDisplayMode(.inline) .onPreferenceChange(CanvasView.CanvasSizePreferenceKey.self) { size in - self.receivedSize = size + // See `HorizontalGeometryReader.swift` + if Thread.isMainThread { + MainActor.assumeIsolated { + self.receivedSize = size + } + } else { + Task { @MainActor in + self.receivedSize = size + } + } } } } diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 0ec3d25..e0036d2 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -572,7 +572,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Debug; @@ -606,7 +606,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Release; @@ -629,7 +629,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_TARGET_NAME = TestApp; }; @@ -652,7 +652,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_TARGET_NAME = TestApp; }; @@ -753,7 +753,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,3,7"; }; name = Test; @@ -776,7 +776,7 @@ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_TARGET_NAME = TestApp; };