Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

RxSwift 超入門その3

class GeolocationViewController: ViewController {
    
    @IBOutlet weak private var noGeolocationView: UIView!
    @IBOutlet weak private var button: UIButton!
    @IBOutlet weak private var button2: UIButton!
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // No GeoLocation Viewを追加する
        view.addSubview(noGeolocationView)
        
        let geolocationService = GeolocationService.instance
        
        // GeoLocation が利用できる場合は No GeoLocation Viewを隠す
        geolocationService.authorized
            .drive(noGeolocationView.rx.isHidden)
            .disposed(by: disposeBag)
        
        // ロケーションが変化したら、座標ラベルを更新する
        geolocationService.location
            .drive(label.rx.coordinates)
            .disposed(by: disposeBag)
        
        // No GeoLocation Viewにあるプリファレンスオープンボタン
        button.rx.tap
            .bindNext { [weak self] in
                self?.openAppPreferences()
            }
            .disposed(by: disposeBag)
        
        // GeoLocation Viewにあるプリファレンスオープンボタン
        button2.rx.tap
            .bindNext { [weak self] in
                self?.openAppPreferences()
            }
            .disposed(by: disposeBag)
    }
    
    private func openAppPreferences() {
        UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
    }
}

RxSwift 超入門その2

let minimalUsernameLength = 5
let minimalPasswordLength = 5

class SimpleValidationViewController : ViewController {

    @IBOutlet weak var usernameOutlet: UITextField!
    @IBOutlet weak var usernameValidOutlet: UILabel!

    @IBOutlet weak var passwordOutlet: UITextField!
    @IBOutlet weak var passwordValidOutlet: UILabel!

    @IBOutlet weak var doSomethingOutlet: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 初期状態で表示するワーニングメッセージ
        usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters"
        // 初期状態で表示するワーニングメッセージ
        passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters"

        // ユーザー名が有効かどうか
        let usernameValid = usernameOutlet.rx.text.orEmpty
            .map { $0.characters.count >= minimalUsernameLength }
            .shareReplay(1)

        // パスワードが有効かどうか
        let passwordValid = passwordOutlet.rx.text.orEmpty
            .map { $0.characters.count >= minimalPasswordLength }
            .shareReplay(1)

        // ユーザー名とパスワードがともに有効かどうか
        let everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 }
            .shareReplay(1)

        // ユーザー名が有効になったらパスワード入力欄をenabledにする。その逆も行う。
        usernameValid
            .bindTo(passwordOutlet.rx.isEnabled)
            .disposed(by: disposeBag)

        // ユーザー名が有効になったらユーザー名不備メッセージを隠す。その逆も行う。
        usernameValid
            .bindTo(usernameValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)

        // パスワードが有効になったらパスワード不備メッセージを隠す。その逆も行う。
        passwordValid
            .bindTo(passwordValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)

        // ユーザー名、パスワードがともに有効になったらボタンを有効にする。その逆も行う。
        everythingValid
            .bindTo(doSomethingOutlet.rx.isEnabled)
            .disposed(by: disposeBag)

        // ボタンがタップされた時の処理
        doSomethingOutlet.rx.tap
            .subscribe(onNext: { [weak self] in self?.showAlert() })
            .disposed(by: disposeBag)
    }

    func showAlert() {
        let alertView = UIAlertView(
            title: "RxExample",
            message: "This is wonderful",
            delegate: nil,
            cancelButtonTitle: "OK"
        )

        alertView.show()
    }

}

このサンプルから学習できることは、.sharePlay(1)です。
これは無駄に同じ処理が複数回走るのを防ぐためのメソッドと考えて良いです。

RxSwift 超入門その1

0)環境: Xcode8.2.1

1) 新規iOSプロジェクトaaaを作成する。

2) aaaフォルダに新規ファイルPackage.swiftを以下の内容で作成する。

import PackageDescription

let package = Package(
    name: "dummy",
    targets: [], 
    dependencies: [
        .Package(url: "https://github.com/ReactiveX/RxSwift.git", majorVersion: 3)
    ]
)

3) aaaフォルダで

$ swift build

を実行する。エラーが出るが気にしない。

4) aaa.xcodeprojに、Packages/RxSwift-3.2.0/Rx.xcodeprojを追加し、
aaa.xcodeprojのターゲットaaaの
Target Dependencies
RxCocoa-iOS,RxSwift-iOSの2つを追加する。
Link Binary With Libraries
にもRxCocoa-iOS,RxSwift-iOSを追加し、ビルドする。

5) aaa.xcodeprojのMain.storyboardにUITextLabelを2つとUILabel一つを追加する。

6) ViewControllerの先頭に以下のソースを追加する。

import RxSwift
import RxCocoa

7) ViewControllerに以下のソースを追加し3つの@IBOutletを接続して実行する。

import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
    @IBOutlet weak var field1:UITextField!
    @IBOutlet weak var field2:UITextField!
    @IBOutlet weak var label:UILabel!
    var disposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad()
        Observable.combineLatest(field1.rx.text.orEmpty,
                                 field2.rx.text.orEmpty) {
                                    textValue1, textValue2 -> Int in
                return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0)
            }
            .map { $0.description }
            .bindTo(label.rx.text)
            .disposed(by: disposeBag)
    }
}

解説:
.rxはUITextFieldやUILabelなどUIKitのコントロールに追加されたswift extensionです。
combineLatestは2つの引数を持ちますが、2つの引数のどちらかに変化が発生した時に、イベントを発生させます。
その時、2つの引数の最新の値を利用してクロージャが実行され、以降の.mapの処理へと続きます。
このフローは、このViewControllerが破棄された時に不要となるため、このViewControllerのメンバ変数であるdisposeBagを使って.disposed(by:)することによって、ViewControllerの破棄タイミングでこのフローも破棄されるようにしています。

Go POSTによるJSONパラメータをデコードする際の注意

まずはソースをみて頂こう。

type User struct {
  name string
}

func q(w http.ResponseWriter, r *http.Request){
  decoder := json.NewDecoder(r.Body)
  var user User
  err := decoder.Decode(&user)
  if err != nil {
    panic(err)
  }
  fmt.Println(user)
}

エラーは発生せず、user.nameは常に空となる。
理由はnameの先頭が小文字なので、decoderがこの変数にアクセスできないためである。(たぶん)
nameをNameに変更してあげると、うまくいく。

ふー、まいった。。

go言語 ネストしたJSONをリテラルで初期化する

go

root := map[string]interface{}{
  "alpha": map[string]interface{}{
    "beta": []map[string]interface{}{
      map[string]interface{}{
        "gamma": "val1",
        "delta": []map[string]interface{}{
          map[string]interface{}{
            "epsilon": "val2",
          },
        },
      },
    },
  },
}

ruby

{
  alpha: {
    beta : [
      gamma: "val1",
      delta: [
        {
          epsilon: "val"
        }
      ]
    ]
  }
}

rubyだとめっちゃ楽なんだけどなあ。