diff --git a/Sources/Introspect.swift b/Sources/Introspect.swift index 0edf3bd..d0979fe 100644 --- a/Sources/Introspect.swift +++ b/Sources/Introspect.swift @@ -19,36 +19,23 @@ extension View { scope: IntrospectionScope? = nil, customize: @escaping (PlatformSpecificEntity) -> Void ) -> some View { - if platforms.contains(where: \.isCurrent) { - let id = IntrospectionAnchorID() + if let platform = platforms.first(where: \.isCurrent) { + let anchorID = IntrospectionAnchorID() self.background( - IntrospectionAnchorView( - id: id - ) - .frame(width: 0, height: 0) + IntrospectionAnchorView( + id: anchorID ) - .overlay( - IntrospectionView( - selector: { entity in - let scope = scope ?? viewType.scope - if - scope.contains(.receiver), - let target = entity.receiver(ofType: PlatformSpecificEntity.self, anchorID: id) - { - return target - } - if - scope.contains(.ancestor), - let target = entity.ancestor(ofType: PlatformSpecificEntity.self) - { - return target - } - return nil - }, - customize: customize - ) - .frame(width: 0, height: 0) + .frame(width: 0, height: 0) + ) + .overlay( + IntrospectionView( + selector: { entity in + (platform.selector ?? .default)(entity, scope ?? viewType.scope, anchorID) + }, + customize: customize ) + .frame(width: 0, height: 0) + ) } else { self } @@ -72,18 +59,15 @@ public protocol PlatformEntity: AnyObject { } extension PlatformEntity { - @_spi(Internals) - public var ancestors: some Sequence { + var ancestors: some Sequence { sequence(first: self~, next: { $0.ancestor~ }).dropFirst() } - @_spi(Internals) - public var allDescendants: [Base] { + var allDescendants: [Base] { self.descendants.reduce([self~]) { $0 + $1.allDescendants~ } } - @_spi(Internals) - public func nearestCommonAncestor(with other: Base) -> Base? { + func nearestCommonAncestor(with other: Base) -> Base? { var nearestAncestor: Base? = self~ while let currentEntity = nearestAncestor, !other.isDescendant(of: currentEntity~) { @@ -93,8 +77,7 @@ extension PlatformEntity { return nearestAncestor } - @_spi(Internals) - public func descendantsBetween(_ bottomEntity: Base, and topEntity: Base) -> [Base] { + func descendantsBetween(_ bottomEntity: Base, and topEntity: Base) -> [Base] { var result: [Base] = [] var entered = false @@ -111,7 +94,7 @@ extension PlatformEntity { return result } - fileprivate func receiver( + func receiver( ofType type: PlatformSpecificEntity.Type, anchorID: IntrospectionAnchorID ) -> PlatformSpecificEntity? { @@ -129,7 +112,7 @@ extension PlatformEntity { .first } - fileprivate func ancestor( + func ancestor( ofType type: PlatformSpecificEntity.Type ) -> PlatformSpecificEntity? { self.ancestors diff --git a/Sources/IntrospectionSelector.swift b/Sources/IntrospectionSelector.swift new file mode 100644 index 0000000..e44d364 --- /dev/null +++ b/Sources/IntrospectionSelector.swift @@ -0,0 +1,38 @@ +public struct IntrospectionSelector { + private let selector: (any PlatformEntity, IntrospectionScope, IntrospectionAnchorID) -> Target? + + static var `default`: Self { .from(Target.self, selector: { $0 }) } + + @_spi(Internals) + public static func from(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self { + .init { entity, scope, anchorID in + if + scope.contains(.receiver), + let entry = entity.receiver(ofType: Entry.self, anchorID: anchorID), + let target = selector(entry) + { + return target + } + if + scope.contains(.ancestor), + let entry = entity.ancestor(ofType: Entry.self), + let target = selector(entry) + { + return target + } + return nil + } + } + + init(_ selector: @escaping (any PlatformEntity, IntrospectionScope, IntrospectionAnchorID) -> Target?) { + self.selector = selector + } + + func callAsFunction( + _ entity: any PlatformEntity, + _ scope: IntrospectionScope, + _ anchorID: IntrospectionAnchorID + ) -> Target? { + selector(entity, scope, anchorID) + } +} diff --git a/Sources/PlatformViewVersion.swift b/Sources/PlatformViewVersion.swift index fd1a912..9684a55 100644 --- a/Sources/PlatformViewVersion.swift +++ b/Sources/PlatformViewVersion.swift @@ -1,35 +1,49 @@ import SwiftUI -public struct PlatformViewVersions { +public struct PlatformViewVersions { let isCurrent: Bool + let selector: IntrospectionSelector? + + private init( + _ versions: [PlatformViewVersion] + ) { + if let currentVersion = versions.first(where: \.isCurrent) { + self.isCurrent = true + self.selector = currentVersion.selector + } else { + self.isCurrent = false + self.selector = nil + } + } public static func iOS(_ versions: (iOSViewVersion)...) -> Self { - Self(isCurrent: versions.contains(where: \.isCurrent)) + Self(versions) } public static func tvOS(_ versions: (tvOSViewVersion)...) -> Self { - Self(isCurrent: versions.contains(where: \.isCurrent)) + Self(versions) } public static func macOS(_ versions: (macOSViewVersion)...) -> Self { - Self(isCurrent: versions.contains(where: \.isCurrent)) + Self(versions) } } -public typealias iOSViewVersion = +public typealias iOSViewVersion = PlatformViewVersion -public typealias tvOSViewVersion = +public typealias tvOSViewVersion = PlatformViewVersion -public typealias macOSViewVersion = +public typealias macOSViewVersion = PlatformViewVersion -public struct PlatformViewVersion { +public struct PlatformViewVersion { let isCurrent: Bool + let selector: IntrospectionSelector? } extension PlatformViewVersion { - @_spi(Internals) public init(for version: Version) { - self.init(isCurrent: version.isCurrent) + @_spi(Internals) public init(for version: Version, selector: IntrospectionSelector? = nil) { + self.init(isCurrent: version.isCurrent, selector: selector) } @_spi(Internals) public static func unavailable(file: StaticString = #file, line: UInt = #line) -> Self { @@ -46,6 +60,6 @@ extension PlatformViewVersion { https://github.com/siteline/swiftui-introspect/issues/new?title=`\(fileName):\(line)`+should+be+marked+unavailable """ ) - return Self(isCurrent: false) + return Self(isCurrent: false, selector: nil) } }