Skip to content

Commit

Permalink
Fixes problems with two way binding that was caused by rx_text and …
Browse files Browse the repository at this point in the history
…always setting the text value even when the value was equal, and thus clearing the marked text state. #649
  • Loading branch information
kzaher committed May 15, 2016
1 parent aca9950 commit 21904f1
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 6 deletions.
10 changes: 10 additions & 0 deletions Rx.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,10 @@
C88E296C1BEB712E001CCB92 /* RunLoopLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88E296A1BEB712E001CCB92 /* RunLoopLock.swift */; };
C88E296D1BEB712E001CCB92 /* RunLoopLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88E296A1BEB712E001CCB92 /* RunLoopLock.swift */; };
C88E296E1BEB712E001CCB92 /* RunLoopLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88E296A1BEB712E001CCB92 /* RunLoopLock.swift */; };
C88F76811CE5341700D5A014 /* RxTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F76801CE5341700D5A014 /* RxTextInput.swift */; };
C88F76821CE5341700D5A014 /* RxTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F76801CE5341700D5A014 /* RxTextInput.swift */; };
C88F76831CE5341700D5A014 /* RxTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F76801CE5341700D5A014 /* RxTextInput.swift */; };
C88F76841CE5341700D5A014 /* RxTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F76801CE5341700D5A014 /* RxTextInput.swift */; };
C8941BDF1BD5695C00A0E874 /* BlockingObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8941BDE1BD5695C00A0E874 /* BlockingObservable.swift */; };
C8941BE01BD5695C00A0E874 /* BlockingObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8941BDE1BD5695C00A0E874 /* BlockingObservable.swift */; };
C8941BE11BD5695C00A0E874 /* BlockingObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8941BDE1BD5695C00A0E874 /* BlockingObservable.swift */; };
Expand Down Expand Up @@ -1629,6 +1633,7 @@
C88254141B8A752B00B02D69 /* UITextView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+Rx.swift"; sourceTree = "<group>"; };
C88BB8711B07E5ED0064D411 /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C88E296A1BEB712E001CCB92 /* RunLoopLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunLoopLock.swift; sourceTree = "<group>"; };
C88F76801CE5341700D5A014 /* RxTextInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTextInput.swift; sourceTree = "<group>"; };
C88FA50C1C25C44800CCFEA4 /* RxTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C88FA51D1C25C4B500CCFEA4 /* RxTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C88FA52E1C25C4C000CCFEA4 /* RxTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -2099,6 +2104,7 @@
C8BCD3F31C14B6D1005F1280 /* NSLayoutConstraint+Rx.swift */,
C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */,
D2F461001CD7ABE400527B4D /* Reactive.swift */,
C88F76801CE5341700D5A014 /* RxTextInput.swift */,
);
path = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -3297,6 +3303,7 @@
C8093EEF1B8A732E0088E94D /* KVOObserver.swift in Sources */,
C882541F1B8A752B00B02D69 /* RxCollectionViewDelegateProxy.swift in Sources */,
C88254201B8A752B00B02D69 /* RxScrollViewDelegateProxy.swift in Sources */,
C88F76811CE5341700D5A014 /* RxTextInput.swift in Sources */,
C882542E1B8A752B00B02D69 /* UILabel+Rx.swift in Sources */,
54D2138E1CE0824E0028D5B4 /* UINavigationItem+Rx.swift in Sources */,
C88254211B8A752B00B02D69 /* RxSearchBarDelegateProxy.swift in Sources */,
Expand Down Expand Up @@ -3359,6 +3366,7 @@
C8093EE41B8A732E0088E94D /* DelegateProxyType.swift in Sources */,
C8093F481B8A732E0088E94D /* NSControl+Rx.swift in Sources */,
C8093F4E1B8A732E0088E94D /* NSTextField+Rx.swift in Sources */,
C88F76821CE5341700D5A014 /* RxTextInput.swift in Sources */,
C8DB967F1BF7496C0084BD53 /* KVORepresentable.swift in Sources */,
C8093EFE1B8A732E0088E94D /* RxTarget.swift in Sources */,
C8093ED21B8A732E0088E94D /* _RX.m in Sources */,
Expand Down Expand Up @@ -4246,6 +4254,7 @@
C8F0C0381BBBFBB9001B112F /* UITextField+Rx.swift in Sources */,
C8F0C0391BBBFBB9001B112F /* NSURLSession+Rx.swift in Sources */,
C8F0C03A1BBBFBB9001B112F /* ControlTarget.swift in Sources */,
C88F76841CE5341700D5A014 /* RxTextInput.swift in Sources */,
C8F0C03B1BBBFBB9001B112F /* UISearchBar+Rx.swift in Sources */,
C8F0C03C1BBBFBB9001B112F /* ItemEvents.swift in Sources */,
7EDBAEBF1C89B9B7006CBE67 /* UITabBarItem+Rx.swift in Sources */,
Expand Down Expand Up @@ -4307,6 +4316,7 @@
C80DDEA91BCE69BA006A1832 /* ObservableConvertibleType+Driver.swift in Sources */,
C80DDEA11BCE69BA006A1832 /* Driver+Subscription.swift in Sources */,
D2138C891BB9BEBE00339B5C /* DelegateProxyType.swift in Sources */,
C88F76831CE5341700D5A014 /* RxTextInput.swift in Sources */,
C811C89F1C24D80100A2DDD4 /* DeallocObservable.swift in Sources */,
54D213921CE08D0C0028D5B4 /* UINavigationItem+Rx.swift in Sources */,
D2F461041CD7AC2100527B4D /* Reactive.swift in Sources */,
Expand Down
39 changes: 39 additions & 0 deletions RxCocoa/Common/RxTextInput.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// RxTextInput.swift
// Rx
//
// Created by Krunoslav Zaher on 5/12/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//

