From 1aa1b570b06a69190f050f2de63b341e043018e4 Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Fri, 13 May 2016 00:38:26 +0200 Subject: [PATCH 1/3] Fixes problems with two way binding that was caused by `rx_text` and always setting the text value even when the value was equal, and thus clearing the marked text state. #649 --- Rx.xcodeproj/project.pbxproj | 10 ++++ RxCocoa/Common/RxTextInput.swift | 39 ++++++++++++++ RxCocoa/iOS/UITextField+Rx.swift | 6 ++- RxCocoa/iOS/UITextView+Rx.swift | 6 ++- RxExample/RxExample.xcodeproj/project.pbxproj | 4 ++ .../APIWrappersViewController.swift | 4 +- RxExample/RxExample/Operators.swift | 54 +++++++++++++++++++ 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 RxCocoa/Common/RxTextInput.swift diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index 5c7d50a0..4c86212d 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -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 */; }; @@ -1629,6 +1633,7 @@ C88254141B8A752B00B02D69 /* UITextView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+Rx.swift"; sourceTree = ""; }; 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 = ""; }; + C88F76801CE5341700D5A014 /* RxTextInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTextInput.swift; sourceTree = ""; }; 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; }; @@ -2099,6 +2104,7 @@ C8BCD3F31C14B6D1005F1280 /* NSLayoutConstraint+Rx.swift */, C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */, D2F461001CD7ABE400527B4D /* Reactive.swift */, + C88F76801CE5341700D5A014 /* RxTextInput.swift */, ); path = Common; sourceTree = ""; @@ -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 */, @@ -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 */, @@ -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 */, @@ -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 */, diff --git a/RxCocoa/Common/RxTextInput.swift b/RxCocoa/Common/RxTextInput.swift new file mode 100644 index 00000000..ba56d379 --- /dev/null +++ b/RxCocoa/Common/RxTextInput.swift @@ -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 { 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 { get } + } +#endif \ No newline at end of file diff --git a/RxCocoa/iOS/UITextField+Rx.swift b/RxCocoa/iOS/UITextField+Rx.swift index 35f888fc..d1e2bfc0 100644 --- a/RxCocoa/iOS/UITextField+Rx.swift +++ b/RxCocoa/iOS/UITextField+Rx.swift @@ -14,7 +14,7 @@ import RxSwift #endif import UIKit -extension UITextField { +extension UITextField : RxTextInput { /** Reactive wrapper for `text` property. @@ -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 + } } ) } diff --git a/RxCocoa/iOS/UITextView+Rx.swift b/RxCocoa/iOS/UITextView+Rx.swift index faebc494..2d11afc1 100644 --- a/RxCocoa/iOS/UITextView+Rx.swift +++ b/RxCocoa/iOS/UITextView+Rx.swift @@ -16,7 +16,7 @@ import RxSwift -extension UITextView { +extension UITextView : RxTextInput { /** Factory method that enables subclasses to implement their own `rx_delegate`. @@ -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) diff --git a/RxExample/RxExample.xcodeproj/project.pbxproj b/RxExample/RxExample.xcodeproj/project.pbxproj index a119849c..f9747599 100644 --- a/RxExample/RxExample.xcodeproj/project.pbxproj +++ b/RxExample/RxExample.xcodeproj/project.pbxproj @@ -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 */; }; @@ -698,6 +699,7 @@ C86E2F3C1AE5A0CA00C31024 /* WikipediaPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaPage.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C86E2F3D1AE5A0CA00C31024 /* WikipediaSearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaSearchResult.swift; sourceTree = ""; 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 = ""; }; C890A65C1AEC084100AFF7E6 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; C891A2C81C07160C00DDD09D /* Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeout.swift; sourceTree = ""; }; C89464281BC6C2B00055219D /* Cancelable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancelable.swift; sourceTree = ""; }; @@ -1628,6 +1630,7 @@ C8CC3E6B1C95CB5300ABA17E /* Common */ = { isa = PBXGroup; children = ( + C88F76851CE53B1300D5A014 /* RxTextInput.swift */, D2F461051CD7AC4D00527B4D /* Reactive.swift */, C8CC3E6C1C95CB5300ABA17E /* _RX.h */, C8CC3E6D1C95CB5300ABA17E /* _RX.m */, @@ -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 */, diff --git a/RxExample/RxExample/Examples/APIWrappers/APIWrappersViewController.swift b/RxExample/RxExample/Examples/APIWrappers/APIWrappersViewController.swift index f9e9c3fc..a80faa01 100644 --- a/RxExample/RxExample/Examples/APIWrappers/APIWrappersViewController.swift +++ b/RxExample/RxExample/Examples/APIWrappers/APIWrappersViewController.swift @@ -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 @@ -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 diff --git a/RxExample/RxExample/Operators.swift b/RxExample/RxExample/Operators.swift index a444eca8..df484c65 100644 --- a/RxExample/RxExample/Operators.swift +++ b/RxExample/RxExample/Operators.swift @@ -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) -> 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 <-> (property: ControlProperty, variable: Variable) -> 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 From bee2ce5e4bb079eda21d8020af182f03e5792596 Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Fri, 13 May 2016 00:47:44 +0200 Subject: [PATCH 2/3] Provides explanations for check. --- RxCocoa/iOS/UITextField+Rx.swift | 3 +++ RxCocoa/iOS/UITextView+Rx.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/RxCocoa/iOS/UITextField+Rx.swift b/RxCocoa/iOS/UITextField+Rx.swift index d1e2bfc0..c45957dc 100644 --- a/RxCocoa/iOS/UITextField+Rx.swift +++ b/RxCocoa/iOS/UITextField+Rx.swift @@ -25,6 +25,9 @@ extension UITextField : RxTextInput { getter: { textField in textField.text ?? "" }, 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.text != value { textField.text = value } diff --git a/RxCocoa/iOS/UITextView+Rx.swift b/RxCocoa/iOS/UITextView+Rx.swift index 2d11afc1..e9828837 100644 --- a/RxCocoa/iOS/UITextView+Rx.swift +++ b/RxCocoa/iOS/UITextView+Rx.swift @@ -53,6 +53,9 @@ extension UITextView : RxTextInput { } let bindingObserver = UIBindingObserver(UIElement: self) { (textView, text: String) 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.text != text { textView.text = text } From 3fa44935a103bee61e22ed48a358940971f8438a Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Sat, 14 May 2016 01:20:54 +0200 Subject: [PATCH 3/3] Adds unit test for only setting different text values. --- Rx.xcodeproj/project.pbxproj | 12 +++++ .../RxCocoaTests/Control+RxTests+UIKit.swift | 15 ------- Tests/RxCocoaTests/UITextField+RxTests.swift | 44 ++++++++++++++++++ Tests/RxCocoaTests/UITextView+RxTests.swift | 45 +++++++++++++++++++ 4 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 Tests/RxCocoaTests/UITextField+RxTests.swift create mode 100644 Tests/RxCocoaTests/UITextView+RxTests.swift diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index 4c86212d..d04d3f70 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -925,6 +925,10 @@ C8F0C0431BBBFBB9001B112F /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E821B8A732E0088E94D /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8F0C0451BBBFBB9001B112F /* _RXKVOObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E861B8A732E0088E94D /* _RXKVOObserver.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8F0C04F1BBBFBCE001B112F /* ObservableConvertibleType+Blocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093F581B8A73A20088E94D /* ObservableConvertibleType+Blocking.swift */; }; + C8F27DC01CE68DA600D5FB4F /* UITextView+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F27DB11CE6711600D5FB4F /* UITextView+RxTests.swift */; }; + C8F27DC11CE68DA700D5FB4F /* UITextView+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F27DB11CE6711600D5FB4F /* UITextView+RxTests.swift */; }; + C8F27DC21CE68DAB00D5FB4F /* UITextField+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F27DAC1CE6710900D5FB4F /* UITextField+RxTests.swift */; }; + C8F27DC31CE68DAC00D5FB4F /* UITextField+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F27DAC1CE6710900D5FB4F /* UITextField+RxTests.swift */; }; C8F6A1451BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F6A1441BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift */; }; C8F6A1461BF0B9B2007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F6A1441BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift */; }; C8F6A1471BF0B9B2007DF367 /* NSObject+Rx+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F6A1441BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift */; }; @@ -1685,6 +1689,8 @@ C8F0C0021BBBFB8B001B112F /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C8F0C04B1BBBFBB9001B112F /* RxCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C8F0C0581BBBFBCE001B112F /* RxBlocking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxBlocking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C8F27DAC1CE6710900D5FB4F /* UITextField+RxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+RxTests.swift"; sourceTree = ""; }; + C8F27DB11CE6711600D5FB4F /* UITextView+RxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+RxTests.swift"; sourceTree = ""; }; C8F6A1441BF0B9B1007DF367 /* NSObject+Rx+RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Rx+RawRepresentable.swift"; sourceTree = ""; }; C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeConverterType.swift; sourceTree = ""; }; C8FA89131C30405400CD3A17 /* VirtualTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeScheduler.swift; sourceTree = ""; }; @@ -2255,6 +2261,8 @@ C8C217D41CB7100E0038A2E6 /* UITableView+RxTests.swift */, C8C217D61CB710200038A2E6 /* UICollectionView+RxTests.swift */, 54700C9E1CE37D1000EF3A8F /* UINavigationItem+RxTests.swift.swift */, + C8F27DAC1CE6710900D5FB4F /* UITextField+RxTests.swift */, + C8F27DB11CE6711600D5FB4F /* UITextView+RxTests.swift */, ); path = RxCocoaTests; sourceTree = ""; @@ -3461,6 +3469,7 @@ C83509641C38706E0027C24C /* VariableTest.swift in Sources */, C83509461C38706E0027C24C /* PrimitiveHotObservable.swift in Sources */, C835097E1C38726E0027C24C /* RxMutableBox.swift in Sources */, + C8F27DC21CE68DAB00D5FB4F /* UITextField+RxTests.swift in Sources */, C83509311C38706E0027C24C /* DelegateProxyTest+UIKit.swift in Sources */, C83509671C38706E0027C24C /* Foundation+Extensions.swift in Sources */, C83509481C38706E0027C24C /* TestConnectableObservable.swift in Sources */, @@ -3504,6 +3513,7 @@ C835092C1C38706E0027C24C /* Control+RxTests+UIKit.swift in Sources */, C83509571C38706E0027C24C /* Observable+CreationTest.swift in Sources */, C83509321C38706E0027C24C /* DelegateProxyTest.swift in Sources */, + C8F27DC01CE68DA600D5FB4F /* UITextView+RxTests.swift in Sources */, C860EC951C42E25E00A664B3 /* SectionedViewDataSourceMock.swift in Sources */, C83509431C38706E0027C24C /* MockDisposable.swift in Sources */, ); @@ -3552,6 +3562,7 @@ 8476A0221C3D5DC60040BA22 /* UIImagePickerController+RxTests.swift in Sources */, C8350A161C38756A0027C24C /* QueueTests.swift in Sources */, C83509C51C3875220027C24C /* NSNotificationCenterTests.swift in Sources */, + C8F27DC31CE68DAC00D5FB4F /* UITextField+RxTests.swift in Sources */, C83509FF1C38755D0027C24C /* Observable+MultipleTest+CombineLatest.swift in Sources */, C83509D81C3875420027C24C /* SentMessageTest.swift in Sources */, C83509F61C38755D0027C24C /* CurrentThreadSchedulerTest.swift in Sources */, @@ -3579,6 +3590,7 @@ C83509D01C38752E0027C24C /* RuntimeStateSnapshot.swift in Sources */, C83509AF1C3874DC0027C24C /* RxTest.swift in Sources */, C83509F41C38755D0027C24C /* BagTest.swift in Sources */, + C8F27DC11CE68DA700D5FB4F /* UITextView+RxTests.swift in Sources */, C860EC961C42E26100A664B3 /* SectionedViewDataSourceMock.swift in Sources */, C8350A0F1C3875630027C24C /* Observable+MultipleTest+Zip.swift in Sources */, ); diff --git a/Tests/RxCocoaTests/Control+RxTests+UIKit.swift b/Tests/RxCocoaTests/Control+RxTests+UIKit.swift index 2fa27c26..53f5be3b 100644 --- a/Tests/RxCocoaTests/Control+RxTests+UIKit.swift +++ b/Tests/RxCocoaTests/Control+RxTests+UIKit.swift @@ -45,13 +45,6 @@ extension ControlTests { } } -// UITextField -extension ControlTests { - func testTextField_TextCompletesOnDealloc() { - ensurePropertyDeallocated({ UITextField() }, "a") { (view: UITextField) in view.rx_text } - } -} - // Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior () // Don't know why can't use ActionSheet and AlertView inside unit tests @@ -130,14 +123,6 @@ extension ControlTests { } } -// UITextView -extension ControlTests { - func testText_DelegateEventCompletesOnDealloc() { - let createView: () -> UITextView = { UITextView(frame: CGRectMake(0, 0, 1, 1)) } - ensurePropertyDeallocated(createView, "text") { (view: UITextView) in view.rx_text } - } -} - // UIActivityIndicatorView extension ControlTests { func testActivityIndicator_HasWeakReference() { diff --git a/Tests/RxCocoaTests/UITextField+RxTests.swift b/Tests/RxCocoaTests/UITextField+RxTests.swift new file mode 100644 index 00000000..e96b0c2e --- /dev/null +++ b/Tests/RxCocoaTests/UITextField+RxTests.swift @@ -0,0 +1,44 @@ +// +// UITextField+RxTests.swift +// Rx +// +// Created by Krunoslav Zaher on 5/13/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import RxSwift +import RxCocoa +import XCTest + +// UITextField +class UITextFieldTests : RxTest { + func testTextCompletesOnDealloc() { + ensurePropertyDeallocated({ UITextField() }, "a") { (view: UITextField) in view.rx_text } + } + + func testSettingTextDoesntClearMarkedText() { + let textField = UITextFieldSubclass(frame: CGRect.zero) + + textField.text = "Text1" + textField.set = false + textField.rx_text.on(.Next("Text1")) + XCTAssertTrue(!textField.set) + textField.rx_text.on(.Next("Text2")) + XCTAssertTrue(textField.set) + } +} + +class UITextFieldSubclass : UITextField { + var set: Bool = false + + override var text: String? { + get { + return super.text + } + set { + set = true + super.text = newValue + } + } +} diff --git a/Tests/RxCocoaTests/UITextView+RxTests.swift b/Tests/RxCocoaTests/UITextView+RxTests.swift new file mode 100644 index 00000000..4fc8a0c6 --- /dev/null +++ b/Tests/RxCocoaTests/UITextView+RxTests.swift @@ -0,0 +1,45 @@ +// +// UITextView+RxTests.swift +// Rx +// +// Created by Krunoslav Zaher on 5/13/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import UIKit +import RxSwift +import RxCocoa +import XCTest + +// UITextView +class UITextViewTests : RxTest { + func testText_DelegateEventCompletesOnDealloc() { + let createView: () -> UITextView = { UITextView(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, "text") { (view: UITextView) in view.rx_text } + } + + func testSettingTextDoesntClearMarkedText() { + let textView = UITextViewSubclass2(frame: CGRect.zero) + + textView.text = "Text1" + textView.set = false + textView.rx_text.on(.Next("Text1")) + XCTAssertTrue(!textView.set) + textView.rx_text.on(.Next("Text2")) + XCTAssertTrue(textView.set) + } +} + +class UITextViewSubclass2 : UITextView { + var set: Bool = false + + override var text: String? { + get { + return super.text + } + set { + set = true + super.text = newValue + } + } +} \ No newline at end of file