142 lines
4.0 KiB
Swift
142 lines
4.0 KiB
Swift
import SwiftUI
|
|
|
|
public struct IntrospectionScope: OptionSet {
|
|
public static let receiver = Self(rawValue: 1 << 0)
|
|
public static let ancestor = Self(rawValue: 1 << 1)
|
|
|
|
@_spi(Private) public let rawValue: UInt
|
|
|
|
@_spi(Private) public init(rawValue: UInt) {
|
|
self.rawValue = rawValue
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
@ViewBuilder
|
|
public func introspect<SwiftUIViewType: IntrospectableViewType, PlatformSpecificEntity: PlatformEntity>(
|
|
_ viewType: SwiftUIViewType,
|
|
on platforms: (PlatformViewVersions<SwiftUIViewType, PlatformSpecificEntity>)...,
|
|
scope: IntrospectionScope? = nil,
|
|
customize: @escaping (PlatformSpecificEntity) -> Void
|
|
) -> some View {
|
|
if let platform = platforms.first(where: \.isCurrent) {
|
|
let introspectionViewID = IntrospectionViewID()
|
|
self.background(
|
|
IntrospectionAnchorView(
|
|
id: introspectionViewID
|
|
)
|
|
.frame(width: 0, height: 0)
|
|
)
|
|
.overlay(
|
|
IntrospectionView(
|
|
id: introspectionViewID,
|
|
selector: { controller in
|
|
(platform.selector ?? .default)(controller, scope ?? viewType.scope)
|
|
},
|
|
customize: customize
|
|
)
|
|
.frame(width: 0, height: 0)
|
|
)
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
}
|
|
|
|
public protocol PlatformEntity: AnyObject {
|
|
associatedtype Base: PlatformEntity
|
|
|
|
@_spi(Internals)
|
|
var ancestor: Base? { get }
|
|
|
|
@_spi(Internals)
|
|
var descendants: [Base] { get }
|
|
|
|
@_spi(Internals)
|
|
func isDescendant(of other: Base) -> Bool
|
|
}
|
|
|
|
extension PlatformEntity {
|
|
@_spi(Internals)
|
|
public var ancestors: some Sequence<Base> {
|
|
sequence(first: self~, next: { $0.ancestor~ }).dropFirst()
|
|
}
|
|
|
|
@_spi(Internals)
|
|
public var allDescendants: some Sequence<Base> {
|
|
recursiveSequence([self~], children: { $0.descendants~ }).dropFirst()
|
|
}
|
|
|
|
func nearestCommonAncestor(with other: Base) -> Base? {
|
|
var nearestAncestor: Base? = self~
|
|
|
|
while let currentEntity = nearestAncestor, !other.isDescendant(of: currentEntity~) {
|
|
nearestAncestor = currentEntity.ancestor~
|
|
}
|
|
|
|
return nearestAncestor
|
|
}
|
|
|
|
func allDescendants(between bottomEntity: Base, and topEntity: Base) -> some Sequence<Base> {
|
|
self.allDescendants
|
|
.lazy
|
|
.drop(while: { $0 !== bottomEntity })
|
|
.prefix(while: { $0 !== topEntity })
|
|
}
|
|
|
|
func receiver<PlatformSpecificEntity: PlatformEntity>(
|
|
ofType type: PlatformSpecificEntity.Type
|
|
) -> PlatformSpecificEntity? {
|
|
let frontEntity = self
|
|
guard
|
|
let backEntity = frontEntity.introspectionAnchorEntity,
|
|
let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~)
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return commonAncestor
|
|
.allDescendants(between: backEntity~, and: frontEntity~)
|
|
.compactMap { $0 as? PlatformSpecificEntity }
|
|
.first
|
|
}
|
|
|
|
func ancestor<PlatformSpecificEntity: PlatformEntity>(
|
|
ofType type: PlatformSpecificEntity.Type
|
|
) -> PlatformSpecificEntity? {
|
|
self.ancestors
|
|
.lazy
|
|
.compactMap { $0 as? PlatformSpecificEntity }
|
|
.first
|
|
}
|
|
}
|
|
|
|
extension PlatformView: PlatformEntity {
|
|
@_spi(Internals)
|
|
public var ancestor: PlatformView? {
|
|
superview
|
|
}
|
|
|
|
@_spi(Internals)
|
|
public var descendants: [PlatformView] {
|
|
subviews
|
|
}
|
|
}
|
|
|
|
extension PlatformViewController: PlatformEntity {
|
|
@_spi(Internals)
|
|
public var ancestor: PlatformViewController? {
|
|
parent
|
|
}
|
|
|
|
@_spi(Internals)
|
|
public var descendants: [PlatformViewController] {
|
|
children
|
|
}
|
|
|
|
@_spi(Internals)
|
|
public func isDescendant(of other: PlatformViewController) -> Bool {
|
|
self.ancestors.contains(other)
|
|
}
|
|
}
|