SwiftUIでMVVM
この記事は ウィルゲート Advent Calendar 2019 の22日目の記事です。
ConbainとSwiftUIが登場したことで、データバインディングの仕組みを公式がサポートしました。 サンプルとしてMVVMでアプリを作ってみようと思います。
作るアプリ
天気を一覧で見れるアプリを作ってみる。 天気情報取得には以下のAPIを使用しました。
ディレクトリ構成について
app - View - ViewModel - Model
上表のように各役割によってディレクトリを分ける。 役割ごとに依存の方向は下図のようにしていきます。
Viewについて
ユーザーへ機能を提供するために描画を担当します。また、ユーザーからの入力を受け取りViewModelへ渡します。 またViewModelのデータとデータバインディングすることでデータの変更と描画の変更を同期させます。
ViewModelについて
Viewを描画するための状態の保持と、Viewから受け取った入力を適切な形に変換してModelに伝達する役目を持っています。 Viewとの通信はデータバインディングの機構を使って行われます。
Modelについて
アプリケーションのドメインを持つのがModelです。ビジネスロジックを書くところになります。また、データの永続化を行う箇所でもあります。
コードについての説明
Model
APIのレスポンス値からCodableのコードを作成してくれる、quicktypeを使用して自動生成できます。 自動生成したので基本的に何も変えずに使用します。
// MARK: - Weathers struct Weathers: Codable { let coord: Coord let weather: [Weather] let base: String let main: Main let visibility: Int let wind: Wind let clouds: Clouds let dt: Int let sys: Sys let id: Int let name: String let cod: Int } // MARK: - Clouds struct Clouds: Codable { let all: Int } // MARK: - Coord struct Coord: Codable { let lon, lat: Double } // MARK: - Main struct Main: Codable { let temp: Double let pressure, humidity: Int let tempMin, tempMax: Double enum CodingKeys: String, CodingKey { case temp, pressure, humidity case tempMin = "temp_min" case tempMax = "temp_max" } } // MARK: - Sys struct Sys: Codable { let type, id: Int let message: Double let country: String let sunrise, sunset: Int } // MARK: - Weather struct Weather: Codable { let id: Int let main, weatherDescription, icon: String enum CodingKeys: String, CodingKey { case id, main case weatherDescription = "description" case icon } } // MARK: - Wind struct Wind: Codable { let speed: Double let deg: Int }
ViewModel
データをAPIから取得する処理とModelの型に合わせて変数へ格納する処理を書いていきます。 また、Viewへデータバインドしたいので、PassthroughSubjectとObservableObjectを使用してバインドするための準備もしています。
class WeatherViewModel: Identifiable { let id = UUID() let weather: Weather init(weather: Weather) { self.weather = weather } var main: String { return self.weather.main } } class WeathersViewModel: ObservableObject { let didChange = PassthroughSubject<MemosViewModel,Never>() init() { fetchTopHeadlines() } var weathers = [MemoViewModel]() { didSet { didChange.send(self) } } private func fetchTopHeadlines() { let apKey = "" let baseURL = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=" guard let url = URL(string: baseURL + apKey) else { fatalError("URL is not correct!") } ApiCommon().loadTopHeadlines(url: url) { weathers in if let weathers = weathers { self.weathers = weathers.map(MemoViewModel.init) } } } }
ViewModelをからデータをバインドするためObservedObjectを使用してオブザーバーを使用できるようにします。 こうすることでデータが変更されると自動的にViewが更新されるようになります。
View
import SwiftUI struct WeathersView: View { @ObservedObject var model = WeathersViewModel() var body: some View { List(model.weathers) { weather in Text(weather.main) } } } struct MemoView_Previews: PreviewProvider { static var previews: some View { MemoView() } }
まとめ
SwiftUIを使うことでこれまでよりも簡単にMVVMの実装をすることが出来ました。 その他のアーキテクチャについても実装をしてみます。
明日はcuresevenさんで、『アドベントカレンダー戦略』です。 お楽しみに!