Skip to content

Commit d43a72c

Browse files
authored
Fix UIWindow dealloc issue (#439)
Add triggerLayoutWithWindow helper method
1 parent e369be4 commit d43a72c

10 files changed

+179
-204
lines changed

Sources/OpenSwiftUITestsSupport/Integration/PlatformAlias.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package import SwiftUI
1010

1111
#if os(iOS)
1212
package import UIKit
13+
package typealias PlatformWindow = UIWindow
1314
package typealias PlatformViewController = UIViewController
1415
package typealias PlatformView = UIView
1516
package typealias PlatformViewControllerRepresentable = UIViewControllerRepresentable
@@ -24,6 +25,7 @@ extension Color {
2425
}
2526
#elseif os(macOS)
2627
package import AppKit
28+
package typealias PlatformWindow = NSWindow
2729
package typealias PlatformViewController = NSViewController
2830
package typealias PlatformView = NSView
2931
// package typealias PlatformViewControllerRepresentable = NSViewControllerRepresentable

Sources/OpenSwiftUITestsSupport/Integration/PlatformHostingControllerHelper.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
#if canImport(Darwin)
66
#if os(iOS)
7-
import UIKit
7+
package import UIKit
88
#elseif os(macOS)
9-
import AppKit
9+
package import AppKit
1010
#endif
1111

12+
package import Testing
13+
1214
extension PlatformViewController {
15+
// NOTE: Remember to withExtendedLifetime for window to ensure it is not deallocated duration animation or update.
1316
package func triggerLayout() {
1417
#if os(iOS)
1518
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
@@ -29,4 +32,48 @@ extension PlatformViewController {
2932
#endif
3033
}
3134
}
35+
36+
@MainActor
37+
package func triggerLayoutWithWindow(
38+
expectedCount: Int = 1,
39+
_ body: @escaping @MainActor (Confirmation) -> PlatformViewController
40+
) async throws {
41+
var window: PlatformWindow!
42+
await confirmation(expectedCount: expectedCount) { confirmation in
43+
let vc = body(confirmation)
44+
vc.triggerLayout()
45+
window = vc.view.window
46+
}
47+
withExtendedLifetime(window) {}
48+
}
49+
50+
@MainActor
51+
package func triggerLayoutWithWindow(
52+
_ body: @escaping @MainActor (UnsafeContinuation<Void, Never>) -> PlatformViewController
53+
) async throws {
54+
var window: PlatformWindow!
55+
await withUnsafeContinuation { continuation in
56+
let vc = body(continuation)
57+
vc.triggerLayout()
58+
window = vc.view.window
59+
}
60+
withExtendedLifetime(window) {}
61+
}
62+
63+
@MainActor
64+
package func triggerLayoutWithWindow(
65+
expectedCount: Int = 1,
66+
_ body: @escaping @MainActor (Confirmation, UnsafeContinuation<Void, Never>) -> PlatformViewController
67+
) async throws {
68+
var window: PlatformWindow!
69+
await confirmation(expectedCount: expectedCount) { @MainActor confirmation in
70+
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
71+
let vc = body(confirmation, continuation)
72+
vc.triggerLayout()
73+
window = vc.view.window
74+
}
75+
}
76+
withExtendedLifetime(window) {}
77+
}
78+
3279
#endif

Tests/OpenSwiftUICompatibilityTests/Animation/Animation/AnimationCompletionCompatibilityTests.swift

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ struct AnimationCompletionCompatibilityTests {
4747
}
4848
}
4949
}
50-
5150
struct RemovedCompletionView: View {
5251
@State private var showRed = false
5352

@@ -71,21 +70,14 @@ struct AnimationCompletionCompatibilityTests {
7170
}
7271
}
7372
}
74-
75-
var vc: PlatformViewController!
76-
await confirmation(expectedCount: 2) { @MainActor confirmation in
77-
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
78-
vc = PlatformHostingController(
79-
rootView: ContentView(
80-
confirmation: confirmation,
81-
continuation: continuation
82-
)
73+
74+
try await triggerLayoutWithWindow(expectedCount: 2) { confirmation, continuation in
75+
PlatformHostingController(
76+
rootView: ContentView(
77+
confirmation: confirmation,
78+
continuation: continuation
8379
)
84-
vc.triggerLayout()
85-
}
86-
}
87-
withExtendedLifetime(vc) {
88-
#expect(Helper.values == [1, 2])
80+
)
8981
}
9082
}
9183
}

Tests/OpenSwiftUICompatibilityTests/Data/State/StateCompatibilityTests.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ struct StateCompatibilityTests {
2323
}
2424
}
2525
}
26-
var vc: PlatformViewController!
27-
await confirmation { @MainActor confirmation in
28-
vc = PlatformHostingController(rootView: ContentView(confirmation: confirmation))
29-
vc.triggerLayout()
26+
27+
try await triggerLayoutWithWindow { confirmation in
28+
PlatformHostingController(
29+
rootView: ContentView(
30+
confirmation: confirmation
31+
)
32+
)
3033
}
31-
withExtendedLifetime(vc) {}
3234
}
3335
}

