Skip to content

Commit

Permalink
Highlight the text in ValueField when shouldResetOnInsertion is enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrii Chernenko committed Sep 11, 2020
1 parent 92a5a34 commit 980fe48
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Form.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
F6DD5CB320A0408000975242 /* ButtonStateStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6DD5CB220A0408000975242 /* ButtonStateStyle.swift */; };
F6FBC82D20AF197B004BC82A /* SectionBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6FBC82C20AF197B004BC82A /* SectionBackgroundStyle.swift */; };
F704C9662436411200208560 /* ValueEditorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F704C9652436411200208560 /* ValueEditorTests.swift */; };
F7315ED6250A316400F393F4 /* ValueFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7315ED5250A316400F393F4 /* ValueFieldTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -229,6 +230,7 @@
F6DD5CB220A0408000975242 /* ButtonStateStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ButtonStateStyle.swift; path = Form/ButtonStateStyle.swift; sourceTree = "<group>"; };
F6FBC82C20AF197B004BC82A /* SectionBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SectionBackgroundStyle.swift; path = Form/SectionBackgroundStyle.swift; sourceTree = "<group>"; };
F704C9652436411200208560 /* ValueEditorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueEditorTests.swift; sourceTree = "<group>"; };
F7315ED5250A316400F393F4 /* ValueFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueFieldTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -285,6 +287,7 @@
F604260920B6A47E00BC4CAB /* ParentChildRelationalTests.swift */,
F6B81B9320CA906000B6AC39 /* NumberEditorTests.swift */,
F704C9652436411200208560 /* ValueEditorTests.swift */,
F7315ED5250A316400F393F4 /* ValueFieldTests.swift */,
724EC30C2241513D001F3E11 /* UILabel+StylingTests.swift */,
723C684A23A3CEA1008FD4B6 /* UILabel+ScalingTests.swift */,
72C5ED6E226F432600E32125 /* UIScrollView+PinningTests.swift */,
Expand Down Expand Up @@ -604,6 +607,7 @@
72C5ED6F226F432600E32125 /* UIScrollView+PinningTests.swift in Sources */,
21367C6B1DACDF990021C98F /* TableChangeTests.swift in Sources */,
1C2881831F20EE2000666A21 /* SelectViewTests.swift in Sources */,
F7315ED6250A316400F393F4 /* ValueFieldTests.swift in Sources */,
1CDD56AA1D9C10D7004B0CA9 /* TableTests.swift in Sources */,
724EC30D2241513D001F3E11 /* UILabel+StylingTests.swift in Sources */,
);
Expand Down
4 changes: 3 additions & 1 deletion Form/FieldStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public struct FieldStyle: Style {
public var placeholder: TextStyle
public var disabled: TextStyle
public var cursorColor: UIColor
public var textHighlightColor: UIColor?
public var autocorrection: UITextAutocorrectionType = .default
public var autocapitalization: UITextAutocapitalizationType = .sentences
public var clearButton: UITextField.ViewMode = .never
Expand All @@ -22,11 +23,12 @@ public struct FieldStyle: Style {
}

public extension FieldStyle {
init(text: TextStyle, placeholder: TextStyle, disabled: TextStyle, cursorColor: UIColor) {
init(text: TextStyle, placeholder: TextStyle, disabled: TextStyle, cursorColor: UIColor, textHighlightColor: UIColor? = nil) {
self.text = text
self.placeholder = placeholder
self.disabled = disabled
self.cursorColor = cursorColor
self.textHighlightColor = textHighlightColor
}
}

Expand Down
1 change: 1 addition & 0 deletions Form/UITextField+Styling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ extension FieldStyle {
placeholder = text.colored(UIColor(white: 0.8, alpha: 1))
disabled = text.colored(UIColor(white: 0.4, alpha: 1))
cursorColor = UIColor(red: 0, green: 0.42, blue: 0.9, alpha: 1)
textHighlightColor = nil
autocorrection = textField.autocorrectionType
autocapitalization = textField.autocapitalizationType
clearButton = textField.clearButtonMode
Expand Down
17 changes: 16 additions & 1 deletion Form/ValueField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ public final class ValueField<Value>: UIControl, UIKeyInput {

public var shouldResetOnInsertion: Bool {
get { return editor.shouldResetOnInsertion }
set { editor.shouldResetOnInsertion = newValue }
set {
editor.shouldResetOnInsertion = newValue
updateTextHighlight()
}
}

internal var shouldHighlightText: Bool {
return shouldResetOnInsertion && isFirstResponder
}

public init<Editor: TextEditor>(value: Value, placeholder: DisplayableString = "", editor: Editor, style: FieldStyle = .default) where Editor.Value == Value {
Expand All @@ -48,6 +55,7 @@ public final class ValueField<Value>: UIControl, UIKeyInput {

label = UILabel(value: editor.text, style: style.text)
label.baselineAdjustment = .none
label.isOpaque = false

placeholderLabel = UILabel(value: placeholder.displayValue, style: style.placeholder)
placeholderLabel.isHidden = true
Expand Down Expand Up @@ -183,6 +191,8 @@ public final class ValueField<Value>: UIControl, UIKeyInput {
guard super.becomeFirstResponder() else { return false }

cursor.isHidden = false
updateTextHighlight()

installAnimation()
sendActions(for: .editingDidBegin)
NotificationCenter.default.post(name: UITextField.textDidBeginEditingNotification, object: self)
Expand Down Expand Up @@ -320,11 +330,16 @@ private extension ValueField {
let trailingRect = trailingText.boundingRect(with: CGSize(width: 0, height: 0), options: [], attributes: style.text.attributes, context: context)
cursorConstraint.constant = trailingRect.width - 1
invalidateIntrinsicContentSize()
updateTextHighlight()

sendActions(for: .editingChanged)
NotificationCenter.default.post(name: UITextField.textDidChangeNotification, object: self)
}

func updateTextHighlight() {
label.backgroundColor = shouldHighlightText ? style.textHighlightColor : nil
}

func updateAlignmentConstraints() {
leftTextAlignmentConstraints.forEach { $0.isActive = style.text.alignmentResolvingNatural != .right }
rightTextAlignmentConstraints.forEach { $0.isActive = style.text.alignmentResolvingNatural != .left }
Expand Down
160 changes: 160 additions & 0 deletions FormTests/ValueFieldTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// Copyright © 2020 iZettle. All rights reserved.
//

import XCTest
import Flow

@testable import Form

class ValueFieldTests: XCTestCase {

lazy var window: UIWindow = {
let window = UIWindow()
window.frame.size = .init(width: 100, height: 100)
window.makeKeyAndVisible()

return window
}()

func createField() -> (ValueField<String>, Disposable) {
let field = ValueField(value: "", editor: ValueEditor())

// add the field to a window so that it can become first responder
window.embedView(field)

return (field, Disposer { field.removeFromSuperview() })
}

func createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: @escaping (ValueField<String>) -> Void,
afterMakingFieldFirstResponder: @escaping (ValueField<String>) -> Void,
file: StaticString = #file,
line: UInt = #line
) -> XCTestExpectation {
let (field, bag) = createField()

beforeMakingFieldFirstResponder(field)

_ = field.becomeFirstResponder()
let becameFirstResponder = expectation(description: "Responder status changed")

// the change of first responder may not happen immediately
DispatchQueue.main.async {
XCTAssert(field.isFirstResponder, file: file, line: line)
afterMakingFieldFirstResponder(field)

bag.dispose()
becameFirstResponder.fulfill()
}

return becameFirstResponder
}

func testTextHighlight_whenNotAFirstResponder_isDisabled() {
let (field, _) = createField()

XCTAssertFalse(field.isFirstResponder)
XCTAssertFalse(field.shouldHighlightText)

field.shouldResetOnInsertion = true
XCTAssertFalse(field.shouldHighlightText)
}

func testTextHighlight_whenResetOnInsertionIsDisabled_isDisabled() {
let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
field.shouldResetOnInsertion = false
XCTAssertFalse(field.shouldHighlightText)
},
afterMakingFieldFirstResponder: { field in
XCTAssertFalse(field.shouldHighlightText)
}
)

wait(for: [fieldBecameFirstResponder], timeout: 0.01)
}

func testTextHighlight_whenIsAFirstResponderAndResetOnInsertionIsEnabled_isEnabled() {
let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
field.shouldResetOnInsertion = true
},
afterMakingFieldFirstResponder: { field in
XCTAssert(field.shouldHighlightText)
}
)

wait(for: [fieldBecameFirstResponder], timeout: 0.01)
}

func testTextHighlight_afterTextIsInserted_isDisabled() {
let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
field.shouldResetOnInsertion = true
},
afterMakingFieldFirstResponder: { field in
field.insertText("foo")
XCTAssertFalse(field.shouldHighlightText)
}
)

wait(for: [fieldBecameFirstResponder], timeout: 0.01)
}

func testTextHighlight_afterFieldBecomesAndResignsFirstResponder_isDisabled() {
let fieldResignedFirstResponder = expectation(description: "Field resigned first responder")

let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
field.shouldResetOnInsertion = true
},
afterMakingFieldFirstResponder: { field in
XCTAssert(field.shouldHighlightText)
_ = field.resignFirstResponder()

DispatchQueue.main.async {
XCTAssertFalse(field.isFirstResponder)
XCTAssertFalse(field.shouldHighlightText)
fieldResignedFirstResponder.fulfill()
}
}
)

wait(for: [fieldBecameFirstResponder, fieldResignedFirstResponder], timeout: 0.01)
}

func testTextHighlight_afterTextIsPasted_isDisabled() {
let currentPasteboardText = UIPasteboard.general.string
let setPasteboardText = { UIPasteboard.general.string = $0 }

let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
setPasteboardText("foo")
field.shouldResetOnInsertion = true
},
afterMakingFieldFirstResponder: { field in
field.paste(nil)
XCTAssertFalse(field.shouldHighlightText)
setPasteboardText(currentPasteboardText)
}
)

wait(for: [fieldBecameFirstResponder], timeout: 0.01)
}

func testTextHighlight_afterTextIsDeleted_isDisabled() {
let fieldBecameFirstResponder = createFieldAndMakeItFirstResponder(
beforeMakingFieldFirstResponder: { field in
field.insertText("foo")
field.shouldResetOnInsertion = true
},
afterMakingFieldFirstResponder: { field in
field.deleteBackward()
XCTAssertFalse(field.shouldHighlightText)
}
)

wait(for: [fieldBecameFirstResponder], timeout: 0.01)
}
}

0 comments on commit 980fe48

Please sign in to comment.