Skip to content

Commit 461fa7b

Browse files
authored
Fix an issue where nested ScrollViews would not be detected properly (#69)
* Fix an issue where nested ScrollViews would not be detected properly * Fix nested ScrollView issue on iOS 13 * Clean up implementation of siblingOrAncestorOfType to avoid extraneous searches * Adjust naming for correctness * Add nested ScrollView tests for macOS This also fixes the same issue iOS had, where nested ScrollViews would not be correctly detected on macOS 11+ * Update UIKit ScrollView tests to match AppKit ones * Add changelog entry for nested scrollview fixes * Change NSScrollView lookup mechanism for macOS 10.15 to match UIKit behavior
1 parent f246b07 commit 461fa7b

File tree

5 files changed

+173
-20
lines changed

5 files changed

+173
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Changelog
77
- Add Github Action
88
- Added `.introspectTextView()`.
99
- Update CircleCI config to use Xcode 12.4.0
10+
- Fixed nested `ScrollView` detection on iOS 14 and macOS 11
1011

1112
## [0.1.2]
1213

Introspect/Introspect.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,28 @@ public enum TargetViewSelector {
244244
}
245245
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
246246
}
247+
248+
public static func siblingContainingOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
249+
if let sibling: TargetView = siblingContaining(from: entry) {
250+
return sibling
251+
}
252+
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
253+
}
247254

248255
public static func siblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
249256
guard let viewHost = Introspect.findViewHost(from: entry) else {
250257
return nil
251258
}
252259
return Introspect.previousSibling(ofType: TargetView.self, from: viewHost)
253260
}
254-
261+
262+
public static func siblingOfTypeOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
263+
if let sibling: TargetView = siblingOfType(from: entry) {
264+
return sibling
265+
}
266+
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
267+
}
268+
255269
public static func ancestorOrSiblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
256270
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
257271
return tableView

Introspect/ViewExtensions.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ extension View {
8383
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
8484
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
8585
if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) {
86-
return introspect(selector: TargetViewSelector.ancestorOrSiblingOfType, customize: customize)
86+
return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize)
8787
} else {
88-
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
88+
return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize)
8989
}
9090
}
9191

@@ -157,7 +157,11 @@ extension View {
157157

158158
/// Finds a `NSScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
159159
public func introspectScrollView(customize: @escaping (NSScrollView) -> ()) -> some View {
160-
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
160+
if #available(macOS 11.0, *) {
161+
return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize)
162+
} else {
163+
return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize)
164+
}
161165
}
162166

163167
/// Finds a `NSTextField` from a `SwiftUI.TextField`