Tests/OpenSwiftUICompatibilityTests/Integration/PlatformHostingControllerCompatibilityTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ struct PlatformHostingControllerCompatibilityTests {
2222
}
2323
let vc = PlatformHostingController(rootView: ContentView())
2424
vc.triggerLayout()
25-
withExtendedLifetime(vc) {}
2625
}
2726

2827
@Test(
@@ -41,6 +40,5 @@ struct PlatformHostingControllerCompatibilityTests {
4140
}
4241
let vc = PlatformHostingController(rootView: ContentView())
4342
vc.triggerLayout()
44-
withExtendedLifetime(vc) {}
4543
}
4644
}

Tests/OpenSwiftUICompatibilityTests/Modifier/ViewModifier/AppearanceActionModifierCompatibilityTests.swift

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ struct AppearanceActionModifierCompatibilityTests {
2121
}
2222
}
2323
}
24-
var vc: PlatformViewController!
25-
await confirmation { @MainActor confirmation in
26-
vc = PlatformHostingController(rootView: ContentView(confirmation: confirmation))
27-
vc.triggerLayout()
24+
25+
try await triggerLayoutWithWindow { confirmation in
26+
PlatformHostingController(
27+
rootView: ContentView(
28+
confirmation: confirmation
29+
)
30+
)
2831
}
29-
withExtendedLifetime(vc) {}
3032
}
3133

3234
@Test
@@ -38,29 +40,36 @@ struct AppearanceActionModifierCompatibilityTests {
3840

3941
struct ContentView: View {
4042
@State private var toggle = false
43+
var continuation: UnsafeContinuation<Void, Never>
4144

4245
var body: some View {
4346
Color.red
4447
.onAppear {
45-
Helper.result += "A"
46-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
47-
toggle.toggle()
48+
if Helper.result.isEmpty {
49+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
50+
toggle.toggle()
51+
}
52+
} else {
53+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
54+
continuation.resume()
55+
}
4856
}
57+
Helper.result += "A"
4958
}
5059
.onDisappear {
5160
Helper.result += "D"
5261
}
5362
.id(toggle)
5463
}
5564
}
56-
let vc = PlatformHostingController(rootView: ContentView())
57-
vc.triggerLayout()
58-
#expect(Helper.result.hasPrefix("A"))
59-
var timeout = 5
60-
while !Helper.result.hasPrefix("AAD"), timeout > 0 {
61-
try await Task.sleep(for: .seconds(1))
62-
timeout -= 1
65+
66+
try await triggerLayoutWithWindow { continuation in
67+
PlatformHostingController(
68+
rootView: ContentView(
69+
continuation: continuation
70+
)
71+
)
6372
}
64-
withExtendedLifetime(vc) {}
73+
#expect(Helper.result == "AADD")
6574
}
6675
}

Tests/OpenSwiftUICompatibilityTests/Modifier/ViewModifier/TaskModifierCompatibilityTests.swift

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,14 @@ struct TaskModifierCompatibilityTests {
2222
}
2323
}
2424

25-
var vc: PlatformViewController!
26-
await confirmation(expectedCount: 1) { @MainActor confirmation in
27-
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
28-
vc = PlatformHostingController(
29-
rootView: ContentView(
30-
confirmation: confirmation,
31-
continuation: continuation
32-
)
25+
try await triggerLayoutWithWindow { confirmation, continuation in
26+
PlatformHostingController(
27+
rootView: ContentView(
28+
confirmation: confirmation,
29+
continuation: continuation
3330
)
34-
vc.triggerLayout()
35-
}
31+
)
3632
}
37-
withExtendedLifetime(vc) {}
3833
}
3934

4035
@Test
@@ -59,19 +54,14 @@ struct TaskModifierCompatibilityTests {
5954
}
6055
}
6156
}
62-
63-
var vc: PlatformViewController!
64-
await confirmation(expectedCount: 2) { @MainActor confirmation in
65-
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
66-
vc = PlatformHostingController(
67-
rootView: ContentView(
68-
confirmation: confirmation,
69-
continuation: continuation
70-
)
57+
58+
try await triggerLayoutWithWindow(expectedCount: 2) { confirmation, continuation in
59+
PlatformHostingController(
60+
rootView: ContentView(
61+
confirmation: confirmation,
62+
continuation: continuation
7163
)
72-
vc.triggerLayout()
73-
}
64+
)
7465
}
75-
withExtendedLifetime(vc) {}
7666
}
7767
}

0 commit comments

Comments
 (0)