#12 Adding Pins to Map using MapKit - RxSwift MVVM Coordinator iOS App
protocol AirportDetailsViewPresentable {
typealias Input = ()
typealias Output = (
airportDetails: Driver<AirportViewPresentable>,
mapDetails: Driver<AirportMapViewPresentable>
)
typealias Dependencies = (
model: AirportModel,
currentLocation: Observable<(lat: Double, lon: Double)?>
)
var input: AirportDetailsViewPresentable.Input { get }
var output: AirportDetailsViewPresentable.Output { get }
typealias ViewModelBuilder = (AirportDetailsViewPresentable.Input) -> (AirportDetailsViewPresentable)
}
private static func transform(input: AirportDetailsViewPresentable.Input, dependencies: AirportDetailsViewPresentable.Dependencies) -> AirportDetailsViewPresentable.Output {
let airportDetails: Driver<AirportViewPresentable> = dependencies
.currentLocation
.compactMap({ $0 })
.map { [airportModel = dependencies.model] currentLocation in
AirportViewModel(model: airportModel, currentLocation: currentLocation)
}
.asDriver(onErrorDriveWith: .empty())
let mapDetails: Driver<AirportMapViewPresentable> = dependencies
.currentLocation
.compactMap({ $0 })
.map { [airportModel = dependencies.model] currentLocation in
guard
let lat = Double(airportModel.lat),
let lon = Double(airportModel.lon) else { throw URLError(.badURL) }
return AirportMapViewModel(airportLocation: (lat: lat, lon: lon), airport: (name: airportModel.name, city: airportModel.city), currentLocation: currentLocation)
}
.asDriver(onErrorDriveWith: .empty())
return (
airportDetails: airportDetails,
mapDetails: mapDetails
)
}
Driver
로 캐스팅해서 UI 업데이트에 적용import Foundation
protocol AirportMapViewPresentable {
var airport: (name: String, city: String) { get }
var currentLocation: (lat: Double, lon: Double) { get }
var airportLocation: (lat: Double, lon: Double) { get }
}
private var viewModel: AirportDetailsViewPresentable?
var viewModelBuilder: AirportDetailsViewPresentable.ViewModelBuilder!
private
으로 숨긴 뷰 모델과 달리 외부 접근 가능한 뷰 모델 빌더override func start() {
let vc = AirportDetailsViewController()
let locationService = LocationService.shared
vc.viewModelBuilder = {
AirportDetailsViewModel(input: $0, dependencies: (model: self.model, currentLocation: locationService.currentLocation))
}
router.present(vc, isAnimated: true, onDismiss: isCompleted)
}
coordinator
에 띄우는 단계인 start()
함수에서 외부 접근 가능한 뷰 모델 빌더의 클로저를 현 시점에서 선언coordinator
이기 때문 private func bind() {
viewModel?.output
.airportDetails
.map({ [weak self] viewModel in
self?.nameLabel.text = viewModel.name
self?.distanceLabel.text = viewModel.formattedDistance
self?.countryLabel.text = viewModel.address
self?.lengthLabel.text = viewModel.runwayLength
})
.drive()
.disposed(by: disposeBag)
viewModel?.output
.mapDetails
.map({ [weak self] viewModel in
let currentPoint = CLLocationCoordinate2D(latitude: viewModel.currentLocation.lat, longitude: viewModel.currentLocation.lon)
let airportPoint = CLLocationCoordinate2D(latitude: viewModel.airportLocation.lat, longitude: viewModel.airportLocation.lon)
let currentPin = AirportPin(name: "Current", coordinate: currentPoint)
let airportPin = AirportPin(name: viewModel.airport.name, city: viewModel.airport.city, coordinate: airportPoint)
self?.mapView.addAnnotations([currentPin, airportPin])
print("MapView add annotations")
let currentPlacemark = MKPlacemark(coordinate: currentPoint)
let destinationPlacemark = MKPlacemark(coordinate: airportPoint)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: currentPlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate { response, error in
guard
let route = response?.routes.first,
error == nil else { return }
self?.mapView.addOverlay(route.polyline
, level: .aboveRoads)
UIView.animate(withDuration: 1) {
let mapRect = route.polyline.boundingMapRect
let region = MKCoordinateRegion(mapRect)
self?.mapView.setRegion(region, animated: true)
}
}
})
.drive()
.disposed(by: disposeBag)
}
Driver
에 의해 관찰 가능한 데이터가 들어올 때 바인딩되는 곳