// // SeismicNetworkTableViewCell.swift // Earthquake Network // // Created by Busi Andrea on 22/09/2020. // Copyright ยฉ 2020 Earthquake Network. All rights reserved. // import UIKit import MapKit import CoreLocation protocol SeismicNetworkTableViewCellDelegate: class { func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) } class SeismicNetworkTableViewCell: UITableViewCell { static let Identifier = "SeismicNetworkTableViewCell" typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor) /// Available informations to display inside the cell enum InformationType: Int { case preliminary case time case distance case coordinate case population case realtimeSmartphones case reportUsers case buttons } /// Available cell type enum DisplayType { /// Compact view case normal /// Cell with map visible case mapExpanded /// Cell with weather info visible case weatherExpanded } /// Delegate weak var delegate: SeismicNetworkTableViewCellDelegate? // MARK: - Internal private static let DefaultVerticalSpacing: CGFloat = 6.0 private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body) private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light) private static var dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss d-MMM" return formatter }() /// Seismic to show private var seismic: EQNSisma? private(set) var displayType = DisplayType.normal private var informationTypes = [InformationType]() private var colors: MagnitudeColors? // MARK: - UI Components private lazy var containerView: UIView = { let view = UIView(frame: .zero) view.translatesAutoresizingMaskIntoConstraints = false view.layer.cornerRadius = AppTheme.shared.borderCornerRadius view.layer.masksToBounds = true return view }() private lazy var titleImageView: UIImageView = { let imageView = UIImageView(frame: .zero) imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() private lazy var placeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.preferredFont(for: .title2, weight: .semibold) label.numberOfLines = 2 return label }() private lazy var networkLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.backgroundColor = UIColor.white.withAlphaComponent(0.5) label.textAlignment = .center return label }() private lazy var magnitudeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.preferredFont(forTextStyle: .largeTitle) label.textColor = .red return label }() private lazy var depthLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Self.DefaultBodyFontLight return label }() private lazy var timeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Self.DefaultBodyFontLight return label }() private lazy var distanceLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Self.DefaultBodyFontLight return label }() private lazy var coordinateLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Self.DefaultBodyFontLight return label }() private lazy var populationLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Self.DefaultBodyFontLight return label }() private lazy var smartphonesLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 label.font = Self.DefaultBodyFont label.textAlignment = .center return label }() private lazy var alertsLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 label.font = Self.DefaultBodyFont label.textAlignment = .center return label }() private lazy var mapView: MKMapView = { let mapView = MKMapView(frame: .zero) mapView.translatesAutoresizingMaskIntoConstraints = false mapView.isScrollEnabled = false mapView.isZoomEnabled = false mapView.layer.cornerRadius = AppTheme.shared.borderCornerRadius mapView.layer.masksToBounds = true return mapView }() private lazy var weatherImageView: UIImageView = { let imageView = UIImageView(frame: .zero) imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() private lazy var weatherInfoLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 label.font = Self.DefaultBodyFontLight return label }() // MARK: - Init override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() } required init?(coder: NSCoder) { super.init(coder: coder) setupUI() } // MARK: - Setup private func setupUI() { selectionStyle = .default translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear // container view contentView.addSubview(containerView) containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5.0).isActive = true containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10.0).isActive = true containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0).isActive = true containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5.0).isActive = true // this variable is used to keep track of the previous view, in order to attach proper constraints var previousView: UIView = containerView // preliminary banner on top of the cell if informationTypes.contains(.preliminary) { let preliminaryLabel = UILabel() preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased() preliminaryLabel.textAlignment = .center preliminaryLabel.backgroundColor = .red preliminaryLabel.textColor = .yellow containerView.addSubview(preliminaryLabel) preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true previousView = preliminaryLabel } // title (bell icon, place label, seismic network and share button) let titleComponentsHeight: CGFloat = 30.0 let stackViewTitle = UIStackView() stackViewTitle.translatesAutoresizingMaskIntoConstraints = false stackViewTitle.axis = .horizontal stackViewTitle.distribution = .fill stackViewTitle.alignment = .center stackViewTitle.spacing = 4 let shareButton = UIButton(type: .custom) shareButton.setImage(UIImage(named: "share_icon"), for: .normal) shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside) stackViewTitle.addArrangedSubview(titleImageView) stackViewTitle.addArrangedSubview(placeLabel) stackViewTitle.addArrangedSubview(networkLabel) stackViewTitle.addArrangedSubview(shareButton) titleImageView.heightAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true titleImageView.widthAnchor.constraint(equalTo: titleImageView.heightAnchor).isActive = true networkLabel.heightAnchor.constraint(equalToConstant: 40.0).isActive = true networkLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true shareButton.widthAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor containerView.addSubview(stackViewTitle) stackViewTitle.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true stackViewTitle.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true stackViewTitle.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true let separator1 = addSeparator(constraintTo: stackViewTitle.bottomAnchor) let informationsLeadingAnchor = separator1.leadingAnchor let informationsTrailingAnchor = separator1.trailingAnchor // magnitude information containerView.addSubview(magnitudeLabel) magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true if !informationTypes.contains(.preliminary) { containerView.addSubview(depthLabel) depthLabel.lastBaselineAnchor.constraint(equalTo: magnitudeLabel.lastBaselineAnchor).isActive = true depthLabel.leadingAnchor.constraint(equalTo: magnitudeLabel.trailingAnchor, constant: 16).isActive = true } // informations let stackViewInformations = UIStackView() stackViewInformations.translatesAutoresizingMaskIntoConstraints = false stackViewInformations.axis = .vertical stackViewInformations.distribution = .equalSpacing stackViewInformations.spacing = 4 if informationTypes.contains(.time) { stackViewInformations.addArrangedSubview(timeLabel) } if informationTypes.contains(.distance) { stackViewInformations.addArrangedSubview(distanceLabel) } if informationTypes.contains(.coordinate) { stackViewInformations.addArrangedSubview(coordinateLabel) } if informationTypes.contains(.population) { stackViewInformations.addArrangedSubview(populationLabel) } containerView.addSubview(stackViewInformations) stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true previousView = stackViewInformations if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) { let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor) let stackViewReports = UIStackView() stackViewReports.translatesAutoresizingMaskIntoConstraints = false stackViewReports.axis = .vertical stackViewReports.distribution = .equalSpacing stackViewReports.alignment = .center stackViewReports.spacing = Self.DefaultVerticalSpacing if informationTypes.contains(.realtimeSmartphones) { stackViewReports.addArrangedSubview(smartphonesLabel) } if informationTypes.contains(.reportUsers) { stackViewReports.addArrangedSubview(alertsLabel) } containerView.addSubview(stackViewReports) stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).isActive = true let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor) previousView = separator3 } if informationTypes.contains(.buttons) { // buttons let stackViewButtons = UIStackView() stackViewButtons.translatesAutoresizingMaskIntoConstraints = false stackViewButtons.axis = .horizontal stackViewButtons.distribution = .fillEqually stackViewButtons.spacing = 4 let buttonMap = createRoundedButton(title: "๐Ÿ—บ", action: #selector(mapTapped(_:))) stackViewButtons.addArrangedSubview(buttonMap) let buttonWeather = createRoundedButton(title: "๐ŸŒค", action: #selector(weatherTapped(_:))) stackViewButtons.addArrangedSubview(buttonWeather) let buttonCalendar = createRoundedButton(title: "๐Ÿ“†", action: #selector(calendarTapped(_:))) stackViewButtons.addArrangedSubview(buttonCalendar) let buttonSettings = createRoundedButton(title: "๐Ÿ”ง", action: #selector(settingsTapped(_:))) stackViewButtons.addArrangedSubview(buttonSettings) containerView.addSubview(stackViewButtons) stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true previousView = stackViewButtons } if displayType == .mapExpanded { containerView.addSubview(mapView) mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true previousView = mapView } else if displayType == .weatherExpanded { let weatherTitleLabel = UILabel() weatherTitleLabel.translatesAutoresizingMaskIntoConstraints = false weatherTitleLabel.text = NSLocalizedString("weather_weather", comment: "") weatherTitleLabel.font = UIFont.preferredFont(forTextStyle: .headline) containerView.addSubview(weatherTitleLabel) weatherTitleLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true weatherTitleLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true weatherTitleLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true containerView.addSubview(weatherInfoLabel) containerView.addSubview(weatherImageView) weatherImageView.heightAnchor.constraint(equalToConstant: 60.0).isActive = true weatherImageView.widthAnchor.constraint(equalTo: weatherImageView.heightAnchor).isActive = true weatherImageView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true weatherImageView.trailingAnchor.constraint(equalTo: weatherInfoLabel.leadingAnchor, constant: -8.0).isActive = true weatherImageView.centerYAnchor.constraint(equalTo: weatherInfoLabel.centerYAnchor).isActive = true weatherInfoLabel.topAnchor.constraint(equalTo: weatherTitleLabel.bottomAnchor, constant: 4.0).isActive = true weatherInfoLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true previousView = weatherInfoLabel } if (displayType == .mapExpanded || displayType == .weatherExpanded) { let buttonClose = createRoundedButton(title: NSLocalizedString("CHIUDI", comment: ""), action: #selector(closeTapped(_:))) containerView.addSubview(buttonClose) buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true } else { previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true } } private func recreateUI() { // remove all subviews and recreate the required components containerView.subviews.forEach({ $0.removeFromSuperview() }) setupUI() } private func updateUI() { guard let seismic = seismic else { return } containerView.backgroundColor = colors?.startColor let notified = couldBeNotified(for: seismic) titleImageView.image = notified ? UIImage(named: "bell") : UIImage(named: "bell_disabled") // update seismic data placeLabel.text = seismic.place networkLabel.text = seismic.provider magnitudeLabel.textColor = colors?.textColor if informationTypes.contains(.preliminary) { let lowerValue = seismic.magnitude.doubleValue - seismic.magnitudeRange.doubleValue/2.0 let upperValue = seismic.magnitude.doubleValue + seismic.magnitudeRange.doubleValue/2.0 magnitudeLabel.text = String(format: "%.1f-%.1f\(seismic.magnitudeType)", lowerValue, upperValue) depthLabel.text = "" } else { magnitudeLabel.text = String(format: "%.1f%@", seismic.magnitude.doubleValue, seismic.magnitudeType) depthLabel.text = String(format: "%@: %.1f km", NSLocalizedString("Profonditร ", comment: ""), seismic.depth.doubleValue) } let formattedDate = Self.dateFormatter.string(from: seismic.date) let timeSuffix = seismic.timeDifference > 60 ? NSLocalizedString("ore fa", comment: "") : NSLocalizedString("minuti fa", comment: "") let timeRounded = seismic.timeDifference > 60 ? Int(round(seismic.timeDifference / 60)) : Int(seismic.timeDifference) timeLabel.text = "๐Ÿ•— \(formattedDate) - \(timeRounded) \(timeSuffix)" let distanceRounded = Int(round(seismic.userDistance)) distanceLabel.text = "๐Ÿ“ \(distanceRounded) km \(NSLocalizedString("dalla tua posizione", comment: ""))" let coordinateText = coordinateString(coordinate: seismic.coordinate.coordinate) coordinateLabel.text = "๐ŸŒ \(coordinateText)" // evaluate population string let populationIsRed = seismic.population100km >= 1_000_000 || seismic.userDistance <= 250 let population = formatPopulation(seismic.population100km) populationLabel.text = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ \(population) \(NSLocalizedString("share_radius100", comment: ""))" populationLabel.textColor = populationIsRed ? AppTheme.Colors.red : .black if seismic.smartphoneNumber.intValue > 0 || true { smartphonesLabel.text = String(format: NSLocalizedString("official_smartphones", comment: ""), seismic.smartphoneNumber) } if seismic.userNumber.intValue > 0 || true { alertsLabel.text = String(format: NSLocalizedString("official_reports", comment: ""), seismic.userNumber) } if displayType == .mapExpanded { // zoom based on population involved let longitudeSpan = mapSpanLongitude(population: seismic.population100km) let span = MKCoordinateSpan(latitudeDelta: longitudeSpan * 1.2, longitudeDelta: longitudeSpan) let region = MKCoordinateRegion(center: seismic.coordinate.coordinate, span: span) mapView.setCenter(seismic.coordinate.coordinate, animated: false) mapView.setRegion(region, animated: false) // add a pin on the center let annotation = MKPointAnnotation() annotation.coordinate = seismic.coordinate.coordinate annotation.title = "" mapView.addAnnotation(annotation) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(mapDetailTapped(_:))) mapView.addGestureRecognizer(tapRecognizer) } else if displayType == .weatherExpanded { weatherInfoLabel.text = "" + String(format: NSLocalizedString("weather_temperature", comment: ""), seismic.weatherTemperature.doubleValue - EQNMathKelvin) + "\n" + String(format: NSLocalizedString("weather_pressure", comment: ""), seismic.weatherPressure) + "\n" + String(format: NSLocalizedString("weather_windspeed", comment: ""), seismic.weatherWindSpeed) + "\n" + String(format: NSLocalizedString("weather_humidity", comment: ""), seismic.weatherHumidity) + "\n" + String(format: NSLocalizedString("weather_clouds", comment: ""), seismic.weatherCloud) weatherImageView.image = UIImage(named: "weather_\(seismic.weatherIcon).png") } } // MARK: - Public /// Configure the cell to display a seismic /// - Parameters: /// - seismic: Seismic to display /// - type: Type of cell /// - informations: Informations to show public func configure(with seismic: EQNSisma, type: DisplayType, informations: [InformationType]) { self.seismic = seismic self.colors = calculateColors(for: seismic.magnitude.doubleValue) self.displayType = type self.informationTypes = informations if !informations.contains(.time) { self.informationTypes += [.time] } if seismic.preliminary.intValue > 0 && !informations.contains(.preliminary) { self.informationTypes += [.preliminary] } if seismic.smartphoneNumber.intValue > 0 && !informations.contains(.realtimeSmartphones) { self.informationTypes += [.realtimeSmartphones] } if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) { self.informationTypes += [.reportUsers] } recreateUI() updateUI() } /// Creates a snapshot of the current cell /// - Returns: Image with the snapshot of the cell public func createSnapshot() -> UIImage { let renderer = UIGraphicsImageRenderer(size: contentView.bounds.size) let image = renderer.image { ctx in contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true) } return image } // MARK: - Actions @objc func shareTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapShare(self) } @objc func mapTapped(_ sender: UIButton) { if displayType != .mapExpanded { delegate?.seismicNetworkCellDidTapMap(self) } } @objc func weatherTapped(_ sender: UIButton) { if displayType != .weatherExpanded { delegate?.seismicNetworkCellDidTapWeather(self) } } @objc func calendarTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapCalendar(self) } @objc func settingsTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapSettings(self) } @objc func closeTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapClose(self) } @objc func mapDetailTapped(_ sender: Any) { delegate?.seismicNetworkCellDidTapMapDetail(self) } // MARK: - Helpers @discardableResult private func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView { let separator = UIView() separator.translatesAutoresizingMaskIntoConstraints = false separator.backgroundColor = .lightGray containerView.addSubview(separator) separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true return separator } private func createRoundedButton(title: String, action: Selector) -> EQNRoundedButton { let button = EQNRoundedButton(frame: .zero) button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: action, for: .touchUpInside) button.setTitle(title, for: .normal) button.setTitleColor(AppTheme.Colors.darkGray, for: .normal) button.backgroundColor = UIColor.white.withAlphaComponent(0.5) return button } /// Check if the user could be received a notification for this seismic private func couldBeNotified(for seismic: EQNSisma) -> Bool { let settings = EQNNotificheReteSismiche.shared() if !settings.isAbilitato { return false } if !settings.listaEnti.contains(seismic.provider) { return false } var notified = true if let radius = Double(settings.distanzaPosizione), seismic.userDistance > radius { notified = false } if let magnitude = Double(settings.energiaSisma), seismic.magnitude.doubleValue < magnitude { notified = false } if settings.isAbilitaVicini, seismic.userDistance < 50 { notified = true } if settings.isTerremortiForti, let strongMagnitude = Double(settings.energiaTerremotiForti), seismic.magnitude.doubleValue >= strongMagnitude { notified = true } return notified } /// Convert coordinates in degrees private func coordinateString(coordinate: CLLocationCoordinate2D) -> String { var latSeconds = Int(coordinate.latitude * 3600) let latDegrees = latSeconds / 3600 latSeconds = abs(latSeconds % 3600) let latMinutes = latSeconds / 60 latSeconds %= 60 var longSeconds = Int(coordinate.longitude * 3600) let longDegrees = longSeconds / 3600 longSeconds = abs(longSeconds % 3600) let longMinutes = longSeconds / 60 longSeconds %= 60 return String(format:"%dยฐ%d'%d\"%@ lat %dยฐ%d'%d\"%@ lon", abs(latDegrees), latMinutes, latSeconds, latDegrees >= 0 ? "N" : "S", abs(longDegrees), longMinutes, longSeconds, longDegrees >= 0 ? "E" : "W" ) } /// Determines the zoom for the map, based on the involved population private func mapSpanLongitude(population: Double) -> CLLocationDegrees { var zoom: CLLocationDegrees = 1 if population > 1_000_000 { zoom = 1 } else if population < 500 { zoom = 24 } else { zoom = 6 } return zoom } /// Format population value (ex. 1.5M, 2.4k) private func formatPopulation(_ population: Double) -> String { var populationString = "" if population > 999_999 { let roundedPopulation = round(population / 100_000) / 10 populationString = "\(roundedPopulation)M" } else if population > 999 { let roundedPopulation = round(population / 100) / 10 populationString = "\(roundedPopulation)K" } else { let roundedPopulation = round(population) populationString = "\(roundedPopulation)" } return populationString } /// Calculate colors to use for text and background of the cell private func calculateColors(for magnitude: Double) -> MagnitudeColors { var textColor = UIColor.black var r = 0, g = 0, b = 0 if (magnitude < 2.0) { let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0) r = Int(round(200.0 + (255.0 - 200.0) * fraction)) g = Int(round(226.0 + (255.0 - 226.0) * fraction)) b = Int(round(196.0 + (255.0 - 196.0) * fraction)) textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0) } if (magnitude >= 2.0 && magnitude < 3.5) { let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2) r = Int(round(136.0 + (200.0 - 136.0) * fraction)) g = Int(round(175.0 + (226.0 - 175.0) * fraction)) b = Int(round(131.0 + (196.0 - 131.0) * fraction)) textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0) } if (magnitude >= 3.5 && magnitude < 4.5) { let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5) r = 252 g = Int(round(233.0 + (253.0 - 233.0) * fraction)) b = Int(round(179.0 + (209.0 - 179.0) * fraction)) textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0) } if (magnitude >= 4.5 && magnitude < 5.5) { let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5) r = 252 g = Int(round(159.0 + (197.0 - 159.0) * fraction)) b = Int(round(161.0 + (197.0 - 161.0) * fraction)) textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0) } if (magnitude >= 5.5) { let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5) r = Int(round(190.0 + (254.0 - 190.0) * fraction)) g = Int(round(124.0 + (219.0 - 124.0) * fraction)) b = 255 textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0) } let r2 = min(r + 30, 255) let g2 = min(g + 30, 255) let b2 = min(b + 30, 255) let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0) let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0) return (textColor: textColor, startColor: startColor, endColor: endColor) } }