From 30fcbff4218827c70d16f62a83b59cee530fffee Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 7 Jun 2023 21:28:05 +0100 Subject: [PATCH] wip wip wip wip --- Sources/Introspect.swift | 35 +++-------- Sources/IntrospectionSelector.swift | 26 ++++---- Sources/IntrospectionView.swift | 94 +++++++++++++++++++---------- 3 files changed, 81 insertions(+), 74 deletions(-) diff --git a/Sources/Introspect.swift b/Sources/Introspect.swift index 859a431..cd699d7 100644 --- a/Sources/Introspect.swift +++ b/Sources/Introspect.swift @@ -20,17 +20,18 @@ extension View { customize: @escaping (PlatformSpecificEntity) -> Void ) -> some View { if let platform = platforms.first(where: \.isCurrent) { - let anchorID = IntrospectionAnchorID() + let introspectionViewID = IntrospectionViewID() self.background( IntrospectionAnchorView( - id: anchorID + id: introspectionViewID ) .frame(width: 0, height: 0) ) .overlay( IntrospectionView( - selector: { entity in - (platform.selector ?? .default)(entity, scope ?? viewType.scope, anchorID) + id: introspectionViewID, + selector: { controller in + (platform.selector ?? .default)(controller, scope ?? viewType.scope) }, customize: customize ) @@ -53,9 +54,6 @@ public protocol PlatformEntity: AnyObject { @_spi(Internals) func isDescendant(of other: Base) -> Bool - - @_spi(Internals) - func entityWithTag(_ tag: Int) -> Base? } extension PlatformEntity { @@ -87,12 +85,11 @@ extension PlatformEntity { } func receiver( - ofType type: PlatformSpecificEntity.Type, - anchorID: IntrospectionAnchorID + ofType type: PlatformSpecificEntity.Type ) -> PlatformSpecificEntity? { let frontEntity = self guard - let backEntity = ContiguousArray(frontEntity.ancestors).last?.entityWithTag(anchorID.hashValue), // optimize this... maybe there's a way to hold a ref somewhere in memory without having to use tags? + let backEntity = frontEntity.introspectionAnchorEntity, let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~) else { return nil @@ -124,11 +121,6 @@ extension PlatformView: PlatformEntity { public var descendants: [PlatformView] { subviews } - - @_spi(Internals) - public func entityWithTag(_ tag: Int) -> PlatformView? { - viewWithTag(tag) - } } extension PlatformViewController: PlatformEntity { @@ -146,17 +138,4 @@ extension PlatformViewController: PlatformEntity { public func isDescendant(of other: PlatformViewController) -> Bool { self.ancestors.contains(other) } - - @_spi(Internals) - public func entityWithTag(_ tag: Int) -> PlatformViewController? { - if self.view.tag == tag { - return self - } - for child in children { - if let childWithTag = child.entityWithTag(tag) { - return childWithTag - } - } - return nil - } } diff --git a/Sources/IntrospectionSelector.swift b/Sources/IntrospectionSelector.swift index 742ed13..3b93efa 100644 --- a/Sources/IntrospectionSelector.swift +++ b/Sources/IntrospectionSelector.swift @@ -6,20 +6,20 @@ public struct IntrospectionSelector { @_spi(Internals) public static func from(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self { .init( - receiverSelector: { controller, anchorID in - controller.as(Entry.self)?.receiver(ofType: Entry.self, anchorID: anchorID).flatMap(selector) + receiverSelector: { controller in + controller.as(Entry.Base.self)?.receiver(ofType: Entry.self).flatMap(selector) }, ancestorSelector: { controller in - controller.as(Entry.self)?.ancestor(ofType: Entry.self).flatMap(selector) + controller.as(Entry.Base.self)?.ancestor(ofType: Entry.self).flatMap(selector) } ) } - private var receiverSelector: (IntrospectionPlatformViewController, IntrospectionAnchorID) -> Target? + private var receiverSelector: (IntrospectionPlatformViewController) -> Target? private var ancestorSelector: (IntrospectionPlatformViewController) -> Target? private init( - receiverSelector: @escaping (IntrospectionPlatformViewController, IntrospectionAnchorID) -> Target?, + receiverSelector: @escaping (IntrospectionPlatformViewController) -> Target?, ancestorSelector: @escaping (IntrospectionPlatformViewController) -> Target? ) { self.receiverSelector = receiverSelector @@ -27,7 +27,7 @@ public struct IntrospectionSelector { } @_spi(Internals) - public func withReceiverSelector(_ selector: @escaping (PlatformViewController, IntrospectionAnchorID) -> Target?) -> Self { + public func withReceiverSelector(_ selector: @escaping (PlatformViewController) -> Target?) -> Self { var copy = self copy.receiverSelector = selector return copy @@ -40,14 +40,10 @@ public struct IntrospectionSelector { return copy } - func callAsFunction( - _ controller: IntrospectionPlatformViewController, - _ scope: IntrospectionScope, - _ anchorID: IntrospectionAnchorID - ) -> Target? { + func callAsFunction(_ controller: IntrospectionPlatformViewController, _ scope: IntrospectionScope) -> Target? { if scope.contains(.receiver), - let target = receiverSelector(controller, anchorID) + let target = receiverSelector(controller) { return target } @@ -62,14 +58,14 @@ public struct IntrospectionSelector { } extension PlatformViewController { - func `as`(_ entityType: Entity.Type) -> (any PlatformEntity)? { - if Entity.Base.self == PlatformView.self { + func `as`(_ baseType: Base.Type) -> (any PlatformEntity)? { + if Base.self == PlatformView.self { #if canImport(UIKit) return viewIfLoaded #elseif canImport(AppKit) return isViewLoaded ? view : nil #endif - } else if Entity.Base.self == PlatformViewController.self { + } else if Base.self == PlatformViewController.self { return self } return nil diff --git a/Sources/IntrospectionView.swift b/Sources/IntrospectionView.swift index ea96909..681666c 100644 --- a/Sources/IntrospectionView.swift +++ b/Sources/IntrospectionView.swift @@ -1,7 +1,30 @@ import SwiftUI -@_spi(Internals) -public typealias IntrospectionAnchorID = UUID +typealias IntrospectionViewID = UUID + +fileprivate enum IntrospectionStore { + static var shared: [IntrospectionViewID: Pair] = [:] + + struct Pair { + weak var controller: IntrospectionPlatformViewController? + weak var anchor: IntrospectionAnchorPlatformViewController? + } +} + +extension PlatformEntity { + var introspectionAnchorEntity: Base? { + if let introspectionController = self as? IntrospectionPlatformViewController { + return IntrospectionStore.shared[introspectionController.id]?.anchor~ + } + if + let view = self as? PlatformView, + let introspectionController = view.introspectionController + { + return IntrospectionStore.shared[introspectionController.id]?.anchor?.view~ + } + return nil + } +} /// ⚓️ struct IntrospectionAnchorView: PlatformViewControllerRepresentable { @@ -14,9 +37,9 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable { @Binding private var observed: Void // workaround for state changes not triggering view updates - let id: IntrospectionAnchorID + let id: IntrospectionViewID - init(id: IntrospectionAnchorID) { + init(id: IntrospectionViewID) { self._observed = .constant(()) self.id = id } @@ -31,11 +54,9 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable { } final class IntrospectionAnchorPlatformViewController: PlatformViewController { - let id: IntrospectionAnchorID - - init(id: IntrospectionAnchorID) { - self.id = id + init(id: IntrospectionViewID) { super.init(nibName: nil, bundle: nil) + IntrospectionStore.shared[id, default: .init()].anchor = self } @available(*, unavailable) @@ -43,24 +64,9 @@ final class IntrospectionAnchorPlatformViewController: PlatformViewController { fatalError("init(coder:) has not been implemented") } - #if canImport(UIKit) - override func viewDidLoad() { - super.viewDidLoad() - view.tag = id.hashValue - } - #elseif canImport(AppKit) - final class TaggableView: NSView { - private var _tag: Int? - override var tag: Int { - get { _tag ?? super.tag } - set { _tag = newValue } - } - } - + #if canImport(AppKit) && !targetEnvironment(macCatalyst) override func loadView() { - let view = TaggableView() - view.tag = id.hashValue - self.view = view + view = NSView() } #endif } @@ -78,14 +84,17 @@ struct IntrospectionView: PlatformViewControllerRepresen @Binding private var observed: Void // workaround for state changes not triggering view updates + private let id: IntrospectionViewID private let selector: (IntrospectionPlatformViewController) -> Target? private let customize: (Target) -> Void init( + id: IntrospectionViewID, selector: @escaping (IntrospectionPlatformViewController) -> Target?, customize: @escaping (Target) -> Void ) { self._observed = .constant(()) + self.id = id self.selector = selector self.customize = customize } @@ -95,7 +104,7 @@ struct IntrospectionView: PlatformViewControllerRepresen } func makePlatformViewController(context: Context) -> IntrospectionPlatformViewController { - let controller = IntrospectionPlatformViewController { controller in + let controller = IntrospectionPlatformViewController(id: id) { controller in guard let target = selector(controller) else { return } @@ -128,9 +137,14 @@ struct IntrospectionView: PlatformViewControllerRepresen } final class IntrospectionPlatformViewController: PlatformViewController { + let id: IntrospectionViewID var handler: (() -> Void)? = nil - fileprivate init(handler: ((IntrospectionPlatformViewController) -> Void)?) { + fileprivate init( + id: IntrospectionViewID, + handler: ((IntrospectionPlatformViewController) -> Void)? + ) { + self.id = id super.init(nibName: nil, bundle: nil) self.handler = { [weak self] in guard let self = self else { @@ -138,6 +152,7 @@ final class IntrospectionPlatformViewController: PlatformViewController { } handler?(self) } + IntrospectionStore.shared[id, default: .init()].controller = self } @available(*, unavailable) @@ -146,13 +161,14 @@ final class IntrospectionPlatformViewController: PlatformViewController { } #if canImport(UIKit) - override func didMove(toParent parent: UIViewController?) { - super.didMove(toParent: parent) + override func viewDidLoad() { + super.viewDidLoad() + view.introspectionController = self handler?() } - override func viewDidLoad() { - super.viewDidLoad() + override func didMove(toParent parent: UIViewController?) { + super.didMove(toParent: parent) handler?() } @@ -168,6 +184,7 @@ final class IntrospectionPlatformViewController: PlatformViewController { #elseif canImport(AppKit) override func loadView() { view = NSView() + view.introspectionController = self } override func viewDidLoad() { @@ -181,3 +198,18 @@ final class IntrospectionPlatformViewController: PlatformViewController { } #endif } + +import ObjectiveC + +extension PlatformView { + fileprivate var introspectionController: IntrospectionPlatformViewController? { + get { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + return objc_getAssociatedObject(self, key) as? IntrospectionPlatformViewController + } + set { + let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self) + objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_ASSIGN) + } + } +}