Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Combine】Operatorsを使ってPublishersを制御する

 

はじめに

【Combine】Operatorsを使ってPublishersが出力する値を変換する - Swift・iOS

の続きです。Appleのドキュメント(https://developer.apple.com/documentation/combine/receiving-and-handling-events-with-combine)の"Customize Publishers with Operators"にあたる内容です。SwiftUI版のサンプルを作って動きを確かめてみたので、記事に残します。

 

開発環境

 

サンプルコード

ContentView.swift

import SwiftUI
import Combine

struct ContentView: View {
    @ObservedObject private var viewModel: ContentViewModel
    
    init(viewModel: ContentViewModel) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        VStack {
            TextField("文字を入力", text: $viewModel.text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 300)
                .padding()
            
            Text("抽出条件に合致した文字列を出力\n\(viewModel.filteredText)")
                .padding()
            
            Button("サンプルをリセット") {
                viewModel.text = ""
                viewModel.filteredText = ""
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: ContentViewModel())
    }
}

 

ContentViewModel.swift

import Foundation
import Combine

final class ContentViewModel: ObservableObject {
    @Published var text = ""
    @Published var filteredText = ""
    
    private var cancellable: AnyCancellable?
    
    init() {
        cancellable = $text
            .filter( { $0.unicodeScalars.allSatisfy( { CharacterSet.alphanumerics.contains($0) } ) } )
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main) // ユーザーの入力が停止するのを待つ
            .receive(on: RunLoop.main)
            .assign(to: \.filteredText, on: self)
    }
}

※補足

Appleのドキュメントの以下実装箇所に関して

 NotificationCenter.default
    .publisher(for: NSControl.textDidChangeNotification, object: filterField)

TextFieldに入力された文字に変更があった際に通知を受け取る処理ですが、SwiftUIの場合は@PublishedなどのProperty Wrappersを使って入力文字の状態を監視することができるため、以下に変更しています。

$text

 

・"debounce(for:scheduler:options:)"によって、指定された時間が経過した後に値を公開しています。使い方のイメージですが、以下チュートリアル用のアプリが参考になりました。

※参考:GitHub - manchan/MVVM-with-Combine-Tutorial-for-iOS: MVVM with Combine Tutorial for iOS

入力した文字に合わせてAPIから天気情報を取得して表示しているようなのですが、1文字入力するごとに即時APIにリクエストするのは仕様上よくありません。そのため、debounceを使ってユーザーの入力を一定時間待った上でAPIにリクエストしています。

(該当の処理はWeeklyWeatherViewModel.swiftにあります。)

 

・処理の結果をfilteredTextに代入してUIを更新するため、"receive(on: RunLoop.main)"を実装しています。

 

SampleApp.swift(ライフサイクルが"SwiftUI App"の場合)

import SwiftUI

@main
struct SampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: ContentViewModel())
        }
    }
}

 

おわりに

前回の内容(【Combine】Operatorsを使ってPublishersが出力する値を変換する - Swift・iOS)とテーマが同じではありますが、値を変換するだけでなく、タイミングなども制御できることを動かしながら理解することができました。サンプルを作りながら新たに理解できたこともあるので、別途記事に残そうと思います。

 

参考