Operators.swift 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. //
  2. // Operators.swift
  3. // RxExample
  4. //
  5. // Created by Krunoslav Zaher on 12/6/15.
  6. // Copyright © 2015 Krunoslav Zaher. All rights reserved.
  7. //
  8. import RxCocoa
  9. import RxSwift
  10. #if os(iOS)
  11. import UIKit
  12. #elseif os(macOS)
  13. import AppKit
  14. #endif
  15. // Two way binding operator between control property and relay, that's all it takes.
  16. infix operator <->: DefaultPrecedence
  17. #if os(iOS)
  18. func nonMarkedText(_ textInput: UITextInput) -> String? {
  19. let start = textInput.beginningOfDocument
  20. let end = textInput.endOfDocument
  21. guard let rangeAll = textInput.textRange(from: start, to: end),
  22. let text = textInput.text(in: rangeAll)
  23. else {
  24. return nil
  25. }
  26. guard let markedTextRange = textInput.markedTextRange else {
  27. return text
  28. }
  29. guard let startRange = textInput.textRange(from: start, to: markedTextRange.start),
  30. let endRange = textInput.textRange(from: markedTextRange.end, to: end)
  31. else {
  32. return text
  33. }
  34. return (textInput.text(in: startRange) ?? "") + (textInput.text(in: endRange) ?? "")
  35. }
  36. func <-> <Base>(textInput: TextInput<Base>, relay: BehaviorRelay<String>) -> Disposable {
  37. let bindToUIDisposable = relay.bind(to: textInput.text)
  38. let bindToRelay = textInput.text
  39. .subscribe(onNext: { [weak base = textInput.base] _ in
  40. guard let base = base else {
  41. return
  42. }
  43. let nonMarkedTextValue = nonMarkedText(base)
  44. /**
  45. In some cases `textInput.textRangeFromPosition(start, toPosition: end)` will return nil even though the underlying
  46. value is not nil. This appears to be an Apple bug. If it's not, and we are doing something wrong, please let us know.
  47. The can be reproed easily if replace bottom code with
  48. if nonMarkedTextValue != relay.value {
  49. relay.accept(nonMarkedTextValue ?? "")
  50. }
  51. and you hit "Done" button on keyboard.
  52. */
  53. if let nonMarkedTextValue = nonMarkedTextValue, nonMarkedTextValue != relay.value {
  54. relay.accept(nonMarkedTextValue)
  55. }
  56. }, onCompleted: {
  57. bindToUIDisposable.dispose()
  58. })
  59. return Disposables.create(bindToUIDisposable, bindToRelay)
  60. }
  61. #endif
  62. func <-> <T>(property: ControlProperty<T>, relay: BehaviorRelay<T>) -> Disposable {
  63. if T.self == String.self {
  64. #if DEBUG && !os(macOS)
  65. 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 relay.\n" +
  66. "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" +
  67. "REMEDY: Just use `textField <-> relay` instead of `textField.rx.text <-> relay`.\n" +
  68. "Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n"
  69. )
  70. #endif
  71. }
  72. let bindToUIDisposable = relay.bind(to: property)
  73. let bindToRelay = property
  74. .subscribe(onNext: { n in
  75. relay.accept(n)
  76. }, onCompleted: {
  77. bindToUIDisposable.dispose()
  78. })
  79. return Disposables.create(bindToUIDisposable, bindToRelay)
  80. }