// // SeismicNetworksMapDetailViewController.swift // Earthquake Network // // Created by Andrea Busi on 21/02/21. // Copyright Ā© 2021 Earthquake Network. All rights reserved. // import UIKit import MapKit import Shogun protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject { func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool) } class SeismicNetworksMapDetailViewController: EQNBaseMapViewController { private var pinStyle: MapPinStyle { get { AppPreferences.shared.mapPinStyle } set { AppPreferences.shared.mapPinStyle = newValue } } private let eqnSeismic = EQNSeismic.shared // MARK: - State override var isCloseButtonVisible: Bool { false } override var isFilterViewVisible: Bool { // a custom filter view is displayed true } weak var delegate: SeismicNetworksMapDetailViewControllerDelegate? // MARK: - UI override var filtersView: UIView { get { return seismicFiltersView } set { seismicFiltersView = newValue } } private lazy var seismicFiltersView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = AppTheme.Colors.lightGray // label with current selecte filter view.addSubview(seismicsFilterLabel) seismicsFilterLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true seismicsFilterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true seismicsFilterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true seismicsFilterLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true return view }() private lazy var seismicsFilterLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textAlignment = .center label.text = "" return label }() // MARK: - Internal private let seismic: EQNSisma? private var allSeismics: [EQNSisma] /// Contains circles drawed on the map private var mapCircles = [MKCircle]() // MARK: - Init init( seismic: EQNSisma?, allSeismics: [EQNSisma] ) { self.seismic = seismic self.allSeismics = allSeismics super.init() } required init?(coder: NSCoder) { fatalError("init(coder:) is not available, please use init(seismic:allSeismics:)") } // MARK: - View Lifecycle override func configureUI() { super.configureUI() navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in self?.dismiss(animated: true) })) navigationItem.rightBarButtonItems = [ UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in self?.shareScreenshot() })), UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in self?.nextPinStyle() })), ] } // MARK: - Public func updateSeismics(_ seismics: [EQNSisma]) { allSeismics = seismics loadDataSource() } override func registerMapAnnotationViews() { mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full)) mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light)) mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle)) } override func loadDataSource() { let annotations = allSeismics.map { EQNMapAnnotationSeismic(seismic: $0) } updateMap(with: annotations) // if the filter is "in radius", // show a circle with selected radius if eqnSeismic.filterOption == .inRadius, let distance = Double(eqnSeismic.maximumDistance) { addCircle(center: EQNUser.default().lastPosition, radius: distance * 1_000) } else { addCircle(center: nil, radius: 0) } loadFiltersRecap() } override func elaborateMapCenter() { if let seismic { setMapCenter(for: seismic.coordinate) } else if let location = CLLocationManager().location { setMapCenter(for: location) } } override func didTapAnnotation(_ annotation: MKAnnotation) { mapView.deselectAnnotation(annotation, animated: true) guard let annotation = annotation as? EQNMapAnnotationSeismic else { return } let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic) let title = "\(viewModel.magnitude) - \(viewModel.place) - \(viewModel.network)" let message = "" + "šŸ“ \(viewModel.depth)" + "\nā± \(viewModel.time)" + "\nšŸ“ \(viewModel.distance)" + "\nšŸŒ \(viewModel.coordinate)" + "\nšŸ‘„ \(viewModel.population)" let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("official_close", comment: ""), style: .cancel)) present(alert, animated: true) } override func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority { guard let annotation = annotation as? EQNMapAnnotationSeismic else { return .min } // il sisma cliccato dall'utente sta sopra a tutti if annotation.seismic == seismic { return .max } // Ordiniamo le annotazioni in base all amagnitudo, quelle con valore maggiore devono stare sopra. // La `zPriority` viene calcolata utilizzando la posizione nella lista let index = mapAnnotations .compactMap { $0 as? EQNMapAnnotationSeismic } .sorted(by: { $0.seismic.magnitude.doubleValue < $1.seismic.magnitude.doubleValue }) .firstIndex(where: { $0 == annotation }) guard let index else { return .min } let priority = Float(index) / Float(mapAnnotations.count) return .init(priority) } // MARK: - Private private func nextPinStyle() { pinStyle.advance() reloadMap() } private func loadFiltersRecap() { let filter = EQNSeismic.shared.filterOption let text = switch filter { case .inRadius: NSLocalizedString("filter_area", comment: "") case .positionRelevant: NSLocalizedString("filter_relevant", comment: "") case .worldWide: NSLocalizedString("filter_all", comment: "") case .userFelt: NSLocalizedString("filter_felt", comment: "") } seismicsFilterLabel.text = text } private func addCircle( center location: CLLocation?, radius: Double ) { // remove any previous circles mapView.removeOverlays(mapCircles) mapCircles.removeAll() guard let location = location else { return } let circles = [ MKCircle(center: location.coordinate, radius: radius), ] // !!note: is important to assign here the circles // otherwise `addOverlays` will not work mapCircles = circles // creo nuovi cerchi mapView.addOverlays(circles) } private func shareScreenshot() { let screenshot = createSnapshot() let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: []) present(controller, animated: true) } // MARK: - Map override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? { guard let annotation = annotation as? EQNMapAnnotationSeismic else { return nil } let isUserSelection = annotation.seismic == seismic return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection) } func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { guard let circleOverlay = overlay as? MKCircle else { return MKOverlayRenderer(overlay: overlay) } let circle = MKCircleRenderer(overlay: circleOverlay) circle.strokeColor = AppTheme.Colors.red circle.lineWidth = 2.0 return circle } } extension EQNMapAnnotationSeismic { func toAnnotationView( mapView: MKMapView, style: MapPinStyle, isUserSelection: Bool = false ) -> MKAnnotationView { switch style { case .full, .light: let identifier = EQNSeismicAnnotationView.identifier(for: style) let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView annotationView.title = self.title annotationView.subtitle = self.subtitle annotationView.magnitude = String(format: "M%.1f", self.seismic.magnitude.doubleValue) annotationView.magnitudeTextColor = self.textColor annotationView.magnitudeBackgroundColor = .white annotationView.isUserSelection = isUserSelection return annotationView case .circle: let identifier = EQNSeismicAnnotationView.identifier(for: style) let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView annotationView.image = image(height: EQNSeismicAnnotationView.CircleViewHeight, isUserSelection: isUserSelection) return annotationView } } } extension SeismicNetworksMapDetailViewController: SeismicFiltersViewControllerDelegate { func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) { delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate) } }