Skip to content
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

Add rx.attributedText to UITextView and UITextField #1249

Merged
merged 5 commits into from
Oct 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions RxCocoa/iOS/UITextField+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ extension Reactive where Base: UITextField {
)
}

/// Bindable sink for `attributedText` property.
public var attributedText: ControlProperty<NSAttributedString?> {
return UIControl.rx.value(
base,
getter: { textField in
textField.attributedText
}, setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.
if textField.attributedText != value {
textField.attributedText = value
}
}
)
}

}

#endif
36 changes: 36 additions & 0 deletions RxCocoa/iOS/UITextView+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ extension Reactive where Base: UITextView {

return ControlProperty(values: source, valueSink: bindingObserver)
}


/// Reactive wrapper for `attributedText` property.
public var attributedText: ControlProperty<NSAttributedString?> {
let source: Observable<NSAttributedString?> = Observable.deferred { [weak textView = self.base] in
let attributedText = textView?.attributedText

let textChanged: Observable<NSAttributedString?> = textView?.textStorage
// This project uses text storage notifications because
// that's the only way to catch autocorrect changes
// in all cases. Other suggestions are welcome.
.rx.didProcessEditingRangeChangeInLength
// This observe on is here because attributedText storage
// will emit event while process is not completely done,
// so rebinding a value will cause an exception to be thrown.
.observeOn(MainScheduler.asyncInstance)
.map { _ in
return textView?.attributedText
}
?? Observable.empty()

return textChanged
.startWith(attributedText)
}

let bindingObserver = Binder(self.base) { (textView, attributedText: NSAttributedString?) in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.
if textView.attributedText != attributedText {
textView.attributedText = attributedText
}
}

return ControlProperty(values: source, valueSink: bindingObserver)
}

/// Reactive wrapper for `delegate` message.
public var didBeginEditing: ControlEvent<()> {
Expand Down
39 changes: 26 additions & 13 deletions RxExample/RxExample-iOSUITests/FlowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,14 @@ extension FlowTests {
extension FlowTests {
func testControls() {
for test in [
_testDatePicker,
_testBarButtonItemTap,
_testButtonTap,
_testSegmentedControl,
_testUISwitch,
_testUITextField,
_testUITextView,
_testSlider
_testDatePicker,
_testBarButtonItemTap,
_testButtonTap,
_testSegmentedControl,
_testUISwitch,
_testUITextField,
_testUITextView,
_testSlider
] {
goToControlsView()
test()
Expand All @@ -148,9 +148,14 @@ extension FlowTests {
tableView.cells.allElementsBoundByIndex[5].tap()
}

func checkDebugLabelValue(_ expected: String) {
func checkDebugLabelValue(_ expected: String, hasPrefix: Bool = false) {
let textValue = app.staticTexts["debugLabel"].value as? String
XCTAssertEqual(textValue, expected)
if hasPrefix {
XCTAssertTrue((textValue ?? "").hasPrefix(expected))
}
else {
XCTAssertEqual(textValue, expected)
}
}

func _testDatePicker() {
Expand Down Expand Up @@ -184,9 +189,9 @@ extension FlowTests {

func _testUISwitch() {
let switchControl = app.switches.allElementsBoundByIndex[0]
switchControl.tap()
switchControl.twoFingerTap()
checkDebugLabelValue("UISwitch value false")
switchControl.tap()
switchControl.twoFingerTap()
checkDebugLabelValue("UISwitch value true")
}

Expand All @@ -195,19 +200,27 @@ extension FlowTests {
textField.tap()
textField.typeText("f")
checkDebugLabelValue("UITextField text f")
let textField2 = app.textFields.allElementsBoundByIndex[1]
textField2.tap()
textField2.typeText("f2")
checkDebugLabelValue("UITextField attributedText f2{", hasPrefix: true)
}

func _testUITextView() {
let textView = app.textViews.allElementsBoundByIndex[0]
textView.tap()
textView.typeText("f")
checkDebugLabelValue("UITextView text f")
let textView2 = app.textViews.allElementsBoundByIndex[1]
textView2.tap()
textView2.typeText("f2")
checkDebugLabelValue("UITextView attributedText f2{", hasPrefix: true)
}

func _testSlider() {
let slider = app.sliders.allElementsBoundByIndex[0]
slider.adjust(toNormalizedSliderPosition: 0)
checkDebugLabelValue("UISlider value 0.0")
checkDebugLabelValue("UISlider value 0.0", hasPrefix: true)
}
}

Expand Down
Loading