From d32bdbc272cffcc1e68a2cbc7de8c1a3950d9209 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:22:03 +0100 Subject: [PATCH 1/3] WIP --- Sources/Introspect.swift | 8 ++++++++ Sources/IntrospectionView.swift | 16 ++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Sources/Introspect.swift b/Sources/Introspect.swift index 3adf8ddf..d7b99469 100644 --- a/Sources/Introspect.swift +++ b/Sources/Introspect.swift @@ -26,6 +26,14 @@ extension View { /// - scope: Optionally overrides the view's default scope of introspection. /// - customize: A closure that hands over the underlying UIKit/AppKit instance ready for customization. /// + /// Note there is no guarantee of one-time execution for this closure. As `customize` may fire multiple times, + /// make sure to guard against repeated or heavy work in your closure by keeping track of its completeness. + /// + /// Additionally, note mutating SwiftUI state within `customize` will trigger runtime warnings unless that mutation + /// is wrapped in a `DispatchQueue.main.async { ... }` call. This is because introspect attempts to hand you + /// the requested view as soon as possible, and this might mean SwiftUI isn't ready for state mutations at that + /// particular moment. + /// /// Here's an example usage: /// /// ```swift diff --git a/Sources/IntrospectionView.swift b/Sources/IntrospectionView.swift index c464b5b3..f1a5b172 100644 --- a/Sources/IntrospectionView.swift +++ b/Sources/IntrospectionView.swift @@ -121,15 +121,6 @@ struct IntrospectionView: PlatformViewControllerRepresen customize(target) controller.handler = nil } - - // - Workaround - - // iOS/tvOS 13 sometimes need a nudge on the next run loop. - if #available(iOS 14, tvOS 14, *) {} else { - DispatchQueue.main.async { [weak controller] in - controller?.handler?() - } - } - return controller } @@ -159,7 +150,12 @@ final class IntrospectionPlatformViewController: PlatformViewController { guard let self else { return } - handler?(self) + + // NB: .introspect makes no guarantees about the number of times it's callback is invoked, so the below is fair play to maximize compatibility and predictability + handler?(self) // we call this eagerly as most customization can successfully happen without a thread hop + DispatchQueue.main.async { + handler?(self) // we also thread hop to cover the rest of the cases where the underlying UI component isn't quite ready for customization + } } self.isIntrospectionPlatformEntity = true IntrospectionStore.shared[id, default: .init()].controller = self From 5fb2b0ccc638ddc4e7fd593c79f2a20a7a535950 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:26:58 +0100 Subject: [PATCH 2/3] i can't believe i've done this --- Sources/IntrospectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IntrospectionView.swift b/Sources/IntrospectionView.swift index f1a5b172..60e9a971 100644 --- a/Sources/IntrospectionView.swift +++ b/Sources/IntrospectionView.swift @@ -151,7 +151,7 @@ final class IntrospectionPlatformViewController: PlatformViewController { return } - // NB: .introspect makes no guarantees about the number of times it's callback is invoked, so the below is fair play to maximize compatibility and predictability + // NB: .introspect makes no guarantees about the number of times its callback is invoked, so the below is fair play to maximize compatibility and predictability handler?(self) // we call this eagerly as most customization can successfully happen without a thread hop DispatchQueue.main.async { handler?(self) // we also thread hop to cover the rest of the cases where the underlying UI component isn't quite ready for customization From abcdb56e34ff6e095e170e69ba99e32b782213b7 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:27:06 +0100 Subject: [PATCH 3/3] WIP --- Sources/IntrospectionView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/IntrospectionView.swift b/Sources/IntrospectionView.swift index 60e9a971..cb80bac3 100644 --- a/Sources/IntrospectionView.swift +++ b/Sources/IntrospectionView.swift @@ -151,7 +151,8 @@ final class IntrospectionPlatformViewController: PlatformViewController { return } - // NB: .introspect makes no guarantees about the number of times its callback is invoked, so the below is fair play to maximize compatibility and predictability + // NB: .introspect makes no guarantees about the number of times its callback is invoked, + // so the below is fair play to maximize compatibility and predictability handler?(self) // we call this eagerly as most customization can successfully happen without a thread hop DispatchQueue.main.async { handler?(self) // we also thread hop to cover the rest of the cases where the underlying UI component isn't quite ready for customization