Swift MVVM 装箱绑定
Swift MVVM Bind with Boxing
我只是想用 WeatherViewController 创建一个天气应用程序,显示带有 cells 的 tableView ],当单元格被点击时导致WeatherDetailsViewController。
我正在使用 boxing 方式进行 binding 如果我设置 Dynamic type[=56 我会感到困惑=] 在下面示例中的 model 和 viewModel 中。你会明白我的意思。
这就是拳击Class
class Dynamic<T>: Decodable where T: Decodable {
typealias Listener = (T) -> ()
var listener: Listener?
var value: T {
didSet {
listener?(value)
}
}
func bind(listener: @escaping Listener) {
self.listener = listener
self.listener?(self.value)
}
init(_ value: T) {
self.value = value
}
private enum CodingKeys: CodingKey {
case value
}
}
这是天气模型结构
struct Weather: Decodable {
let date: Dynamic<Int>
let description: Dynamic<String>
let maxTemperature: Dynamic<Double>
private enum CodingKeys: String, CodingKey {
case date = "time"
case description = "summary"
case maxTemperature = "temperatureMax"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try Dynamic(container.decode(Int.self, forKey: .date))
description = try Dynamic(container.decode(String.self, forKey: .description))
maxTemperature = try Dynamic(container.decode(Double.self, forKey: .maxTemperature))
}
}
这是我的 WeatherListViewModel 和 WeatherViewModel
在我的 WeatherViewModel 中,我已将类型指定为 Dynamic,但也在模型中指定了类型以便绑定到我的 WeatherDetailsViewController,对吗?
class WeatherListViewModel {
var weatherViewModels: [WeatherViewModel]
private var sessionProvider: URLSessionProvider
init(sessionProvider: URLSessionProvider) {
self.sessionProvider = sessionProvider
self.weatherViewModels = [WeatherViewModel]()
}
func numberOfRows(inSection section: Int) -> Int {
return weatherViewModels.count
}
func modelAt(_ index: Int) -> WeatherViewModel {
return weatherViewModels[index]
}
func didSelect(at indexPath: Int) -> WeatherViewModel {
return weatherViewModels[indexPath]
}
}
这是用于网络获取的 WeatherListViewModel 扩展,我在其中初始化 WeatherViewModel
func fetchWeatherLocation(withLatitude latitude: CLLocationDegrees, longitude: CLLocationDegrees, completion: @escaping handler) {
sessionProvider.request(type: WeatherWrapper.self, service: WeatherService.specificLocation, latitude: latitude, longitude: longitude) { [weak self] result in
switch result {
case let .success(weatherWrapper):
let weathers = weatherWrapper.daily.weathers
self?.weatherViewModels = weathers.map {
return WeatherViewModel(weather: [=14=])
}
completion()
case let .failure(error):
print("Error: \(error)")
}
}
}
这是 WeatherViewModel
结构 WeatherViewModel {
private(set) var weather: Weather
var temperature: Dynamic<Double>
var date: Dynamic<Int>
var description: Dynamic<String>
init(weather: Weather) {
self.weather = weather
self.temperature = Dynamic(weather.maxTemperature)
self.date = Dynamic(weather.date)
self.description = Dynamic(weather.description)
}
}
这是我的 WeatherDetailsViewController
这里我分别给labels赋值绑定得到变化
class WeatherDetailsViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
@IBOutlet private var cityLabel: UILabel!
@IBOutlet private var dateLabel: UILabel!
@IBOutlet private var descriptionLabel: UILabel!
@IBOutlet private var temperatureLabel: UILabel!
var viewModel: WeatherViewModel?
override func viewDidLoad() {
super.viewDidLoad()
setupVMBinding()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .never
}
private func setupVMBinding() {
if let viewModel = viewModel {
viewModel.date.bind {
self.dateLabel.text = [=16=].toString()
}
viewModel.temperature.bind {
self.temperatureLabel.text = "\([=16=])"
}
viewModel.description.bind {
self.descriptionLabel.text = [=16=].description
}
}
}
}
问题是,我是否只是在模型和视图模型中重复编写 Dynamic 类型?有没有更好的方法来做到这一点,或者我在正确的轨道上。抱歉,代码示例很长。
我认为您在 Weather
模型中重复编写 Dynamic。
不需要是Dynamic类型。
您可以创建一个 GenericDataSource
class GenericDataSource<T>: NSObject {
var data: Dynamic<T>?
}
在您的视图模型中。这将引用您的天气模型而无需创建动态类型。
class WeatherViewModel {
var dataSource: GenericDataSource<Weather>?
....
}
在你的视图控制器中
class WeatherDetailsViewController {
var viewModel: WeatherViewModel?
override func viewDidLoad() {
viewModel = ViewModel()
var dataSource = GenericDataSource<Weather>()
dataSource.data = Dynamic(Weather)
viewModel.dataSource = dataSource
setupVMBinding()
}
private func setupVMBinding() {
viewModel?.dataSource?.data?.bind {
self.dateLabel.text = [=12=].date
self.temperatureLabel.text = "\([=12=].maxTemperature)"
self.descriptionLabel.text = [=12=].description
}
}
}
我只是想用 WeatherViewController 创建一个天气应用程序,显示带有 cells 的 tableView ],当单元格被点击时导致WeatherDetailsViewController。
我正在使用 boxing 方式进行 binding 如果我设置 Dynamic type[=56 我会感到困惑=] 在下面示例中的 model 和 viewModel 中。你会明白我的意思。
这就是拳击Class
class Dynamic<T>: Decodable where T: Decodable {
typealias Listener = (T) -> ()
var listener: Listener?
var value: T {
didSet {
listener?(value)
}
}
func bind(listener: @escaping Listener) {
self.listener = listener
self.listener?(self.value)
}
init(_ value: T) {
self.value = value
}
private enum CodingKeys: CodingKey {
case value
}
}
这是天气模型结构
struct Weather: Decodable {
let date: Dynamic<Int>
let description: Dynamic<String>
let maxTemperature: Dynamic<Double>
private enum CodingKeys: String, CodingKey {
case date = "time"
case description = "summary"
case maxTemperature = "temperatureMax"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try Dynamic(container.decode(Int.self, forKey: .date))
description = try Dynamic(container.decode(String.self, forKey: .description))
maxTemperature = try Dynamic(container.decode(Double.self, forKey: .maxTemperature))
}
}
这是我的 WeatherListViewModel 和 WeatherViewModel
在我的 WeatherViewModel 中,我已将类型指定为 Dynamic,但也在模型中指定了类型以便绑定到我的 WeatherDetailsViewController,对吗?
class WeatherListViewModel {
var weatherViewModels: [WeatherViewModel]
private var sessionProvider: URLSessionProvider
init(sessionProvider: URLSessionProvider) {
self.sessionProvider = sessionProvider
self.weatherViewModels = [WeatherViewModel]()
}
func numberOfRows(inSection section: Int) -> Int {
return weatherViewModels.count
}
func modelAt(_ index: Int) -> WeatherViewModel {
return weatherViewModels[index]
}
func didSelect(at indexPath: Int) -> WeatherViewModel {
return weatherViewModels[indexPath]
}
}
这是用于网络获取的 WeatherListViewModel 扩展,我在其中初始化 WeatherViewModel
func fetchWeatherLocation(withLatitude latitude: CLLocationDegrees, longitude: CLLocationDegrees, completion: @escaping handler) {
sessionProvider.request(type: WeatherWrapper.self, service: WeatherService.specificLocation, latitude: latitude, longitude: longitude) { [weak self] result in
switch result {
case let .success(weatherWrapper):
let weathers = weatherWrapper.daily.weathers
self?.weatherViewModels = weathers.map {
return WeatherViewModel(weather: [=14=])
}
completion()
case let .failure(error):
print("Error: \(error)")
}
}
}
这是 WeatherViewModel
结构 WeatherViewModel {
private(set) var weather: Weather
var temperature: Dynamic<Double>
var date: Dynamic<Int>
var description: Dynamic<String>
init(weather: Weather) {
self.weather = weather
self.temperature = Dynamic(weather.maxTemperature)
self.date = Dynamic(weather.date)
self.description = Dynamic(weather.description)
}
}
这是我的 WeatherDetailsViewController 这里我分别给labels赋值绑定得到变化
class WeatherDetailsViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
@IBOutlet private var cityLabel: UILabel!
@IBOutlet private var dateLabel: UILabel!
@IBOutlet private var descriptionLabel: UILabel!
@IBOutlet private var temperatureLabel: UILabel!
var viewModel: WeatherViewModel?
override func viewDidLoad() {
super.viewDidLoad()
setupVMBinding()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .never
}
private func setupVMBinding() {
if let viewModel = viewModel {
viewModel.date.bind {
self.dateLabel.text = [=16=].toString()
}
viewModel.temperature.bind {
self.temperatureLabel.text = "\([=16=])"
}
viewModel.description.bind {
self.descriptionLabel.text = [=16=].description
}
}
}
}
问题是,我是否只是在模型和视图模型中重复编写 Dynamic 类型?有没有更好的方法来做到这一点,或者我在正确的轨道上。抱歉,代码示例很长。
我认为您在 Weather
模型中重复编写 Dynamic。
不需要是Dynamic类型。
您可以创建一个 GenericDataSource
class GenericDataSource<T>: NSObject {
var data: Dynamic<T>?
}
在您的视图模型中。这将引用您的天气模型而无需创建动态类型。
class WeatherViewModel {
var dataSource: GenericDataSource<Weather>?
....
}
在你的视图控制器中
class WeatherDetailsViewController {
var viewModel: WeatherViewModel?
override func viewDidLoad() {
viewModel = ViewModel()
var dataSource = GenericDataSource<Weather>()
dataSource.data = Dynamic(Weather)
viewModel.dataSource = dataSource
setupVMBinding()
}
private func setupVMBinding() {
viewModel?.dataSource?.data?.bind {
self.dateLabel.text = [=12=].date
self.temperatureLabel.text = "\([=12=].maxTemperature)"
self.descriptionLabel.text = [=12=].description
}
}
}