import Foundation

#if os(iOS) || os(tvOS)
import UIKit

/**
Represents text input with reactive extensions.
*/
public protocol RxTextInput : UITextInput {

/**
Reactive wrapper for `text` property.
*/
var rx_text: ControlProperty<String> { get }
}
#endif

#if os(OSX)
import Cocoa

/**
Represents text input with reactive extensions.
*/
public protocol RxTextInput : NSTextInput {

/**
Reactive wrapper for `text` property.
*/
var rx_text: ControlProperty<String> { get }
}
#endif
6 changes: 4 additions & 2 deletions RxCocoa/iOS/UITextField+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import RxSwift
#endif
import UIKit

extension UITextField {
extension UITextField : RxTextInput {

/**
Reactive wrapper for `text` property.
Expand All @@ -25,7 +25,9 @@ extension UITextField {
getter: { textField in
textField.text ?? ""
}, setter: { textField, value in
textField.text = value
if textField.text != value {
textField.text = value
}
}
)
}
Expand Down
6 changes: 4 additions & 2 deletions RxCocoa/iOS/UITextView+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import RxSwift



extension UITextView {
extension UITextView : RxTextInput {

/**
Factory method that enables subclasses to implement their own `rx_delegate`.
Expand Down Expand Up @@ -53,7 +53,9 @@ extension UITextView {
}

let bindingObserver = UIBindingObserver(UIElement: self) { (textView, text: String) in
textView.text = text
if textView.text != text {
textView.text = text
}
}

return ControlProperty(values: source, valueSink: bindingObserver)
Expand Down
4 changes: 4 additions & 0 deletions RxExample/RxExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
C88BB8C71B07E6C90064D411 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E3C2321B03605B0010338D /* Dependencies.swift */; };
C88BB8CA1B07E6C90064D411 /* WikipediaAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86E2F3B1AE5A0CA00C31024 /* WikipediaAPI.swift */; };
C88BB8CC1B07E6C90064D411 /* WikipediaPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86E2F3C1AE5A0CA00C31024 /* WikipediaPage.swift */; };
C88F76861CE53B1300D5A014 /* RxTextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F76851CE53B1300D5A014 /* RxTextInput.swift */; };
C890A65D1AEC084100AFF7E6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C890A65C1AEC084100AFF7E6 /* ViewController.swift */; };
C891A2C91C07160C00DDD09D /* Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C891A2C81C07160C00DDD09D /* Timeout.swift */; };
C894649E1BC6C2B00055219D /* Cancelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89464281BC6C2B00055219D /* Cancelable.swift */; };
Expand Down Expand Up @@ -698,6 +699,7 @@
C86E2F3C1AE5A0CA00C31024 /* WikipediaPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaPage.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C86E2F3D1AE5A0CA00C31024 /* WikipediaSearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaSearchResult.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C88BB8DC1B07E6C90064D411 /* RxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
C88F76851CE53B1300D5A014 /* RxTextInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTextInput.swift; sourceTree = "<group>"; };
C890A65C1AEC084100AFF7E6 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C891A2C81C07160C00DDD09D /* Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeout.swift; sourceTree = "<group>"; };
C89464281BC6C2B00055219D /* Cancelable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancelable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1628,6 +1630,7 @@
C8CC3E6B1C95CB5300ABA17E /* Common */ = {
isa = PBXGroup;
children = (
C88F76851CE53B1300D5A014 /* RxTextInput.swift */,
D2F461051CD7AC4D00527B4D /* Reactive.swift */,
C8CC3E6C1C95CB5300ABA17E /* _RX.h */,
C8CC3E6D1C95CB5300ABA17E /* _RX.m */,
Expand Down Expand Up @@ -2327,6 +2330,7 @@
C89464F71BC6C2B00055219D /* ObserverBase.swift in Sources */,
C8CC3F441C95D16C00ABA17E /* AnimatableSectionModelType.swift in Sources */,
C8CC3F111C95CB5300ABA17E /* UIImageView+Rx.swift in Sources */,
C88F76861CE53B1300D5A014 /* RxTextInput.swift in Sources */,
C89464E31BC6C2B00055219D /* TakeUntil.swift in Sources */,
C8CC3F421C95D16C00ABA17E /* AnimatableSectionModel.swift in Sources */,
C89464FB1BC6C2B00055219D /* Rx.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class APIWrappersViewController: ViewController {

// also test two way binding
let textValue = Variable("")
textField.rx_text <-> textValue
textField <-> textValue

textValue.asObservable()
.subscribeNext { [weak self] x in
Expand All @@ -162,7 +162,7 @@ class APIWrappersViewController: ViewController {

// also test two way binding
let textViewValue = Variable("")
textView.rx_text <-> textViewValue
textView <-> textViewValue

textViewValue.asObservable()
.subscribeNext { [weak self] x in
Expand Down
54 changes: 54 additions & 0 deletions RxExample/RxExample/Operators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,66 @@ import RxSwift
import RxCocoa
#endif

import UIKit

// Two way binding operator between control property and variable, that's all it takes {

infix operator <-> {
}

func nonMarkedText(textInput: UITextInput) -> String? {
let start = textInput.beginningOfDocument
let end = textInput.endOfDocument

guard let rangeAll = textInput.textRangeFromPosition(start, toPosition: end),
text = textInput.textInRange(rangeAll) else {
return nil
}

guard let markedTextRange = textInput.markedTextRange else {
return text
}

guard let startRange = textInput.textRangeFromPosition(start, toPosition: markedTextRange.start),
endRange = textInput.textRangeFromPosition(markedTextRange.end, toPosition: end) else {
return text
}

return (textInput.textInRange(startRange) ?? "") + (textInput.textInRange(endRange) ?? "")
}

func <-> (textInput: RxTextInput, variable: Variable<String>) -> Disposable {
let bindToUIDisposable = variable.asObservable()
.bindTo(textInput.rx_text)
let bindToVariable = textInput.rx_text
.subscribe(onNext: { [weak textInput] n in
guard let textInput = textInput else {
return
}

let nonMarkedTextValue = nonMarkedText(textInput)

if nonMarkedTextValue != variable.value {
variable.value = nonMarkedTextValue ?? ""
}
}, onCompleted: {
bindToUIDisposable.dispose()
})

return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}

func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
if T.self == String.self {
#if DEBUG
fatalError("It is ok to delete this message, but this is here to warn that you are maybe trying to bind to some `rx_text` property directly to variable.\n" +
"That will usually work ok, but for some languages that use IME, that simplistic method could cause unexpected issues because it will return intermediate results while text is being inputed.\n" +
"REMEDY: Just use `textField <-> variable` instead of `textField.rx_text <-> variable`.\n" +
"Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n"
)
#endif
}

let bindToUIDisposable = variable.asObservable()
.bindTo(property)
let bindToVariable = property
Expand Down

0 comments on commit 21904f1

Please sign in to comment.