Extends `UIView` with `rx_alpha`, `rx_hidden`. `NSLayoutConstraint` with `rx_constant`.

This commit is contained in:
Krunoslav Zaher 2015-12-06 20:33:03 +01:00
parent a0c68de05d
commit b08c1a5969
13 changed files with 330 additions and 96 deletions

View File

@ -0,0 +1,43 @@
//
// NSLayoutConstraint+Rx.swift
// Rx
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if os(OSX)
import Cocoa
#else
import UIKit
#endif
#if !RX_NO_MODULE
import RxSwift
#endif
#if os(iOS) || os(OSX) || os(tvOS)
extension NSLayoutConstraint {
/**
Bindable sink for `constant` property.
*/
public var rx_constant: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.constant = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
}
#endif

View File

@ -0,0 +1,53 @@
//
// NSView+Rx.swift
// Rx
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import Cocoa
#if !RX_NO_MODULE
import RxSwift
#endif
extension NSView {
/**
Bindable sink for `hidden` property.
*/
public var rx_hidden: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.hidden = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
/**
Bindable sink for `alphaValue` property.
*/
public var rx_alpha: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.alphaValue = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
}

View File

@ -34,7 +34,7 @@ extension UIControl {
}
}
}
/**
Reactive wrapper for target action pattern.

View File

@ -0,0 +1,57 @@
//
// UIView+Rx.swift
// Rx
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if os(iOS) || os(tvOS)
import Foundation
import UIKit
#if !RX_NO_MODULE
import RxSwift
#endif
extension UIView {
/**
Bindable sink for `hidden` property.
*/
public var rx_hidden: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.hidden = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
/**
Bindable sink for `alpha` property.
*/
public var rx_alpha: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.alpha = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
}
#endif

View File

@ -0,0 +1,72 @@
//
// ValidationService.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import RxSwift
class ValidationService {
let API: GitHubAPI
init (API: GitHubAPI) {
self.API = API
}
// validation
let minPasswordCount = 5
func validateUsername(username: String) -> ValidationObservable {
if username.characters.count == 0 {
return just((false, nil))
}
// this obviously won't be
if username.rangeOfCharacterFromSet(NSCharacterSet.alphanumericCharacterSet().invertedSet) != nil {
return just((false, "Username can only contain numbers or digits"))
}
let loadingValue = (valid: nil as Bool?, message: "Checking availabilty ..." as String?)
return API.usernameAvailable(username)
.map { available in
if available {
return (true, "Username available")
}
else {
return (false, "Username already taken")
}
}
.startWith(loadingValue)
}
func validatePassword(password: String) -> ValidationResult {
let numberOfCharacters = password.characters.count
if numberOfCharacters == 0 {
return (false, nil)
}
if numberOfCharacters < minPasswordCount {
return (false, "Password must be at least \(minPasswordCount) characters")
}
return (true, "Password acceptable")
}
func validateRepeatedPassword(password: String, repeatedPassword: String) -> ValidationResult {
if repeatedPassword.characters.count == 0 {
return (false, nil)
}
if repeatedPassword == password {
return (true, "Password repeated")
}
else {
return (false, "Password different")
}
}
}

View File

@ -0,0 +1,9 @@
//
// NumbersViewController.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation

View File

@ -0,0 +1,59 @@
//
// SimpleValidation.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import UIKit
import RxSwift
import RxCocoa
let mininalUsernameLength = 5
let mininalPasswordLength = 5
class SimpleValidationViewController : ViewController {
@IBOutlet weak var username: UITextField!
@IBOutlet weak var usernameValid: UILabel!
@IBOutlet weak var password: UITextField!
@IBOutlet weak var passwordValid: UILabel!
@IBOutlet weak var doSomething: UIButton!
func showAlert() {
DefaultWireframe.sharedInstance.promptFor("Something wonderful has been done", cancelAction: "OK", actions: [])
}
override func viewDidLoad() {
super.viewDidLoad()
let usernameValid = username.rx_text
.map { $0.characters.count >= mininalUsernameLength }
.shareReplay(1) // without this map would be executed once for each binding, rx is stateless by default
let passwordValid = password.rx_text
.map { $0.characters.count >= mininalPasswordLength }
.shareReplay(1)
let everythingValid = combineLatest(usernameValid, passwordValid) { $0 && $1 }
.shareReplay(1)
usernameValid
.bindTo(password.rx_enabled)
.addDisposableTo(disposeBag)
everythingValid
.bindTo(doSomething.rx_enabled)
.addDisposableTo(disposeBag)
doSomething.rx_tap
.subscribeNext(showAlert)
.addDisposableTo(disposeBag)
}
}

View File

@ -0,0 +1,9 @@
//
// Operators.swift
// RxExample
//
// Created by Krunoslav Zaher on 12/6/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation

View File

@ -0,0 +1,9 @@
//
// NSLayoutConstraint+RxTests.swift
// RxTests
//
// Created by Krunoslav Zaher on 12/6/15.
//
//
import Foundation

View File

@ -0,0 +1,9 @@
//
// NSView+RxTests.swift
// RxTests
//
// Created by Krunoslav Zaher on 12/6/15.
//
//
import Foundation

View File

@ -1,15 +0,0 @@
//
// RxCocoaTests.swift
// RxTests
//
// Created by Krunoslav Zaher on 7/6/15.
//
//
import Foundation
import XCTest
import RxSwift
import RxCocoa
class RxCocoaTest : RxTest {
}

View File

@ -1,80 +0,0 @@
//
// UI+RxTests.swift
// RxTests
//
// Created by Krunoslav Zaher on 5/3/15.
//
//
import Foundation
import RxSwift
import RxCocoa
import XCTest
class UITextFieldMock {
let observableText = Variable<String>("")
var text: String! = "" {
didSet {
observableText.value = self.text
}
}
func rx_text() -> Observable<String> {
return observableText.asObservable()
}
}
extension ObservableType where E == String {
func subscribeTextOf(label: UILabelMock) -> Disposable {
return self.subscribeNext { t in
label.text = t
}
}
}
class UILabelMock {
var text: String! = ""
}
class UIRxTests : RxTest {
func testArea() {
}
func testReadmeExample() {
// We have some async Wolfram Alpha API that calculates is number prime.
let WolframAlphaIsPrime: (Int) -> Observable<PrimeNumber> = { just(PrimeNumber($0, isPrime($0))) }
let primeTextField = UITextFieldMock()
let resultLabel = UILabelMock()
let _ = primeTextField.rx_text()
.map { WolframAlphaIsPrime(Int($0) ?? 0) }
.concat()
.map { "number \($0.n) is prime? \($0.isPrime)" }
.subscribeTextOf(resultLabel)
.scopedDispose()
// this will set resultLabel.text! == "number 43 is prime? true"
primeTextField.text = "43"
}
}
struct PrimeNumber : Equatable {
let n: Int
let isPrime: Bool
init(_ n: Int, _ isPrime: Bool) {
self.n = n
self.isPrime = isPrime
}
}
func == (lhs: PrimeNumber, rhs: PrimeNumber) -> Bool {
return lhs.n == rhs.n && lhs.isPrime == rhs.isPrime
}

View File

@ -0,0 +1,9 @@
//
// UIView+RxTests.swift
// RxTests
//
// Created by Krunoslav Zaher on 12/6/15.
//
//
import Foundation