Fix UITextField with cornerRadius (#83)

This commit is contained in:
JannThomas 2021-03-17 20:27:21 +01:00 committed by GitHub
parent fab6cddbaf
commit 07a80dd280
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 11 deletions

View File

@ -3,6 +3,7 @@ Changelog
## master
- Fix UITextField with cornerRadius
- Added `.introspectTabView()` on macOS
## [0.1.3]

View File

@ -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.
@ -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).
@ -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

View File

@ -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`

View File

@ -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)
}
}
}
}
@ -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() {