1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[iOS]いきなり!RxSwift (当方はSwift初心者でいきなりRxSwift!) [実践編1] カウンターアプリを作ろう

1
Posted at

はじめに

前回は、RxSwiftの概要だけでお腹いっぱいになってしまいました。今回は実食に移りたいと思います。

環境

Xcode10.3
Swift5.0.1
RxSwift 4.3.1
RxCocoa 4.3.1

作るもの

前回に引き続き、

比較して学ぶRxSwift入門

こちらの書籍から、カウンターアプリを作っていきます。
機能としては、

・カウントの値を見ることができる
・カウントアップができる
・カウントダウンができる
・リセットができる

になります。

準備

1.プロジェクト作成

-Xcodeを起動
-Create a new Xcode project
 >Single View App
 >Product Name:CounterApp
 >完了。すぐにプロジェクトを閉じます。

2.ターミナルを起動して、ディレクトリに移動

$ cd CounterApp

3.Podfile作成/編集

$ pod init
$ vi Podfile
Podfile
# platform :ios, '9.0'

target 'CounterApp' do
  use_frameworks!

  pod 'RxSwift',    '~> 4.3.1' #この行を追加
  pod 'RxCocoa',    '~> 4.3.1' #この行を追加  

end

4.ライブラリのインストール

$ pod install

5.プロジェクトを開く
必ずCounterApp.xcworkspaceから起動する(.xcodeprojから起動した場合、導入したライブラリーが使えません)

Storyboardを削除!?

Storyboardを取り除き、ViewController + xibスタイルで開発をしていくとのことです。
著者様によりますと、Storyboardのデメリットとして、

  • アプリが大きくなるほど画面遷移が複雑になり見辛くなる
  • 都度、ViewControllerの生成が面倒
  • チーム開発になると*.storyboardがconflictしまくる

だそうです。当方、チーム開発経験がないため、わからず。
とりあえず、著者様のおっしゃる通りに進めてみましょう。

1.Main.storyboardの削除
/CounterApp/Main.storyboardをDelete > Move to Trash

2.Info.plist
Info.plistを開く > Main storyboard file base nameの項目を削除(マイナスボタン)

3.AppDelegateの修正

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    /* 追加 ここから */
    self.window = UIWindow(frame: UIScreen.main.bounds)
    let navigationController = UINavigationController(rootViewController: ViewController())
    self.window?.rootViewController = navigationController
    self.window?.makeKeyAndVisible()
    /* 追加 ここまで */
    
    return true
}

4.ViewController.xibの作成

  • New File > View > Save As: ViewController.xib > Create
  • ViewController.xibを開く
  • Placeholders > File's Ownerを選択
  • ClassにViewControllerを指定
  • OutletのviewとXibのViewを接続
  • Build & Run > 成功でOK

レイアウト修正

ボタンを3つ > IBActionとしてViewController.swiftに接続
ラベルを1つ > IBOutletとしてViewController.swiftに接続
以下のように配置します。
スクリーンショット 2019-07-26 18.38.14.png

↓接続後はこんな形になります。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func countUp(_ sender: Any) {
    }
    
    @IBAction func countDown(_ sender: Any) {
    }
    
    @IBAction func countReset(_ sender: Any) {
    }
}

パターン1:callbackを利用

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    
    private var viewModel: CounterViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel = CounterViewModel()

    }

    @IBAction func countUp(_ sender: Any) {
        viewModel.incrementCount(callback: { [weak self] count in
            self?.updateCountLabel(count)
        })
    }
    
    @IBAction func countDown(_ sender: Any) {
        viewModel.decrementCount(callback: {[weak self] count in
            self?.updateCountLabel(count)
        })
    }
    
    @IBAction func countReset(_ sender: Any) {
        viewModel.resetCount(callback: {[weak self] count in
            self?.updateCountLabel(count)
        })
    }
    
    private func updateCountLabel(_ count: Int) {
        countLabel.text = String(count)
    }
}

class CounterViewModel {
    private(set) var count = 0
    