IntrospectTests/AppKitTests.swift

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,49 @@ private struct ListTestView: View {
5555
@available(macOS 10.15.0, *)
5656
private struct ScrollTestView: View {
5757

58-
let spy1: () -> Void
59-
let spy2: () -> Void
58+
let spy1: (NSScrollView) -> Void
59+
let spy2: (NSScrollView) -> Void
6060

6161
var body: some View {
6262
HStack {
6363
ScrollView {
6464
Text("Item 1")
6565
}
6666
.introspectScrollView { scrollView in
67-
self.spy1()
67+
self.spy1(scrollView)
6868
}
69+
6970
ScrollView {
7071
Text("Item 1")
72+
.introspectScrollView { scrollView in
73+
self.spy2(scrollView)
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
@available(macOS 10.15.0, *)
81+
private struct NestedScrollTestView: View {
82+
83+
let spy1: (NSScrollView) -> Void
84+
let spy2: (NSScrollView) -> Void
85+
86+
var body: some View {
87+
HStack {
88+
ScrollView {
89+
Text("Item 1")
90+
91+
ScrollView {
92+
Text("Item 1")
93+
}
7194
.introspectScrollView { scrollView in
72-
self.spy2()
95+
self.spy2(scrollView)
7396
}
7497
}
98+
.introspectScrollView { scrollView in
99+
self.spy1(scrollView)
100+
}
75101
}
76102
}
77103
}
@@ -175,18 +201,59 @@ class AppKitTests: XCTestCase {
175201
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
176202
}
177203

178-
func testScrollView() {
204+
func testScrollView() throws {
179205

180206
let expectation1 = XCTestExpectation()
181207
let expectation2 = XCTestExpectation()
208+
209+
var scrollView1: NSScrollView?
210+
var scrollView2: NSScrollView?
211+
182212
let view = ScrollTestView(
183-
spy1: { expectation1.fulfill() },
184-
spy2: { expectation2.fulfill() }
213+
spy1: { scrollView in
214+
scrollView1 = scrollView
215+
expectation1.fulfill() },
216+
spy2: { scrollView in
217+
scrollView2 = scrollView
218+
expectation2.fulfill()
219+
}
185220
)
186221
TestUtils.present(view: view)
187222
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
223+
224+
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
225+
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
226+
227+
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
188228
}
189-
229+
230+
func testNestedScrollView() throws {
231+
232+
let expectation1 = XCTestExpectation()
233+
let expectation2 = XCTestExpectation()
234+
235+
var scrollView1: NSScrollView?
236+
var scrollView2: NSScrollView?
237+
238+
let view = NestedScrollTestView(
239+
spy1: { scrollView in
240+
scrollView1 = scrollView
241+
expectation1.fulfill()
242+
},
243+
spy2: { scrollView in
244+
scrollView2 = scrollView
245+
expectation2.fulfill()
246+
}
247+
)
248+
TestUtils.present(view: view)
249+
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
250+
251+
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
252+
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
253+
254+
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
255+
}
256+
190257
func testTextField() {
191258

192259
let expectation = XCTestExpectation()

IntrospectTests/UIKitTests.swift

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,27 +139,52 @@ private struct ListTestView: View {
139139
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
140140
private struct ScrollTestView: View {
141141

142-
let spy1: () -> Void
143-
let spy2: () -> Void
142+
let spy1: (UIScrollView) -> Void
143+
let spy2: (UIScrollView) -> Void
144144

145145
var body: some View {
146146
HStack {
147147
ScrollView {
148148
Text("Item 1")
149149
}
150150
.introspectScrollView { scrollView in
151-
self.spy1()
151+
self.spy1(scrollView)
152152
}
153153
ScrollView {
154154
Text("Item 1")
155155
.introspectScrollView { scrollView in
156-
self.spy2()
156+
self.spy2(scrollView)
157157
}
158158
}
159159
}
160160
}
161161
}
162162

163+
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
164+
private struct NestedScrollTestView: View {
165+
166+
let spy1: (UIScrollView) -> Void
167+
let spy2: (UIScrollView) -> Void
168+
169+
var body: some View {
170+
HStack {
171+
ScrollView(showsIndicators: true) {
172+
Text("Item 1")
173+
174+
ScrollView(showsIndicators: false) {
175+
Text("Item 1")
176+
}
177+
.introspectScrollView { scrollView in
178+
self.spy2(scrollView)
179+
}
180+
}
181+
.introspectScrollView { scrollView in
182+
self.spy1(scrollView)
183+
}
184+
}
185+
}
186+
}
187+
163188
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
164189
private struct TextFieldTestView: View {
165190
let spy: () -> Void
@@ -316,18 +341,60 @@ class UIKitTests: XCTestCase {
316341
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
317342
}
318343

319-
func testScrollView() {
344+
func testScrollView() throws {
320345

321346
let expectation1 = XCTestExpectation()
322347
let expectation2 = XCTestExpectation()
348+
349+
var scrollView1: UIScrollView?
350+
var scrollView2: UIScrollView?
351+
323352
let view = ScrollTestView(
324-
spy1: { expectation1.fulfill() },
325-
spy2: { expectation2.fulfill() }
353+
spy1: { scrollView in
354+
scrollView1 = scrollView
355+
expectation1.fulfill()
356+
},
357+
spy2: { scrollView in
358+
scrollView2 = scrollView
359+
expectation2.fulfill()
360+
}
326361
)
327362
TestUtils.present(view: view)
328363
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
364+
365+
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
366+
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
367+
368+
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
329369
}
330-
370+
371+
func testNestedScrollView() throws {
372+
373+
let expectation1 = XCTestExpectation()
374+
let expectation2 = XCTestExpectation()
375+
376+
var scrollView1: UIScrollView?
377+
var scrollView2: UIScrollView?
378+
379+
let view = NestedScrollTestView(
380+
spy1: { scrollView in
381+
scrollView1 = scrollView
382+
expectation1.fulfill()
383+
},
384+
spy2: { scrollView in
385+
scrollView2 = scrollView
386+
expectation2.fulfill()
387+
}
388+
)
389+
TestUtils.present(view: view)
390+
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)
391+
392+
let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
393+
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)
394+
395+
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
396+
}
397+
331398
func testTextField() {
332399

333400
let expectation = XCTestExpectation()

0 commit comments

Comments
 (0)