Skip to content

Commit

Permalink
Fix an issue where nested ScrollViews would not be detected properly (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
simba909 authored Mar 14, 2021
1 parent f246b07 commit 461fa7b
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Changelog
- Add Github Action
- Added `.introspectTextView()`.
- Update CircleCI config to use Xcode 12.4.0
- Fixed nested `ScrollView` detection on iOS 14 and macOS 11

## [0.1.2]

Expand Down
16 changes: 15 additions & 1 deletion Introspect/Introspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,28 @@ public enum TargetViewSelector {
}
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
}

public static func siblingContainingOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let sibling: TargetView = siblingContaining(from: entry) {
return sibling
}
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
}

public static func siblingOfType<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
guard let viewHost = Introspect.findViewHost(from: entry) else {
return nil
}
return Introspect.previousSibling(ofType: TargetView.self, from: viewHost)
}


public static func siblingOfTypeOrAncestor<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let sibling: TargetView = siblingOfType(from: entry) {
return sibling
}
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
}

public static func ancestorOrSiblingContaining<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
return tableView
Expand Down
10 changes: 7 additions & 3 deletions Introspect/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ extension View {
/// Finds a `UIScrollView` from a `SwiftUI.ScrollView`, or `SwiftUI.ScrollView` child.
public func introspectScrollView(customize: @escaping (UIScrollView) -> ()) -> some View {
if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) {
return introspect(selector: TargetViewSelector.ancestorOrSiblingOfType, customize: customize)
return introspect(selector: TargetViewSelector.siblingOfTypeOrAncestor, customize: customize)
} else {
return introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize)
return introspect(selector: TargetViewSelector.siblingContainingOrAncestor, customize: customize)
}
}

Expand Down Expand Up @@ -157,7 +157,11 @@ extension View {

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

/// Finds a `NSTextField` from a `SwiftUI.TextField`
Expand Down
83 changes: 75 additions & 8 deletions IntrospectTests/AppKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,49 @@ private struct ListTestView: View {
@available(macOS 10.15.0, *)
private struct ScrollTestView: View {

let spy1: () -> Void
let spy2: () -> Void
let spy1: (NSScrollView) -> Void
let spy2: (NSScrollView) -> Void

var body: some View {
HStack {
ScrollView {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy1()
self.spy1(scrollView)
}

ScrollView {
Text("Item 1")
.introspectScrollView { scrollView in
self.spy2(scrollView)
}
}
}
}
}

@available(macOS 10.15.0, *)
private struct NestedScrollTestView: View {

let spy1: (NSScrollView) -> Void
let spy2: (NSScrollView) -> Void

var body: some View {
HStack {
ScrollView {
Text("Item 1")

ScrollView {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy2()
self.spy2(scrollView)
}
}
.introspectScrollView { scrollView in
self.spy1(scrollView)
}
}
}
}
Expand Down Expand Up @@ -175,18 +201,59 @@ class AppKitTests: XCTestCase {
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
}

func testScrollView() {
func testScrollView() throws {

let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()

var scrollView1: NSScrollView?
var scrollView2: NSScrollView?

let view = ScrollTestView(
spy1: { expectation1.fulfill() },
spy2: { expectation2.fulfill() }
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill() },
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)

let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)

XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}


func testNestedScrollView() throws {

let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()

var scrollView1: NSScrollView?
var scrollView2: NSScrollView?

let view = NestedScrollTestView(
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)

let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)

XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}

func testTextField() {

let expectation = XCTestExpectation()
Expand Down
83 changes: 75 additions & 8 deletions IntrospectTests/UIKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,27 +139,52 @@ private struct ListTestView: View {
@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct ScrollTestView: View {

let spy1: () -> Void
let spy2: () -> Void
let spy1: (UIScrollView) -> Void
let spy2: (UIScrollView) -> Void

var body: some View {
HStack {
ScrollView {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy1()
self.spy1(scrollView)
}
ScrollView {
Text("Item 1")
.introspectScrollView { scrollView in
self.spy2()
self.spy2(scrollView)
}
}
}
}
}

@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct NestedScrollTestView: View {

let spy1: (UIScrollView) -> Void
let spy2: (UIScrollView) -> Void

var body: some View {
HStack {
ScrollView(showsIndicators: true) {
Text("Item 1")

ScrollView(showsIndicators: false) {
Text("Item 1")
}
.introspectScrollView { scrollView in
self.spy2(scrollView)
}
}
.introspectScrollView { scrollView in
self.spy1(scrollView)
}
}
}
}

@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct TextFieldTestView: View {
let spy: () -> Void
Expand Down Expand Up @@ -316,18 +341,60 @@ class UIKitTests: XCTestCase {
wait(for: [expectation1, expectation2, cellExpectation1, cellExpectation2], timeout: TestUtils.Constants.timeout)
}

func testScrollView() {
func testScrollView() throws {

let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()

var scrollView1: UIScrollView?
var scrollView2: UIScrollView?

let view = ScrollTestView(
spy1: { expectation1.fulfill() },
spy2: { expectation2.fulfill() }
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)

let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)

XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}


func testNestedScrollView() throws {

let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()

var scrollView1: UIScrollView?
var scrollView2: UIScrollView?

let view = NestedScrollTestView(
spy1: { scrollView in
scrollView1 = scrollView
expectation1.fulfill()
},
spy2: { scrollView in
scrollView2 = scrollView
expectation2.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation1, expectation2], timeout: TestUtils.Constants.timeout)

let unwrappedScrollView1 = try XCTUnwrap(scrollView1)
let unwrappedScrollView2 = try XCTUnwrap(scrollView2)

XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}

func testTextField() {

let expectation = XCTestExpectation()
Expand Down

0 comments on commit 461fa7b

Please sign in to comment.