    func incrementCount(callback: (Int) -> ()) {
        count += 1
        callback(count)
    }
    
    func decrementCount(callback: (Int) -> ()) {
        count -= 1
        callback(count)
    }
    
    func resetCount(callback: (Int) -> ()) {
        count = 0
        callback(count)
    }
}


パターン2:delegateを利用

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    
    private let presenter = CounterPresenter()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.attachView(self)
    }

    @IBAction func countUp(_ sender: Any) {
        presenter.increamentCount()
    }
    
    @IBAction func countDown(_ sender: Any) {
        presenter.decrementCount()
    }
    
    @IBAction func countReset(_ sender: Any) {
        presenter.resetCount()
    }
}

extension ViewController: CounterDelegate {
    func updateCount(count: Int) {
        countLabel.text = String(count)
    }
}

protocol CounterDelegate  {
    func updateCount(count: Int)
}

class CounterPresenter {
    private var count = 0 {
        didSet {
            delegate?.updateCount(count: count)
        }
    }
    
    private var delegate: CounterDelegate?
    
    func attachView(_ delegate: CounterDelegate) {
        self.delegate = delegate
    }
    
    func detachView() {
        self.delegate = nil
    }
    
    func increamentCount() {
        count += 1
    }
    
    func decrementCount() {
        count -= 1
    }
    
    func resetCount() {
        count = 0
    }
}


パターン3:RxSwiftを利用

ViewController.swift
import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    
    @IBOutlet weak var countLabel: UILabel!
    @IBOutlet weak var countUpButton: UIButton!
    @IBOutlet weak var countDownButton: UIButton!
    @IBOutlet weak var countResetButton: UIButton!
    
    private let diposeBag = DisposeBag()
    private var viewModel: CounterRxViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViewModel()
    }
    
    private func setupViewModel() {
        viewModel = CounterRxViewModel()
        let input = CounterViewModelInput(
            countUpButton: countUpButton.rx.tap.asObservable(),
            countDownButton: countDownButton.rx.tap.asObservable(),
            countResetButton: countResetButton.rx.tap.asObservable()
        )
        viewModel.setup(input: input)
        
        //iOSはメインスレッドでUI操作のため、Observable(BehaviorRelay)をDriverに変換して、監視するらしい
        viewModel.outputs?.counterText
            .drive(countLabel.rx.text)
            .disposed(by: diposeBag)
    }
}

struct CounterViewModelInput {
    let countUpButton: Observable<Void>
    let countDownButton: Observable<Void>
    let countResetButton: Observable<Void>
}

protocol CounterViewModelOutput {
    var counterText: Driver<String?>  {get}
}

protocol CounterViewModelType {
    var outputs: CounterViewModelOutput? {get}
    func setup(input: CounterViewModelInput)
}


//ViewModel
class CounterRxViewModel: CounterViewModelType {
    var outputs: CounterViewModelOutput?
    
    private let countRelay = BehaviorRelay<Int>(value: 0)
    private let initialCount = 0
    private let disposeBag = DisposeBag()
    
    init() {
        self.outputs = self
        resetCount()
    }
    
    func setup(input: CounterViewModelInput) {
        input.countUpButton
            .subscribe(onNext: { [weak self] in
                self?.incrementCount()
            })
            .disposed(by: disposeBag)
        
        input.countDownButton
            .subscribe(onNext: { [weak self] in
                self?.decrementCount()
            })
            .disposed(by: disposeBag)
        
        input.countResetButton
            .subscribe(onNext: { [weak self] in
                self?.resetCount()
            })
            .disposed(by: disposeBag)
    }
    
    private func incrementCount() {
        let count = countRelay.value + 1
        countRelay.accept(count)
    }
    
    private func decrementCount() {
        let count = countRelay.value - 1
        countRelay.accept(count)
    }
    
    private func resetCount() {
        countRelay.accept(initialCount)
    }
}

extension CounterRxViewModel: CounterViewModelOutput {
    var counterText: Driver<String?> {
        return countRelay
            .map {"\($0)" }
            .asDriver(onErrorJustReturn: nil)
    }
}

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?