Skip to content

Fix UITextField with cornerRadius #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 17, 2021
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Changelog

## master

- Fix UITextField with cornerRadius
- Added `.introspectTabView()` on macOS

## [0.1.3]
Expand Down
53 changes: 53 additions & 0 deletions Introspect/Introspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,39 @@ public enum Introspect {
return nil
}

/// Finds a subview of the specified type.
/// This method will recursively look for this view.
/// Returns nil if it can't find a view of the specified type.
public static func findChildUsingFrame<AnyViewType: PlatformView>(
ofType type: AnyViewType.Type,
in root: PlatformView,
from originalEntry: PlatformView
) -> AnyViewType? {
var children: [AnyViewType] = []
for subview in root.subviews {
if let typed = subview as? AnyViewType {
children.append(typed)
} else if let typed = findChild(ofType: type, in: subview) {
children.append(typed)
}
}

if children.count > 1 {
for child in children {
let converted = child.convert(
CGPoint(x: originalEntry.frame.size.width / 2, y: originalEntry.frame.size.height / 2),
from: originalEntry
)
if CGRect(origin: .zero, size: child.frame.size).contains(converted) {
return child
}
}
return nil
}

return children.first
}

/// Finds a previous sibling that contains a view of the specified type.
/// This method inspects siblings recursively.
/// Returns nil if no sibling contains the specified type.
Expand Down Expand Up @@ -207,6 +240,19 @@ public enum Introspect {
return nil
}

/// Finds an ancestor of the specified type.
/// If it reaches the top of the view without finding the specified view type, it returns nil.
public static func findAncestorOrAncestorChild<AnyViewType: PlatformView>(ofType type: AnyViewType.Type, from entry: PlatformView) -> AnyViewType? {
var superview = entry.superview
while let s = superview {
if let typed = s as? AnyViewType ?? findChildUsingFrame(ofType: type, in: s, from: entry) {
return typed
}
superview = s.superview
}
return nil
}

/// Finds the hosting view of a specific subview.
/// Hosting views generally contain subviews for one specific SwiftUI element.
/// For instance, if there are multiple text fields in a VStack, the hosting view will contain those text fields (and their host views, see below).
Expand Down Expand Up @@ -252,6 +298,13 @@ public enum TargetViewSelector {
return Introspect.findAncestor(ofType: TargetView.self, from: entry)
}

public static func siblingContainingOrAncestorOrAncestorChild<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
if let sibling: TargetView = siblingContaining(from: entry) {
return sibling
}
return Introspect.findAncestorOrAncestorChild(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
Expand Down
2 changes: 1 addition & 1 deletion Introspect/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ extension View {

/// Finds a `UITextField` from a `SwiftUI.TextField`
public func introspectTextField(customize: @escaping (UITextField) -> ()) -> some View {
return introspect(selector: TargetViewSelector.siblingContaining, customize: customize)
return introspect(selector: TargetViewSelector.siblingContainingOrAncestorOrAncestorChild, customize: customize)
}

/// Finds a `UITextView` from a `SwiftUI.TextEditor`
Expand Down
65 changes: 55 additions & 10 deletions IntrospectTests/UIKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,31 @@ private struct NestedScrollTestView: View {

@available(iOS 13.0, tvOS 13.0, macOS 10.15.0, *)
private struct TextFieldTestView: View {
let spy: () -> Void
let spy1: (UITextField) -> Void
let spy2: (UITextField) -> Void
let spy3: (UITextField) -> Void
@State private var textFieldValue = ""

let textField1Placeholder = "Text Field 1"
let textField2Placeholder = "Text Field 2"
let textField3Placeholder = "Text Field 3"

var body: some View {
TextField("Text Field", text: $textFieldValue)
.introspectTextField { textField in
self.spy()
VStack {
TextField(textField1Placeholder, text: $textFieldValue)
.introspectTextField { textField in
self.spy1(textField)
}
.cornerRadius(8)
TextField(textField2Placeholder, text: $textFieldValue)
.introspectTextField { textField in
self.spy2(textField)
}
.cornerRadius(8)
TextField(textField3Placeholder, text: $textFieldValue)
.introspectTextField { textField in
self.spy3(textField)
}
}
}
}
Expand Down Expand Up @@ -396,14 +415,40 @@ class UIKitTests: XCTestCase {
XCTAssertNotEqual(unwrappedScrollView1, unwrappedScrollView2)
}

func testTextField() {
func testTextField() throws {

let expectation = XCTestExpectation()
let view = TextFieldTestView(spy: {
expectation.fulfill()
})
let expectation1 = XCTestExpectation()
let expectation2 = XCTestExpectation()
let expectation3 = XCTestExpectation()

var textField1: UITextField?
var textField2: UITextField?
var textField3: UITextField?

let view = TextFieldTestView(
spy1: {
textField1 = $0
expectation1.fulfill()
},
spy2: {
textField2 = $0
expectation2.fulfill()
},
spy3: {
textField3 = $0
expectation3.fulfill()
}
)
TestUtils.present(view: view)
wait(for: [expectation], timeout: TestUtils.Constants.timeout)
wait(for: [expectation1, expectation2, expectation3], timeout: TestUtils.Constants.timeout)

let unwrappedTextField1 = try XCTUnwrap(textField1)
let unwrappedTextField2 = try XCTUnwrap(textField2)
let unwrappedTextField3 = try XCTUnwrap(textField3)

XCTAssertEqual(unwrappedTextField1.placeholder, view.textField1Placeholder)
XCTAssertEqual(unwrappedTextField2.placeholder, view.textField2Placeholder)
XCTAssertEqual(unwrappedTextField3.placeholder, view.textField3Placeholder)
}

func testSegmentedControl() {
Expand Down