Skip to content

Commit 43a8b9c

Browse files
authored
Unify introspect modifiers (#232)
1 parent a84dfbf commit 43a8b9c

File tree

4 files changed

+186
-161
lines changed

4 files changed

+186
-161
lines changed

Sources/Introspect.swift

Lines changed: 98 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ public struct IntrospectionScope: OptionSet {
1313

1414
extension View {
1515
@ViewBuilder
16-
public func introspect<SwiftUIViewType: IntrospectableViewType, PlatformSpecificView: PlatformView>(
16+
public func introspect<SwiftUIViewType: IntrospectableViewType, PlatformSpecificEntity: PlatformEntity>(
1717
_ viewType: SwiftUIViewType,
18-
on platforms: (PlatformViewVersions<SwiftUIViewType, PlatformSpecificView>)...,
18+
on platforms: (PlatformViewVersions<SwiftUIViewType, PlatformSpecificEntity>)...,
1919
scope: IntrospectionScope? = nil,
20-
customize: @escaping (PlatformSpecificView) -> Void
20+
customize: @escaping (PlatformSpecificEntity) -> Void
2121
) -> some View {
2222
if platforms.contains(where: \.isCurrent) {
23-
let id = UUID()
23+
let id = IntrospectionAnchorID()
2424
self.background(
2525
IntrospectionAnchorView(
2626
id: id
@@ -29,17 +29,17 @@ extension View {
2929
)
3030
.overlay(
3131
IntrospectionView(
32-
selector: { (view: PlatformView) in
32+
selector: { entity in
3333
let scope = scope ?? viewType.scope
3434
if
3535
scope.contains(.receiver),
36-
let target = view.receiver(ofType: PlatformSpecificView.self, anchorID: id)
36+
let target = entity.receiver(ofType: PlatformSpecificEntity.self, anchorID: id)
3737
{
3838
return target
3939
}
4040
if
4141
scope.contains(.ancestor),
42-
let target = view.ancestor(ofType: PlatformSpecificView.self)
42+
let target = entity.ancestor(ofType: PlatformSpecificEntity.self)
4343
{
4444
return target
4545
}
@@ -53,149 +53,138 @@ extension View {
5353
self
5454
}
5555
}
56-
57-
@ViewBuilder
58-
public func introspect<SwiftUIViewType: IntrospectableViewType, PlatformSpecificViewController: PlatformViewController>(
59-
_ viewType: SwiftUIViewType,
60-
on platforms: (PlatformViewVersions<SwiftUIViewType, PlatformSpecificViewController>)...,
61-
scope: IntrospectionScope? = nil,
62-
customize: @escaping (PlatformSpecificViewController) -> Void
63-
) -> some View {
64-
if platforms.contains(where: \.isCurrent) {
65-
self.overlay(
66-
IntrospectionView(
67-
selector: { (viewController: PlatformViewController) in
68-
let scope = scope ?? viewType.scope
69-
if
70-
scope.contains(.receiver),
71-
let target = viewController.receiver(ofType: PlatformSpecificViewController.self)
72-
{
73-
return target
74-
}
75-
if
76-
scope.contains(.ancestor),
77-
let target = viewController.ancestor(ofType: PlatformSpecificViewController.self)
78-
{
79-
return target
80-
}
81-
return nil
82-
},
83-
customize: customize
84-
)
85-
.frame(width: 0, height: 0)
86-
)
87-
} else {
88-
self
89-
}
90-
}
9156
}
9257

93-
extension PlatformView {
94-
fileprivate func receiver<PlatformSpecificView: PlatformView>(
95-
ofType type: PlatformSpecificView.Type,
96-
anchorID: IntrospectionAnchorView.ID
97-
) -> PlatformSpecificView? {
98-
let frontView = self
99-
guard
100-
let backView = Array(frontView.superviews).last?.viewWithTag(anchorID.hashValue),
101-
let superview = backView.nearestCommonSuperviewWith(frontView)
102-
else {
103-
return nil
104-
}
58+
public protocol PlatformEntity: AnyObject {
59+
associatedtype Base: PlatformEntity
10560

106-
return superview
107-
.subviewsBetween(backView, and: frontView)
108-
.compactMap { $0 as? PlatformSpecificView }
109-
.first
110-
}
61+
@_spi(Internals)
62+
var ancestor: Base? { get }
11163

112-
fileprivate func ancestor<PlatformSpecificView: PlatformView>(
113-
ofType type: PlatformSpecificView.Type
114-
) -> PlatformSpecificView? {
115-
self.superviews.lazy.compactMap { $0 as? PlatformSpecificView }.first
116-
}
64+
@_spi(Internals)
65+
var descendants: [Base] { get }
66+
67+
@_spi(Internals)
68+
func isDescendant(of other: Base) -> Bool
69+
70+
@_spi(Internals)
71+
func entityWithTag(_ tag: Int) -> Base?
11772
}
11873

119-
extension PlatformView {
120-
private var superviews: some Sequence<PlatformView> {
121-
sequence(first: self, next: \.superview).dropFirst()
74+
extension PlatformEntity {
75+
@_spi(Internals)
76+
public var ancestors: some Sequence<Base> {
77+
sequence(first: self~, next: { $0.ancestor~ }).dropFirst()
12278
}
12379

124-
private func nearestCommonSuperviewWith(_ other: PlatformView) -> PlatformView? {
125-
var nearestAncestor: PlatformView? = self
80+
@_spi(Internals)
81+
public var allDescendants: [Base] {
82+
self.descendants.reduce([self~]) { $0 + $1.allDescendants~ }
83+
}
84+
85+
@_spi(Internals)
86+
public func nearestCommonAncestor(with other: Base) -> Base? {
87+
var nearestAncestor: Base? = self~
12688

127-
while let currentView = nearestAncestor, !other.isDescendant(of: currentView) {
128-
nearestAncestor = currentView.superview
89+
while let currentEntity = nearestAncestor, !other.isDescendant(of: currentEntity~) {
90+
nearestAncestor = currentEntity.ancestor~
12991
}
13092

13193
return nearestAncestor
13294
}
13395

134-
private func subviewsBetween(_ bottomView: PlatformView, and topView: PlatformView) -> [PlatformView] {
96+
@_spi(Internals)
97+
public func descendantsBetween(_ bottomEntity: Base, and topEntity: Base) -> [Base] {
13598
var entered = false
136-
var result: [PlatformView] = []
99+
var result: [Base] = []
137100

138-
for subview in self.allSubviews {
139-
if subview === bottomView {
101+
for descendant in self.allDescendants {
102+
if descendant === bottomEntity {
140103
entered = true
141104
continue
142105
}
143-
if subview === topView {
106+
if descendant === topEntity {
144107
return result
145108
}
146109
if entered {
147-
result.append(subview)
110+
result.append(descendant)
148111
}
149112
}
150113

151114
return result
152115
}
153116

154-
private var allSubviews: [PlatformView] {
155-
self.subviews.reduce([self]) { $0 + $1.allSubviews }
156-
}
157-
}
117+
fileprivate func receiver<PlatformSpecificEntity: PlatformEntity>(
118+
ofType type: PlatformSpecificEntity.Type,
119+
anchorID: IntrospectionAnchorID
120+
) -> PlatformSpecificEntity? {
121+
let frontEntity = self
122+
guard
123+
let backEntity = Array(frontEntity.ancestors).last?.entityWithTag(anchorID.hashValue),
124+
let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~)
125+
else {
126+
return nil
127+
}
158128

159-
extension PlatformViewController {
160-
fileprivate func receiver<PlatformSpecificViewController: PlatformViewController>(
161-
ofType type: PlatformSpecificViewController.Type
162-
) -> PlatformSpecificViewController? {
163-
self.hostingView?
164-
.allChildren(ofType: PlatformSpecificViewController.self)
165-
.filter { !($0 is IntrospectionPlatformViewController) }
129+
return commonAncestor
130+
.descendantsBetween(backEntity~, and: frontEntity~)
131+
.compactMap { $0 as? PlatformSpecificEntity }
166132
.first
167133
}
168134

169-
fileprivate func ancestor<PlatformSpecificViewController: PlatformViewController>(
170-
ofType type: PlatformSpecificViewController.Type
171-
) -> PlatformSpecificViewController? {
172-
self.parents
135+
fileprivate func ancestor<PlatformSpecificEntity: PlatformEntity>(
136+
ofType type: PlatformSpecificEntity.Type
137+
) -> PlatformSpecificEntity? {
138+
self.ancestors
173139
.lazy
174-
.filter { !($0 is IntrospectionPlatformViewController) }
175-
.compactMap { $0 as? PlatformSpecificViewController }
140+
.compactMap { $0 as? PlatformSpecificEntity }
176141
.first
177142
}
178143
}
179144

180-
extension PlatformViewController {
181-
private var parents: some Sequence<PlatformViewController> {
182-
sequence(first: self, next: \.parent).dropFirst()
145+
extension PlatformView: PlatformEntity {
146+
@_spi(Internals)
147+
public var ancestor: PlatformView? {
148+
superview
149+
}
150+
151+
@_spi(Internals)
152+
public var descendants: [PlatformView] {
153+
subviews
154+
}
155+
156+
@_spi(Internals)
157+
public func entityWithTag(_ tag: Int) -> PlatformView? {
158+
viewWithTag(tag)
159+
}
160+
}
161+
162+
extension PlatformViewController: PlatformEntity {
163+
@_spi(Internals)
164+
public var ancestor: PlatformViewController? {
165+
parent
183166
}
184167

185-
private var hostingView: PlatformViewController? {
186-
self.parents.first(where: {
187-
let type = String(reflecting: type(of: $0))
188-
return type.hasPrefix("SwiftUI.") && type.contains("Hosting")
189-
})
168+
@_spi(Internals)
169+
public var descendants: [PlatformViewController] {
170+
children
190171
}
191172

192-
private func allChildren<PlatformSpecificViewController: PlatformViewController>(
193-
ofType type: PlatformSpecificViewController.Type
194-
) -> [PlatformSpecificViewController] {
195-
var result = self.children.compactMap { $0 as? PlatformSpecificViewController }
196-
for subview in self.children {
197-
result.append(contentsOf: subview.allChildren(ofType: type))
173+
@_spi(Internals)
174+
public func isDescendant(of other: PlatformViewController) -> Bool {
175+
self.ancestors.contains(other)
176+
}
177+
178+
@_spi(Internals)
179+
public func entityWithTag(_ tag: Int) -> PlatformViewController? {
180+
if self.view.tag == tag {
181+
return self
198182
}
199-
return result
183+
for child in children {
184+
if let childWithTag = child.entityWithTag(tag) {
185+
return childWithTag
186+
}
187+
}
188+
return nil
200189
}
201190
}

0 commit comments

Comments
 (0)