Skip to content

Commit 6ddd059

Browse files
committed
Fix .introspectScrollView on iOS 14
1 parent 5c501bf commit 6ddd059

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Changelog
55

66
- Allow `Introspect` to be imported in apps that support older platform versions.
77
- Added Catalyst support in the Introspect iOS framework.
8+
- Fixed `.introspectScrollView()` on iOS 14
9+
[#55](https://github.com/siteline/SwiftUI-Introspect/issues/55)
810

911
## [0.1.0]
1012

Introspect/Introspect.swift

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ public enum Introspect {
7575
return nil
7676
}
7777

78+
/// Finds a previous sibling that is of the specified type.
79+
/// This method inspects siblings recursively.
80+
/// Returns nil if no sibling contains the specified type.
81+
public static func previousSibling<AnyViewType: PlatformView>(
82+
ofType type: AnyViewType.Type,
83+
from entry: PlatformView
84+
) -> AnyViewType? {
85+
86+
guard let superview = entry.superview,
87+
let entryIndex = superview.subviews.firstIndex(of: entry),
88+
entryIndex > 0
89+
else {
90+
return nil
91+
}
92+
93+
for subview in superview.subviews[0..<entryIndex].reversed() {
94+
if let typed = subview as? AnyViewType {
95+
return typed
96+
}
97+
}
98+
99+
return nil
100+
}
101+
78102
/// Finds a previous sibling that contains a view controller of the specified type.
79103
/// This method inspects siblings recursively.
80104
/// Returns nil if no sibling contains the specified type.
@@ -147,6 +171,29 @@ public enum Introspect {
147171
return nil
148172
}
149173

174+
/// Finds a next sibling that if of the specified type.
175+
/// This method inspects siblings recursively.
176+
/// Returns nil if no sibling contains the specified type.
177+
public static func nextSibling<AnyViewType: PlatformView>(
178+
ofType type: AnyViewType.Type,
179+
from entry: PlatformView
180+
) -> AnyViewType? {
181+
182+
guard let superview = entry.superview,
183+
let entryIndex = superview.subviews.firstIndex(of: entry)
184+
else {
185+
return nil
186+
}
187+
188+
for subview in superview.subviews[entryIndex..<superview.subviews.endIndex] {
189+
if let typed = subview as? AnyViewType {
190+
return typed
191+
}
192+
}
193+
194+
return nil
195+
}
196+
150197
/// Finds an ancestor of the specified type.
151198
/// If it reaches the top of the view without finding the specified view type, it returns nil.
152199
public static func findAncestor<AnyViewType: PlatformView>(ofType type: AnyViewType.Type, from entry: PlatformView) -> AnyViewType? {
@@ -191,18 +238,32 @@ public enum Introspect {
191238
}
192239

193240
public enum TargetViewSelector {
194-
public static func sibling<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
241+
public static func siblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
195242
guard let viewHost = Introspect.findViewHost(from: entry) else {
196243
return nil
197244
}
198245
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
199246
}
200247

201-
public static func ancestorOrSibling<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
248+
public static func siblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
249+
guard let viewHost = Introspect.findViewHost(from: entry) else {
250+
return nil
251+
}
252+
return Introspect.previousSibling(ofType: TargetView.self, from: viewHost)
253+
}
254+
255+
public static func ancestorOrSiblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
256+
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
257+
return tableView
258+
}
259+
return siblingContaining(from: entry)
260+
}
261+
262+
public static func ancestorOrSiblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
202263
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
203264
return tableView
204265
}
205-
return sibling(from: entry)
266+
return siblingOfType(from: entry)
206267
}
207268
}
208269

Introspect/ViewExtensions.swift

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,46 +72,50 @@ extension View {
7272

7373
/// Finds a `UITableView` from a `SwiftUI.List`, or `SwiftUI.List` child.
7474
public func introspectTableView(customize: @escaping (UITableView) -> ()) -> some View {
75-
return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
75+
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
7676
}
7777

7878
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
7979
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
80-
return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
80+
if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) {
81+
return introspect(selector: TargetViewSelector.ancestorOrSiblingOfType, customize: customize)
82+
} else {
83+
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
84+
}
8185
}
8286

8387
/// Finds a `UITextField` from a `SwiftUI.TextField`
8488
public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View {
85-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
89+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
8690
}
8791

8892
/// Finds a `UISwitch` from a `SwiftUI.Toggle`
8993
@available(tvOS, unavailable)
9094
public func introspectSwitch(customize: @escaping (UISwitch) -> ()) -> some View {
91-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
95+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
9296
}
9397

9498
/// Finds a `UISlider` from a `SwiftUI.Slider`
9599
@available(tvOS, unavailable)
96100
public func introspectSlider(customize: @escaping (UISlider) -> ()) -> some View {
97-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
101+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
98102
}
99103

100104
/// Finds a `UIStepper` from a `SwiftUI.Stepper`
101105
@available(tvOS, unavailable)
102106
public func introspectStepper(customize: @escaping (UIStepper) -> ()) -> some View {
103-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
107+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
104108
}
105109

106110
/// Finds a `UIDatePicker` from a `SwiftUI.DatePicker`
107111
@available(tvOS, unavailable)
108112
public func introspectDatePicker(customize: @escaping (UIDatePicker) -> ()) -> some View {
109-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
113+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
110114
}
111115

112116
/// Finds a `UISegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle`
113117
public func introspectSegmentedControl(customize: @escaping (UISegmentedControl) -> ()) -> some View {
114-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
118+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
115119
}
116120
}
117121
#endif
@@ -133,37 +137,37 @@ extension View {
133137

134138
/// Finds a `NSTableView` from a `SwiftUI.List`, or `SwiftUI.List` child.
135139
public func introspectTableView(customize: @escaping (NSTableView) -> ()) -> some View {
136-
return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
140+
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
137141
}
138142

139143
/// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
140144
public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View {
141-
return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
145+
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
142146
}
143147

144148
/// Finds a `NSTextField` from a `SwiftUI.TextField`
145149
public func introspectTextField(customize: @escaping (NSTextField) -> ()) -> some View {
146-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
150+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
147151
}
148152

149153
/// Finds a `NSSlider` from a `SwiftUI.Slider`
150154
public func introspectSlider(customize: @escaping (NSSlider) -> ()) -> some View {
151-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
155+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
152156
}
153157

154158
/// Finds a `NSStepper` from a `SwiftUI.Stepper`
155159
public func introspectStepper(customize: @escaping (NSStepper) -> ()) -> some View {
156-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
160+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
157161
}
158162

159163
/// Finds a `NSDatePicker` from a `SwiftUI.DatePicker`
160164
public func introspectDatePicker(customize: @escaping (NSDatePicker) -> ()) -> some View {
161-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
165+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
162166
}
163167

164168
/// Finds a `NSSegmentedControl` from a `SwiftUI.Picker` with style `SegmentedPickerStyle`
165169
public func introspectSegmentedControl(customize: @escaping (NSSegmentedControl) -> ()) -> some View {
166-
return introspect(selector: TargetViewSelector.sibling, customize: customize)
170+
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
167171
}
168172
}
169173
#endif

0 commit comments

Comments
 (0)