wip

wip

wip
This commit is contained in:
David Roman 2023-06-07 21:28:05 +01:00
parent e7f08760f8
commit 30fcbff421
No known key found for this signature in database
GPG Key ID: 7058646EEFCB70A7
3 changed files with 81 additions and 74 deletions

View File

@ -20,17 +20,18 @@ extension View {
customize: @escaping (PlatformSpecificEntity) -> Void customize: @escaping (PlatformSpecificEntity) -> Void
) -> some View { ) -> some View {
if let platform = platforms.first(where: \.isCurrent) { if let platform = platforms.first(where: \.isCurrent) {
let anchorID = IntrospectionAnchorID() let introspectionViewID = IntrospectionViewID()
self.background( self.background(
IntrospectionAnchorView( IntrospectionAnchorView(
id: anchorID id: introspectionViewID
) )
.frame(width: 0, height: 0) .frame(width: 0, height: 0)
) )
.overlay( .overlay(
IntrospectionView( IntrospectionView(
selector: { entity in id: introspectionViewID,
(platform.selector ?? .default)(entity, scope ?? viewType.scope, anchorID) selector: { controller in
(platform.selector ?? .default)(controller, scope ?? viewType.scope)
}, },
customize: customize customize: customize
) )
@ -53,9 +54,6 @@ public protocol PlatformEntity: AnyObject {
@_spi(Internals) @_spi(Internals)
func isDescendant(of other: Base) -> Bool func isDescendant(of other: Base) -> Bool
@_spi(Internals)
func entityWithTag(_ tag: Int) -> Base?
} }
extension PlatformEntity { extension PlatformEntity {
@ -87,12 +85,11 @@ extension PlatformEntity {
} }
func receiver<PlatformSpecificEntity: PlatformEntity>( func receiver<PlatformSpecificEntity: PlatformEntity>(
ofType type: PlatformSpecificEntity.Type, ofType type: PlatformSpecificEntity.Type
anchorID: IntrospectionAnchorID
) -> PlatformSpecificEntity? { ) -> PlatformSpecificEntity? {
let frontEntity = self let frontEntity = self
guard 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~) let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~)
else { else {
return nil return nil
@ -124,11 +121,6 @@ extension PlatformView: PlatformEntity {
public var descendants: [PlatformView] { public var descendants: [PlatformView] {
subviews subviews
} }
@_spi(Internals)
public func entityWithTag(_ tag: Int) -> PlatformView? {
viewWithTag(tag)
}
} }
extension PlatformViewController: PlatformEntity { extension PlatformViewController: PlatformEntity {
@ -146,17 +138,4 @@ extension PlatformViewController: PlatformEntity {
public func isDescendant(of other: PlatformViewController) -> Bool { public func isDescendant(of other: PlatformViewController) -> Bool {
self.ancestors.contains(other) 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
}
} }

View File

@ -6,20 +6,20 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
@_spi(Internals) @_spi(Internals)
public static func from<Entry: PlatformEntity>(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self { public static func from<Entry: PlatformEntity>(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self {
.init( .init(
receiverSelector: { controller, anchorID in receiverSelector: { controller in
controller.as(Entry.self)?.receiver(ofType: Entry.self, anchorID: anchorID).flatMap(selector) controller.as(Entry.Base.self)?.receiver(ofType: Entry.self).flatMap(selector)
}, },
ancestorSelector: { controller in 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 var ancestorSelector: (IntrospectionPlatformViewController) -> Target?
private init( private init(
receiverSelector: @escaping (IntrospectionPlatformViewController, IntrospectionAnchorID) -> Target?, receiverSelector: @escaping (IntrospectionPlatformViewController) -> Target?,
ancestorSelector: @escaping (IntrospectionPlatformViewController) -> Target? ancestorSelector: @escaping (IntrospectionPlatformViewController) -> Target?
) { ) {
self.receiverSelector = receiverSelector self.receiverSelector = receiverSelector
@ -27,7 +27,7 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
} }
@_spi(Internals) @_spi(Internals)
public func withReceiverSelector(_ selector: @escaping (PlatformViewController, IntrospectionAnchorID) -> Target?) -> Self { public func withReceiverSelector(_ selector: @escaping (PlatformViewController) -> Target?) -> Self {
var copy = self var copy = self
copy.receiverSelector = selector copy.receiverSelector = selector
return copy return copy
@ -40,14 +40,10 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
return copy return copy
} }
func callAsFunction( func callAsFunction(_ controller: IntrospectionPlatformViewController, _ scope: IntrospectionScope) -> Target? {
_ controller: IntrospectionPlatformViewController,
_ scope: IntrospectionScope,
_ anchorID: IntrospectionAnchorID
) -> Target? {
if if
scope.contains(.receiver), scope.contains(.receiver),
let target = receiverSelector(controller, anchorID) let target = receiverSelector(controller)
{ {
return target return target
} }
@ -62,14 +58,14 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
} }
extension PlatformViewController { extension PlatformViewController {
func `as`<Entity: PlatformEntity>(_ entityType: Entity.Type) -> (any PlatformEntity)? { func `as`<Base: PlatformEntity>(_ baseType: Base.Type) -> (any PlatformEntity)? {
if Entity.Base.self == PlatformView.self { if Base.self == PlatformView.self {
#if canImport(UIKit) #if canImport(UIKit)
return viewIfLoaded return viewIfLoaded
#elseif canImport(AppKit) #elseif canImport(AppKit)
return isViewLoaded ? view : nil return isViewLoaded ? view : nil
#endif #endif
} else if Entity.Base.self == PlatformViewController.self { } else if Base.self == PlatformViewController.self {
return self return self
} }
return nil return nil

View File

@ -1,7 +1,30 @@
import SwiftUI import SwiftUI
@_spi(Internals) typealias IntrospectionViewID = UUID
public typealias IntrospectionAnchorID = 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 { struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
@ -14,9 +37,9 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
@Binding @Binding
private var observed: Void // workaround for state changes not triggering view updates 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._observed = .constant(())
self.id = id self.id = id
} }
@ -31,11 +54,9 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
} }
final class IntrospectionAnchorPlatformViewController: PlatformViewController { final class IntrospectionAnchorPlatformViewController: PlatformViewController {
let id: IntrospectionAnchorID init(id: IntrospectionViewID) {
init(id: IntrospectionAnchorID) {
self.id = id
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
IntrospectionStore.shared[id, default: .init()].anchor = self
} }
@available(*, unavailable) @available(*, unavailable)
@ -43,24 +64,9 @@ final class IntrospectionAnchorPlatformViewController: PlatformViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
#if canImport(UIKit) #if canImport(AppKit) && !targetEnvironment(macCatalyst)
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 }
}
}
override func loadView() { override func loadView() {
let view = TaggableView() view = NSView()
view.tag = id.hashValue
self.view = view
} }
#endif #endif
} }
@ -78,14 +84,17 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
@Binding @Binding
private var observed: Void // workaround for state changes not triggering view updates private var observed: Void // workaround for state changes not triggering view updates
private let id: IntrospectionViewID
private let selector: (IntrospectionPlatformViewController) -> Target? private let selector: (IntrospectionPlatformViewController) -> Target?
private let customize: (Target) -> Void private let customize: (Target) -> Void
init( init(
id: IntrospectionViewID,
selector: @escaping (IntrospectionPlatformViewController) -> Target?, selector: @escaping (IntrospectionPlatformViewController) -> Target?,
customize: @escaping (Target) -> Void customize: @escaping (Target) -> Void
) { ) {
self._observed = .constant(()) self._observed = .constant(())
self.id = id
self.selector = selector self.selector = selector
self.customize = customize self.customize = customize
} }
@ -95,7 +104,7 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
} }
func makePlatformViewController(context: Context) -> IntrospectionPlatformViewController { func makePlatformViewController(context: Context) -> IntrospectionPlatformViewController {
let controller = IntrospectionPlatformViewController { controller in let controller = IntrospectionPlatformViewController(id: id) { controller in
guard let target = selector(controller) else { guard let target = selector(controller) else {
return return
} }
@ -128,9 +137,14 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
} }
final class IntrospectionPlatformViewController: PlatformViewController { final class IntrospectionPlatformViewController: PlatformViewController {
let id: IntrospectionViewID
var handler: (() -> Void)? = nil 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) super.init(nibName: nil, bundle: nil)
self.handler = { [weak self] in self.handler = { [weak self] in
guard let self = self else { guard let self = self else {
@ -138,6 +152,7 @@ final class IntrospectionPlatformViewController: PlatformViewController {
} }
handler?(self) handler?(self)
} }
IntrospectionStore.shared[id, default: .init()].controller = self
} }
@available(*, unavailable) @available(*, unavailable)
@ -146,13 +161,14 @@ final class IntrospectionPlatformViewController: PlatformViewController {
} }
#if canImport(UIKit) #if canImport(UIKit)
override func didMove(toParent parent: UIViewController?) { override func viewDidLoad() {
super.didMove(toParent: parent) super.viewDidLoad()
view.introspectionController = self
handler?() handler?()
} }
override func viewDidLoad() { override func didMove(toParent parent: UIViewController?) {
super.viewDidLoad() super.didMove(toParent: parent)
handler?() handler?()
} }
@ -168,6 +184,7 @@ final class IntrospectionPlatformViewController: PlatformViewController {
#elseif canImport(AppKit) #elseif canImport(AppKit)
override func loadView() { override func loadView() {
view = NSView() view = NSView()
view.introspectionController = self
} }
override func viewDidLoad() { override func viewDidLoad() {
@ -181,3 +198,18 @@ final class IntrospectionPlatformViewController: PlatformViewController {
} }
#endif #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)
}
}
}