Commit 9142aa06 by Dmitriy Stepanets

Merge branch 'feature/radar'

# Conflicts:
#	1Weather.xcodeproj/project.pbxproj
#	1Weather/AppDelegate.swift
#	1Weather/Model/Settings.swift
#	1Weather/Resources/en.lproj/Localizable.strings
#	Podfile.lock
#	Pods/Manifest.lock
#	Pods/Pods.xcodeproj/project.pbxproj
#	Pods/Pods.xcodeproj/xcuserdata/dstepanets.xcuserdatad/xcschemes/xcschememanagement.plist
#	Pods/Target Support Files/Pods-1Weather/Pods-1Weather.debug.xcconfig
#	Pods/Target Support Files/Pods-1Weather/Pods-1Weather.release.xcconfig
parents cf0c3811 a674920c
No preview for this file type
......@@ -7,7 +7,7 @@
<key>1Weather.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>24</integer>
<integer>26</integer>
</dict>
<key>PG (Playground) 1.xcscheme</key>
<dict>
......
......@@ -3,70 +3,4 @@
uuid = "55281C35-FE9F-4CED-865E-FBED0E7393F6"
type = "0"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "89C8A0DD-3D0D-4C33-BA19-94E6A7991DA2"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "1Weather/UI/Helpers/DayControlsNavigationBar.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "50"
endingLineNumber = "50"
landmarkName = "restoreProgress()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "14AFAEC9-A39D-4B7B-9B2D-C2DA2519300A"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "1Weather/UI/Helpers/DayControlsNavigationBar.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
landmarkName = "showControls(progressValue:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "D794B66E-D7A6-4A78-A1CE-C34AC9DC948E"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "1Weather/UI/Helpers/DayControlsNavigationBar.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "51"
endingLineNumber = "51"
landmarkName = "restoreProgress()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "2AD266BE-E133-463F-BF26-2797D4C1A0EF"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "1Weather/UI/Helpers/DayControlsNavigationBar.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "40"
endingLineNumber = "40"
landmarkName = "showControls(progressValue:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
//
// UserDefaultsWrapper.swift
// AdsUserDefaultsWrapper.swift
// OneWeather
//
// Created by Demid Merzlyakov on 20.11.2018.
......@@ -9,7 +9,7 @@
import Foundation
/// A helper class for saving properties of a given type to UserDefaults.
public struct UserDefaultsWrapper<T> {
public struct AdsUserDefaultsWrapper<T> {
/// Name of the property to be used as a key in UserDefaults. Please, make sure this name doesn't overlap with other UserDefaults properties in other places in the app.
private let name: String
......
......@@ -121,7 +121,7 @@ public class Interstitial: NSObject {
private let log = AdLogger(componentName: "Interstitial")
private let userDefaults: UserDefaults
private var moPubInterstitial: MPInterstitialAdController?
private let currentScreenCountStorage: UserDefaultsWrapper<UInt>
private let currentScreenCountStorage: AdsUserDefaultsWrapper<UInt>
private var currentScreenCount: UInt {
get {
return currentScreenCountStorage.get() ?? 0
......@@ -210,7 +210,7 @@ public class Interstitial: NSObject {
self.delegate = delegate
self.loggingAlias = loggingAlias
self.userDefaults = userDefaults
self.currentScreenCountStorage = UserDefaultsWrapper<UInt>(name: "kInterstitialAdScreenCountKey-\(placementName)", userDefaults: userDefaults)
self.currentScreenCountStorage = AdsUserDefaultsWrapper<UInt>(name: "kInterstitialAdScreenCountKey-\(placementName)", userDefaults: userDefaults)
super.init()
updateLogPrefix()
notificationCenter.addObserver(self, selector: #selector(Interstitial.configChanged), name: .adConfigChanged, object: nil)
......
......@@ -11,6 +11,10 @@ import Firebase
import Flurry_iOS_SDK
import MoEngage
import GoogleMobileAds
import Swarm
private let WDT_APP_ID = "e3b73414"
private let WDT_APP_KEY = "25e8d6b72de3bcd528f7769b073cc335"
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
......@@ -19,6 +23,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ThemeManager.refreshAppearance()
// WDT radar setup
SwarmManager.sharedManager.authentication = SkywiseAuthentication(
app_id: WDT_APP_ID,
app_key: WDT_APP_KEY
)
self.window = UIWindow(frame: UIScreen.main.bounds)
CCPAHelper.shared.onAppLaunch()
FirebaseApp.configure()
......
......@@ -29,7 +29,7 @@ class AppCoordinator: Coordinator {
forecastCoordinator.start()
childCoordinators.append(forecastCoordinator)
let radarCoordinator = ForecastCoordinator(tabBarController: tabBarController)
let radarCoordinator = RadarCoordinator(tabBarController: tabBarController)
radarCoordinator.start()
childCoordinators.append(radarCoordinator)
......
//
// RadarCoordinator.swift
// 1Weather
//
// Created by Dmitry Stepanets on 09.04.2021.
//
import UIKit
class RadarCoordinator: Coordinator {
//Private
private let navigationController = UINavigationController(nibName: nil, bundle: nil)
private var tabBarController:UITabBarController?
//Public
var childCoordinators = [Coordinator]()
var parentCoordinator: Coordinator?
init(tabBarController:UITabBarController) {
self.tabBarController = tabBarController
}
func start() {
let radarViewController = RadarViewController(coordinator: self)
navigationController.viewControllers = [radarViewController]
tabBarController?.add(viewController: navigationController)
}
func viewControllerDidEnd(controller: UIViewController) {
//
}
}
//
// CACornerMask+All.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.04.2021.
//
import UIKit
extension CACornerMask {
static var all:CACornerMask {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
}
//
// UITabBarController+Hide.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
extension UITabBarController {
/**
Show or hide the tab bar.
- Parameter hidden: `true` if the bar should be hidden.
- Parameter animated: `true` if the action should be animated.
- Parameter transitionCoordinator: An optional `UIViewControllerTransitionCoordinator` to perform the animation
along side with. For example during a push on a `UINavigationController`.
*/
func setTabBar(hidden: Bool, animated: Bool = true, along transitionCoordinator: UIViewControllerTransitionCoordinator? = nil) {
guard isTabBarHidden != hidden else { return }
let offsetY = hidden ? tabBar.frame.height : -tabBar.frame.height
let endFrame = tabBar.frame.offsetBy(dx: 0, dy: offsetY)
let vc: UIViewController? = viewControllers?[selectedIndex]
var newInsets: UIEdgeInsets? = vc?.additionalSafeAreaInsets
let originalInsets = newInsets
newInsets?.bottom -= offsetY
/// Helper method for updating child view controller's safe area insets.
func set(childViewController cvc: UIViewController?, additionalSafeArea: UIEdgeInsets) {
cvc?.additionalSafeAreaInsets = additionalSafeArea
cvc?.view.setNeedsLayout()
}
// Update safe area insets for the current view controller before the animation takes place when hiding the bar.
if hidden, let insets = newInsets { set(childViewController: vc, additionalSafeArea: insets) }
guard animated else {
tabBar.frame = endFrame
return
}
// Perform animation with coordinato if one is given. Update safe area insets _after_ the animation is complete,
// if we're showing the tab bar.
weak var tabBarRef = self.tabBar
if let tc = transitionCoordinator {
tc.animateAlongsideTransition(in: self.view, animation: { _ in tabBarRef?.frame = endFrame }) { context in
if !hidden, let insets = context.isCancelled ? originalInsets : newInsets {
set(childViewController: vc, additionalSafeArea: insets)
}
}
} else {
UIView.animate(withDuration: 0.25, animations: { tabBarRef?.frame = endFrame }) { didFinish in
if !hidden, didFinish, let insets = newInsets {
set(childViewController: vc, additionalSafeArea: insets)
}
}
}
}
/// `true` if the tab bar is currently hidden.
var isTabBarHidden: Bool {
return !tabBar.frame.intersects(view.frame)
}
}
......@@ -122,7 +122,7 @@ public class LocationManager {
}
}
guard index < locations.count else {
assertionFailure("This shouldn't happen. Got to investigate.")
// assertionFailure("This shouldn't happen. Got to investigate.")
// But in runtime we can handle it gracefully
DispatchQueue.main.async {
self.selectedLocationIndex = self.locations.count > 0 ? 0 : nil
......
......@@ -67,6 +67,24 @@ public struct Location {
return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")"
}
public var supportsRadar: Bool {
// as of 4.2, we support most of North America, USA, Canada, Mexico, Cuba
// https://www.countrycallingcodes.com/iso-country-codes/north-america-codes.php
// Not Panama, Costa Rica
if let co = countryCode {
switch co {
case "", "US", "CA", "MX", "CU", "AI", "AG", "AW", "BS", "BB", "BZ", "BM",
"BQ", "VG", "KY", "NI", "CW", "DM", "DO", "SV", "GL", "GD", "GP",
"GT", "HT", "HN", "JM", "MQ", "PM", "MS", "KN", "PR",
"SX", "LC", "VC", "TT", "TC", "VI":
return true
default:
return false
}
}
return false
}
public func equals(to other: Location, onlyCompareLocationInfo: Bool) -> Bool {
guard !onlyCompareLocationInfo else {
return self == other
......
//
// RadarLayer.swift
// 1Weather
//
// Created by Dmitry Stepanets on 16.04.2021.
//
import UIKit
struct RadarLayer {
var pinned = false
let layer:RadarLayerType
}
//
// RadarLayerProtocol.swift
// 1Weather
//
// Created by Dmitry Stepanets on 19.04.2021.
//
import UIKit
protocol RadarLayerType {
var id:String { get }
var name:String { get }
var values:[String] { get }
var colors:[CGColor] { get }
}
//
// SevereLayerType.swift
// 1Weather
//
// Created by Dmitry Stepanets on 19.04.2021.
//
import UIKit
import Swarm
public enum SevereLayerType:String, CaseIterable, RadarLayerType {
case fire = "radar.severeLayer.fire"
case floods = "radar.severeLayer.floods"
case fog = "radar.severeLayer.fog"
case freezing = "radar.severeLayer.freezing"
case hurricaneTropical = "radar.severeLayer.hurricaneTropical"
case hurricaneTropicalTracks = "radar.severeLayer.hurricaneTropicalTracks"
case ice = "radar.severeLayer.ice"
case snow = "radar.severeLayer.snow"
case stormTornados = "radar.severeLayer.stormTornados"
case wind = "radar.severeLayer.wind"
case winter = "radar.severeLayer.winter"
var id:String {
return self.rawValue
}
var name:String {
return self.rawValue.localized()
}
var swarmLayer:SwarmGroup {
switch self {
case .fire:
return .fire
case .floods:
return .flood
case .fog:
return .fog
case .freezing:
return .freezing
case .hurricaneTropical:
return .hurricaneAndTropical
case .hurricaneTropicalTracks:
return .hurricaneTracks
case .ice:
return .ice
case .snow:
return .snow
case .stormTornados:
return .stormAndTornadoes
case .wind:
return .wind
case .winter:
return .winter
}
}
var values:[String] {
switch self {
case .fire:
return ["Watch", "Warning"]
case .floods:
return ["Flash warning", "Flash watch", "Flood warning"]
case .fog:
return ["Freezing advisory" , "Dense advisory"]
case .freezing:
return ["Freezing warning", "Frost advisory", "Frost warning"]
case .hurricaneTropical:
return ["Warning", "Watch", "Tropical warning", "Tropical watch"]
case .hurricaneTropicalTracks:
return ["Forecast track", "Past track", "Forecast cone", "Hurricane", "Tropical storm", "Low", "Invest area"]
case .ice:
return ["Strom warning", "Storm watch", "Sleet warning", "Sleet advisory", "Freezing rain adisory."]
case .snow:
return ["Heavy snow warning", "Snow advisory", "Blowing snow warning", "Blowing snow advisory"]
case .stormTornados:
return ["Tornado warning", "Tornado watch", "Severe T-Storm warning", "Severe T-storm watch"]
case .wind:
return ["High wind warning", "High wind advisory", "High wind watch", "Wind advisory", "Dust strom warning", "Blowing dust advisory"]
case .winter:
return ["Blizzard warning", "Blizzard watch", "Winter strom warning", "Winter storm watch", "Winter weather advisory"]
}
}
var colors:[CGColor] {
switch self {
case .fire:
return [UIColor(hex: 0xFFDDAD),
UIColor(hex: 0xFF1494)].map{$0.cgColor}
case .floods:
return [UIColor(hex: 0x8C0001),
UIColor(hex: 0x32CD33),
UIColor(hex: 0x00FF01)].map{$0.cgColor}
case .fog:
return [UIColor(hex: 0x008081),
UIColor(hex: 0x708090)].map{$0.cgColor}
case .freezing:
return [UIColor(hex: 0x00FFFF),
UIColor(hex: 0x6395EC),
UIColor(hex: 0x2B32B4)].map{$0.cgColor}
case .hurricaneTropical:
return [UIColor(hex: 0xFE1745),
UIColor(hex: 0xFF00FE),
UIColor(hex: 0x7F1819),
UIColor(hex: 0xF0807F)].map{$0.cgColor}
case .hurricaneTropicalTracks:
return [UIColor.black,
UIColor.black,
UIColor.black,
UIColor.black,
UIColor.black,
UIColor.black,
UIColor.black].map{$0.cgColor}
case .ice:
return [UIColor(hex: 0x8A008B),
UIColor(hex: 0x8A008B),
UIColor(hex: 0x87CEEA),
UIColor(hex: 0x7C68EE),
UIColor(hex: 0xDA70D5)].map{$0.cgColor}
case .snow:
return [UIColor(hex: 0x8A2BE1),
UIColor(hex: 0x669ACC),
UIColor(hex: 0xB0DFE5),
UIColor(hex: 0x81A1AC)].map{$0.cgColor}
case .stormTornados:
return [UIColor(hex: 0xFE0000),
UIColor(hex: 0xFFFF01),
UIColor(hex: 0xFEA500),
UIColor(hex: 0xDC7092)].map{$0.cgColor}
case .wind:
return [UIColor(hex: 0xDAA521),
UIColor(hex: 0xA78432),
UIColor(hex: 0xC48B00),
UIColor(hex: 0x9C8768),
UIColor(hex: 0xFEE4C3),
UIColor(hex: 0xAFAB64)].map{$0.cgColor}
case .winter:
return [UIColor(hex: 0xFE4600),
UIColor(hex: 0xCFFF86),
UIColor(hex: 0xFF69B3),
UIColor(hex: 0x4682B4),
UIColor(hex: 0x7C68EE)].map{$0.cgColor}
}
}
}
//
// WeatherLayerType.swift
// 1Weather
//
// Created by Dmitry Stepanets on 19.04.2021.
//
import UIKit
import Swarm
public enum WeatherLayerType:String, CaseIterable, RadarLayerType {
case radar = "radar.weatherLayer.radar"
case clouds = "radar.weatherLayer.clouds"
case surfaceTemp = "radar.weatherLayer.surfaceTemp"
case dewPoint = "radar.weatherLayer.dewPoint"
case relativeHumidity = "radar.weatherLayer.relativeHumidity"
case windSpeed = "radar.weatherLayer.windSpeed"
case uvIndex = "radar.weatherLayer.uvIndex"
var id:String {
return self.rawValue
}
var name:String {
return self.rawValue.localized()
}
var swarmLayer:SwarmBaseLayer {
switch self {
case .radar:
return .radar
case .clouds:
return .satellite
case .surfaceTemp:
return .surfaceTemp
case .dewPoint:
return .dewPoint
case .relativeHumidity:
return .humidiy
case .windSpeed:
return .wind
case .uvIndex:
return .uvIndex
}
}
var values:[String] {
switch self {
case .radar:
return ["Rain" , "Mixed", "Snow"]
case .clouds:
return ["Intense", "Weak"]
case .surfaceTemp:
return ["-40F", "0", "32", "70", "120"]
case .dewPoint:
return ["10", "20", "30", "40", "50", "60", "70"]
case .relativeHumidity:
return ["0%", "20", "50", "70", "100"]
case .windSpeed:
return ["0mph", "10", "20", "30", "40", "50"]
case .uvIndex:
return ["0", "1", "2", "3", "4", "6", "8", "10", "12", "14"]
}
}
var colors:[CGColor] {
switch self {
case .radar:
return [UIColor.red, UIColor.black].map{$0.cgColor}
case .clouds:
return [UIColor(hex: 0xbf0a04),
UIColor(hex: 0xfce500),
UIColor(hex: 0x00b6fc),
UIColor(hex: 0xadadad),
UIColor(hex: 0x000000)].map{$0.cgColor}
case .surfaceTemp:
return [UIColor(hex: 0x6406f6),
UIColor(hex: 0xa9f2ff),
UIColor(hex: 0xffeb30),
UIColor(hex: 0xff3700),
UIColor(hex: 0xff99ca),
UIColor(hex: 0xeeeeee),
UIColor(hex: 0xFF1D0E)].map{$0.cgColor}
case .dewPoint:
return [UIColor(hex: 0x0f720f),
UIColor(hex: 0x28de28),
UIColor(hex: 0x90de92),
UIColor(hex: 0xf2ff7e),
UIColor(hex: 0xffd22f),
UIColor(hex: 0xeb9308),
UIColor(hex: 0x943000)].map{$0.cgColor}
case .relativeHumidity:
return [UIColor(hex: 0x0f720f),
UIColor(hex: 0x28de28),
UIColor(hex: 0x90de92),
UIColor(hex: 0xf2ff7e),
UIColor(hex: 0xffd22f),
UIColor(hex: 0xeb9308),
UIColor(hex: 0x943000)].map{$0.cgColor}
case .windSpeed:
return [UIColor(hex: 0xf705fa),
UIColor(hex: 0xfc0503),
UIColor(hex: 0xe18e00),
UIColor(hex: 0xecfa0d),
UIColor(hex: 0x00c896),
UIColor(hex: 0x1b3cec),
UIColor(hex: 0xFFFFFF)].map{$0.cgColor}
case .uvIndex:
return [UIColor(hex: 0x5a5a50),
UIColor(hex: 0x73736e),
UIColor(hex: 0x919191),
UIColor(hex: 0x916e6e),
UIColor(hex: 0xc8af5a),
UIColor(hex: 0xf0f03c),
UIColor(hex: 0xdc8c0a),
UIColor(hex: 0xdc4600),
UIColor(hex: 0xd2190a),
UIColor(hex: 0xdc378c)].map{$0.cgColor}
}
}
}
......@@ -8,63 +8,6 @@
import UIKit
import Localize_Swift
@propertyWrapper
struct UserDefaultsUnitValue<T> {
var wrappedValue: T {
get {
guard
let data = UserDefaults.standard.data(forKey: key),
let decoded = NSKeyedUnarchiver.unarchiveObject(with: data) as? T
else {
return defaultValue
}
return decoded
}
set {
let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
UserDefaults.standard.set(data, forKey: key)
UserDefaults.standard.synchronize()
Settings.shared.delegate.invoke { (delegate) in
delegate.settingsDidChange()
}
}
}
private let key: String
private let defaultValue: T
init(wrappedValue defaultValue:T, key:String) {
self.defaultValue = defaultValue
self.key = key
}
}
@propertyWrapper
struct UserDefaultsBasicValue<T> {
var wrappedValue: T {
get {
let value = UserDefaults.standard.value(forKey: key) as? T
return value ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
private let key: String
private let defaultValue: T
init(wrappedValue defaultValue:T, key:String) {
self.defaultValue = defaultValue
self.key = key
}
}
protocol SettingsDelegate:class {
func settingsDidChange()
}
......@@ -117,6 +60,12 @@ class Settings {
return Locale(identifier: Localize.currentLanguage())
}
@UserDefaultsBasicValue(key: "pinnedLayers")
public var pinnedLayerIds:[String] = []
@UserDefaultsBasicValue(key: "selectedLayer")
public var selectedLayerId:String = WeatherLayerType.radar.rawValue
#warning("Not implemented!")
//TODO: implement store in UserDefaults and configure via UI in debug builds.
public var adLogging: Bool = true
......
{
"images" : [
{
"filename" : "cross_icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "map_fullscreen.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "map_layers.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "map_legend_close.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "original"
}
}
{
"images" : [
{
"filename" : "map_precipitation.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "map_radar.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "map_time_bubble.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "pause_icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "pin.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "play_icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "radar_layer_pin.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "253",
"green" : "247",
"red" : "246"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "26",
"green" : "23",
"red" : "23"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -10,6 +10,7 @@
"general.close" = "close";
"general.more" = "more";
"general.search" = "search";
"general.add" = "add";
//Alert
"today.alert.infoTemplate.oneAlert" = "#ALERT_DESCRIPTION";
......@@ -122,7 +123,7 @@
"search.error.deleteError" = "Failed to delete location.";
"search.accessibility.tapToSelectLocation" = "Tap to select this location";
// Location
//Location
"location.servicesAreTurnedOff" = "System location services are turned off.";
"location.blockedByPermission" = "Location access is blocked by permission settings.";
"location.goToSettings.title" = "Location Services";
......@@ -135,6 +136,31 @@
"location.status.whenInUser" = "when in use";
"location.status.unknown" = "unknown";
//Radar
"radar.layers.base" = "Base layer";
"radar.layers.severe" = "Severe weather layer";
"radar.weatherLayer.radar" = "Radar";
"radar.weatherLayer.clouds" = "Clouds";
"radar.weatherLayer.surfaceTemp" = "Surface Temperature";
"radar.weatherLayer.dewPoint" = "Dew Point";
"radar.weatherLayer.relativeHumidity" = "Relative Humidity";
"radar.weatherLayer.windSpeed" = "Wind Speed";
"radar.weatherLayer.uvIndex" = "UV Index";
"radar.severeLayer.fire" = "Fire";
"radar.severeLayer.floods" = "Floods";
"radar.severeLayer.fog" = "Fog";
"radar.severeLayer.freezing" = "Freezing";
"radar.severeLayer.hurricaneTropical" = "Hurricane and tropical";
"radar.severeLayer.hurricaneTropicalTracks" = "Hurricane and tropical tracks";
"radar.severeLayer.ice" = "Ice";
"radar.severeLayer.snow" = "Snow";
"radar.severeLayer.stormTornados" = "Strorm and tornados";
"radar.severeLayer.wind" = "Wind";
"radar.severeLayer.winter" = "Winter";
//Menu
"menu.goPremium" = "Go premium";
"menu.premium.desc" = "Experience app without ads @ $8.99 a year";
......
//
// UserDefaultsWrapper.swift
// 1Weather
//
// Created by Dmitry Stepanets on 19.04.2021.
//
import Foundation
@propertyWrapper
struct UserDefaultsUnitValue<T> {
var wrappedValue: T {
get {
guard
let data = UserDefaults.standard.data(forKey: key),
let decoded = NSKeyedUnarchiver.unarchiveObject(with: data) as? T
else {
return defaultValue
}
return decoded
}
set {
let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
UserDefaults.standard.set(data, forKey: key)
UserDefaults.standard.synchronize()
Settings.shared.delegate.invoke { (delegate) in
delegate.settingsDidChange()
}
}
}
private let key: String
private let defaultValue: T
init(wrappedValue defaultValue:T, key:String) {
self.defaultValue = defaultValue
self.key = key
}
}
@propertyWrapper
struct UserDefaultsBasicValue<T> {
var wrappedValue: T {
get {
let value = UserDefaults.standard.value(forKey: key) as? T
return value ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
private let key: String
private let defaultValue: T
init(wrappedValue defaultValue:T, key:String) {
self.defaultValue = defaultValue
self.key = key
}
}
......@@ -110,4 +110,9 @@ struct DefaultTheme: ThemeProtocol {
var graphTintColor: UIColor {
return UIColor(named: "graph_tint_color") ?? .red
}
//Map
var mapControlsColor: UIColor {
return UIColor(named: "map_controls_color") ?? .red
}
}
......@@ -46,4 +46,7 @@ public protocol ThemeProtocol {
//Graph
var graphColor:UIColor { get }
var graphTintColor:UIColor { get }
//Map
var mapControlsColor:UIColor { get }
}
......@@ -145,7 +145,7 @@ private extension CityCell {
func prepareAddButton() {
addButton.isHidden = false
addButton.setTitle("+ \("search.add".localized().uppercased())", for: .normal)
addButton.setTitle("+ \("general.add".localized().uppercased())", for: .normal)
addButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
addButton.setTitleColor(.white, for: .normal)
addButton.backgroundColor = ThemeManager.currentTheme.graphTintColor
......
//
// MapLayersDismissAnimator.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
class MapLayersDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: .from) else {
return
}
let container = transitionContext.containerView
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
fromViewController.view.frame.origin.x = container.bounds.width
} completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
//
// MapLayersPresentationAnimator.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
class MapLayersPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to) else {
return
}
let container = transitionContext.containerView
toViewController.view.frame = .init(x: container.bounds.width,
y: 0,
width: 220,
height: container.frame.height)
container.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
toViewController.view.frame.origin.x = container.frame.width - 220
} completion: { finished in
transitionContext.completeTransition(finished)
}
}
}
//
// RadarLayerCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
protocol RadarLayerCellDelegate:class {
func cellPinButtonTouched(atLayer:RadarLayer)
}
class RadarLayerCell: UITableViewCell {
//Private
private let nameLabel = UILabel()
private let pinButton = UIButton()
private var currentRadarLayer:RadarLayer?
//Public
weak var delegate:RadarLayerCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
preparePinButton()
prepareNameLabel()
updateUI()
}
override var isSelected: Bool {
didSet {
contentView.backgroundColor = isSelected ? ThemeManager.currentTheme.graphTintColor.withAlphaComponent(0.2) :
ThemeManager.currentTheme.baseBackgroundColor
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func configure(radarLayer:RadarLayer) {
self.currentRadarLayer = radarLayer
nameLabel.text = radarLayer.layer.name
nameLabel.font = radarLayer.pinned ? AppFont.SFPro.bold(size: 14) : AppFont.SFPro.regular(size: 14)
pinButton.backgroundColor = radarLayer.pinned ? UIColor.black : UIColor(hex: 0x242B2E).withAlphaComponent(0.57)
pinButton.tintColor = radarLayer.pinned ? UIColor.white : UIColor(hex: 0xD9D9D9)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
private func updateUI() {
switch interfaceStyle {
case .light:
nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
nameLabel.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
@objc private func handlePinButton() {
guard let layer = currentRadarLayer else { return }
self.nameLabel.font = layer.pinned ? AppFont.SFPro.bold(size: 14) : AppFont.SFPro.regular(size: 14)
self.pinButton.backgroundColor = !layer.pinned ? UIColor.black : UIColor(hex: 0x242B2E).withAlphaComponent(0.57)
self.pinButton.tintColor = !layer.pinned ? UIColor.white : UIColor(hex: 0xD9D9D9)
self.delegate?.cellPinButtonTouched(atLayer: layer)
}
}
//MARK:- Prepare
private extension RadarLayerCell {
func prepareCell() {
selectionStyle = .none
contentView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
func prepareNameLabel() {
nameLabel.lineBreakMode = .byTruncatingMiddle
nameLabel.font = AppFont.SFPro.regular(size: 14)
contentView.addSubview(nameLabel)
nameLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(15)
make.centerY.equalToSuperview()
make.right.equalTo(pinButton.snp.left).offset(-8).priority(999)
}
}
func preparePinButton() {
pinButton.setImage(UIImage(named: "radar_layer_pin"), for: .normal)
pinButton.tintColor = .white
pinButton.layer.cornerRadius = 9
pinButton.backgroundColor = .black
pinButton.addTarget(self, action: #selector(handlePinButton), for: .touchUpInside)
contentView.addSubview(pinButton)
pinButton.snp.makeConstraints { (make) in
make.width.height.equalTo(18)
make.centerY.equalToSuperview()
make.right.equalToSuperview().inset(22)
}
}
}
//
// RadarLayersCellFactory.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
private enum Section {
case weather
case severe
}
private struct LayerSection {
let type:Section
let rowsCount:Int
}
class RadarLayersCellFactory: CellFactoryProtocol {
//Private
private let radarViewModel:RadarViewModel
private let sections:[LayerSection]
init(radarViewModel:RadarViewModel) {
self.radarViewModel = radarViewModel
sections = [LayerSection(type: .weather, rowsCount: radarViewModel.weatherLayers.count),
LayerSection(type: .severe, rowsCount: radarViewModel.severeLayers.count)]
}
var numberOfSections: Int {
return sections.count
}
func numberOfRows(inSection section: Int) -> Int {
return sections[section].rowsCount
}
func registerCells(on tableView: UITableView) {
registerCell(type: RadarLayerCell.self, tableView: tableView)
}
func cellFromTableView(tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
let cell = dequeueReusableCell(type: RadarLayerCell.self, tableView: tableView, indexPath: indexPath)
cell.delegate = self
switch sections[indexPath.section].type {
case .weather:
cell.configure(radarLayer: radarViewModel.weatherLayers[indexPath.row])
cell.isSelected = radarViewModel.weatherLayers[indexPath.row].layer.id == radarViewModel.selectedLayer?.layer.id
case .severe:
cell.configure(radarLayer: radarViewModel.severeLayers[indexPath.row])
cell.isSelected = radarViewModel.severeLayers[indexPath.row].layer.id == radarViewModel.selectedLayer?.layer.id
}
return cell
}
func didSelectRow(indexPath:IndexPath) {
switch sections[indexPath.section].type {
case .weather:
radarViewModel.select(layer: radarViewModel.weatherLayers[indexPath.row])
case .severe:
radarViewModel.select(layer: radarViewModel.severeLayers[indexPath.row])
}
}
}
//MARK:- RadarLayer cell delegate
extension RadarLayersCellFactory: RadarLayerCellDelegate {
func cellPinButtonTouched(atLayer: RadarLayer) {
radarViewModel.updatePinnedState(forLayer: atLayer)
}
}
//
// MapButton.swift
// 1Weather
//
// Created by Dmitry Stepanets on 09.04.2021.
//
import UIKit
class MapButton: UIControl {
//Private
private let imageView = UIImageView()
private let gradient = CAGradientLayer()
init(image: UIImage?) {
super.init(frame: .zero)
self.imageView.image = image
prepareButton()
prepareGradient()
prepareImageView()
updateUI()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = (self.bounds.height / 2).rounded(.down)
self.gradient.frame = self.bounds
self.gradient.cornerRadius = (self.bounds.height / 2).rounded(.down)
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
UIView.performWithoutAnimation {
self.alpha = 0.7
}
}
else {
UIView.performWithoutAnimation {
self.alpha = 1
}
}
}
}
//Private
private func updateUI() {
switch interfaceStyle {
case .light:
self.gradient.opacity = 0
self.backgroundColor = ThemeManager.currentTheme.mapControlsColor.withAlphaComponent(0.8)
self.imageView.tintColor = UIColor(hex: 0x3a4044)
case .dark:
self.gradient.opacity = 0.8
self.backgroundColor = .clear
self.imageView.tintColor = .white
}
}
//Public
public func setImage(image:UIImage?) {
self.imageView.image = image
}
}
//MARK:- Prepare
private extension MapButton {
func prepareButton() {
self.clipsToBounds = false
layer.shadowColor = UIColor.black.withAlphaComponent(0.3).cgColor
layer.shadowOffset = .init(width: 0, height: 3)
layer.shadowOpacity = 1
layer.shadowRadius = 5
}
func prepareGradient() {
gradient.startPoint = .init(x: 0.5, y: 0)
gradient.endPoint = .init(x: 0.5, y: 1)
gradient.colors = [UIColor(hex: 0x49494d).cgColor, UIColor(hex: 0x29292d).cgColor]
gradient.opacity = 0.8
layer.insertSublayer(gradient, at: 0)
}
func prepareImageView() {
imageView.isUserInteractionEnabled = false
imageView.contentMode = .center
imageView.clipsToBounds = true
addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}
//
// MapLegendGradientView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
class MapLegendGradientView: UIView {
init() {
super.init(frame: .zero)
prepare()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
public func set(colors:[CGColor]) {
guard let gradientLayer = self.layer as? CAGradientLayer else { return }
gradientLayer.colors = colors
}
}
//MARK:- Prepare
private extension MapLegendGradientView {
func prepare() {
self.clipsToBounds = true
self.backgroundColor = .clear
guard let gradientLayer = self.layer as? CAGradientLayer else { return }
gradientLayer.startPoint = .init(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 1, y: 0.5)
}
}
//
// MapLegendSevereView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 20.04.2021.
//
import UIKit
class MapLegendSevereView: UIView {
//Private
private let stackView = UIStackView()
init() {
super.init(frame: .zero)
prepare()
}
public func configure(severeLayer:SevereLayerType) {
stackView.removeAll()
severeLayer.values.enumerated().forEach {
let element = SevereLegendElement(color:UIColor(cgColor: severeLayer.colors[$0]), value: $1)
stackView.addArrangedSubview(element)
}
stackView.layoutIfNeeded()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func prepare() {
stackView.axis = .horizontal
stackView.spacing = 8
stackView.distribution = .equalSpacing
addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}
//MARK:- Severe Legend Element
private class SevereLegendElement:UIView {
private let imageView = UIImageView()
private let textLabel = UILabel()
init(color:UIColor, value:String) {
super.init(frame: .zero)
prepare()
updateUI()
imageView.backgroundColor = color
textLabel.text = value
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
private func prepare() {
self.snp.makeConstraints { (make) in
make.width.equalTo(50).priority(999)
}
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 5
imageView.contentMode = .scaleAspectFit
addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.top.equalToSuperview().inset(3).priority(999)
make.width.equalTo(30)
make.height.equalTo(10)
make.centerX.equalToSuperview()
}
textLabel.font = AppFont.SFPro.medium(size: 10)
textLabel.numberOfLines = 0
textLabel.textAlignment = .center
textLabel.lineBreakMode = .byWordWrapping
addSubview(textLabel)
textLabel.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalTo(imageView.snp.bottom).offset(2)
make.bottom.equalToSuperview().inset(2)
}
}
private func updateUI() {
switch interfaceStyle {
case .light:
textLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
textLabel.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
}
//
// MapLegendView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 12.04.2021.
//
import UIKit
import SnapKit
private enum LegendType {
case weather
case severe
}
class MapLegendView: UIView {
//Private
private let kCloseWidth:CGFloat = 40
private let containerView = UIView()
private let button = UIButton()
private let viewGradient = CAGradientLayer()
private let buttonGradient = CAGradientLayer()
private let legendWeatherView = MapLegendWeatherView()
private let legendSevereView = MapLegendSevereView()
private var legendType = LegendType.weather
private var widthConstraint:Constraint?
private var heightConstraint:Constraint?
private var isOpened = false
init() {
super.init(frame: .zero)
prepareView()
prepareViewGradient()
prepareButton()
prepareContainer()
prepareLegend()
updateUI()
containerView.setNeedsLayout()
containerView.layoutIfNeeded()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = (self.frame.height / 2).rounded()
self.buttonGradient.frame = self.button.bounds
self.buttonGradient.cornerRadius = self.button.layer.cornerRadius
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
//Public
public func configure(radarLayer:RadarLayer) {
legendSevereView.alpha = 0
legendWeatherView.alpha = 0
legendSevereView.snp.removeConstraints()
legendWeatherView.snp.removeConstraints()
if let weatherLayer = radarLayer.layer as? WeatherLayerType {
self.configure(weatherLayer: weatherLayer)
}
if let severeLayer = radarLayer.layer as? SevereLayerType {
self.configure(severeLayer: severeLayer)
}
}
//Private
private func configure(weatherLayer:WeatherLayerType) {
legendType = .weather
legendWeatherView.alpha = isOpened ? 1 : 0
buttonGradient.colors = weatherLayer.colors
legendWeatherView.configure(weatherLayer: weatherLayer)
rebuildWeatherConstraints()
}
private func configure(severeLayer:SevereLayerType) {
legendType = .severe
legendSevereView.alpha = isOpened ? 1 : 0
buttonGradient.colors = severeLayer.colors
legendSevereView.configure(severeLayer: severeLayer)
rebuildSevereConstraints()
}
private func updateUI() {
switch interfaceStyle {
case .light:
self.viewGradient.opacity = 0
self.backgroundColor = ThemeManager.currentTheme.mapControlsColor.withAlphaComponent(0.8)
case .dark:
self.viewGradient.opacity = 0.8
self.backgroundColor = .clear
}
}
private func rebuildSevereConstraints() {
legendSevereView.snp.makeConstraints { (make) in
make.left.top.bottom.equalToSuperview()
make.right.equalToSuperview().priority(.medium)
}
}
private func rebuildWeatherConstraints() {
legendWeatherView.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.right.equalToSuperview().priority(.medium)
make.top.equalToSuperview().inset(13)
make.bottom.equalToSuperview().inset(6)
}
}
@objc private func handleButton() {
if self.isOpened {
self.isOpened = false
self.widthConstraint?.isActive = true
self.heightConstraint?.isActive = true
}
else {
self.isOpened = true
self.widthConstraint?.isActive = false
self.heightConstraint?.isActive = false
}
self.containerView.setNeedsLayout()
self.setNeedsLayout()
self.button.setImage(isOpened ? UIImage(named: "map_legend_close") : nil, for: .normal)
UIView.animate(withDuration: 0.3) {
self.superview?.layoutIfNeeded()
self.layoutIfNeeded()
switch self.legendType {
case .weather:
self.legendWeatherView.alpha = self.isOpened ? 1 : 0
case .severe:
self.legendSevereView.alpha = self.isOpened ? 1 : 0
}
self.layer.borderWidth = self.isOpened ? 0.5 : 0
}
}
}
//MARK:- Prepare
private extension MapLegendView {
func prepareView() {
self.clipsToBounds = false
self.layer.borderColor = UIColor(hex: 0x353535).cgColor
layer.shadowColor = UIColor.black.withAlphaComponent(0.3).cgColor
layer.shadowOffset = .init(width: 0, height: 3)
layer.shadowOpacity = 1
layer.shadowRadius = 5
self.snp.makeConstraints { (make) in
self.widthConstraint = make.width.equalTo(kCloseWidth).constraint
self.heightConstraint = make.height.equalTo(kCloseWidth).constraint
}
}
func prepareViewGradient() {
viewGradient.startPoint = .init(x: 0.5, y: 0)
viewGradient.endPoint = .init(x: 0.5, y: 1)
viewGradient.colors = [UIColor(hex: 0x49494d).cgColor, UIColor(hex: 0x29292d).cgColor]
viewGradient.opacity = 0.8
layer.insertSublayer(viewGradient, at: 0)
}
func prepareButton() {
buttonGradient.startPoint = .init(x: 0, y: 0.5)
buttonGradient.endPoint = .init(x: 1, y: 0.5)
button.layer.cornerRadius = 14
button.addTarget(self, action: #selector(handleButton), for: .touchUpInside)
button.layer.insertSublayer(buttonGradient, below: button.imageView?.layer)
addSubview(button)
button.snp.makeConstraints { (make) in
make.width.height.equalTo(28)
make.left.equalToSuperview().inset(6)
make.centerY.equalToSuperview()
}
}
func prepareContainer() {
containerView.backgroundColor = .clear
containerView.clipsToBounds = true
addSubview(containerView)
containerView.snp.makeConstraints { (make) in
make.left.equalTo(button.snp.right).offset(6).priority(999)
make.right.equalToSuperview().inset(16)
make.top.bottom.equalToSuperview()
}
}
func prepareLegend() {
legendWeatherView.alpha = 0
legendWeatherView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(legendWeatherView)
legendSevereView.alpha = 0
legendSevereView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(legendSevereView)
}
}
//
// MapLegendWeatherView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 20.04.2021.
//
import UIKit
class MapLegendWeatherView: UIView {
//Private
private let legendGradient = MapLegendGradientView()
private let legendLabelsStackView = UIStackView()
private var legendLabels = [UILabel]()
override init(frame: CGRect) {
super.init(frame: frame)
prepareLegend()
prepareStackView()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
public func configure(weatherLayer:WeatherLayerType) {
legendGradient.set(colors: weatherLayer.colors)
legendLabelsStackView.removeAll()
weatherLayer.values.enumerated().forEach {
let label = UILabel()
label.font = AppFont.SFPro.regular(size: 10)
label.text = $1
self.legendLabels.append(label)
legendLabelsStackView.addArrangedSubview(label)
}
legendLabelsStackView.layoutIfNeeded()
}
//Private
private func updateUI() {
self.legendLabels.forEach {
switch interfaceStyle {
case .light:
$0.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
$0.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
}
}
private extension MapLegendWeatherView {
func prepareLegend() {
legendGradient.layer.cornerRadius = 2
addSubview(legendGradient)
legendGradient.snp.makeConstraints { (make) in
make.left.right.top.equalToSuperview()
make.height.equalTo(4).priority(999)
}
}
func prepareStackView() {
legendLabelsStackView.axis = .horizontal
legendLabelsStackView.spacing = 8
legendLabelsStackView.distribution = .fill
addSubview(legendLabelsStackView)
legendLabelsStackView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalTo(legendGradient.snp.bottom).offset(6).priority(999)
make.bottom.equalToSuperview().inset(4)
}
}
}
//
// MapCurrentTimeView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 09.04.2021.
//
import UIKit
class MapCurrentTimeView: UIView {
private let imageView = UIImageView()
private let valueLabel = UILabel()
private let timeLabel = UILabel()
private static var timeFormatter:DateFormatter = {
let fmt = DateFormatter()
fmt.dateFormat = "h:mm a"
return fmt
}()
init() {
super.init(frame: .zero)
prepareView()
prepareImageView()
prepareLabels()
}
public func configure(timeItem:MapTimeControlItem) {
MapCurrentTimeView.timeFormatter.timeZone = timeItem.timeZone
valueLabel.text = ""
timeLabel.text = MapCurrentTimeView.timeFormatter.string(from: timeItem.date)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK:- Prepare
private extension MapCurrentTimeView {
func prepareView() {
self.backgroundColor = .clear
}
func prepareImageView() {
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "map_time_bubble")
imageView.tintColor = ThemeManager.currentTheme.graphTintColor
addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
func prepareLabels() {
valueLabel.font = AppFont.SFPro.bold(size: 14)
valueLabel.textColor = .white
valueLabel.text = "49°"
valueLabel.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
addSubview(valueLabel)
timeLabel.textAlignment = .center
timeLabel.font = AppFont.SFPro.bold(size: 10)
timeLabel.textColor = .white
timeLabel.text = "2:50 PM"
addSubview(timeLabel)
//Constraints
valueLabel.snp.makeConstraints { (make) in
make.top.equalToSuperview().inset(4)
make.centerX.equalToSuperview()
}
timeLabel.snp.makeConstraints { (make) in
make.top.equalTo(valueLabel.snp.bottom)
make.left.right.equalToSuperview().inset(4)
make.bottom.equalToSuperview().inset(9)
}
}
}
//
// MapPinnedLayersView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 12.04.2021.
//
import UIKit
protocol MapPinnedLayersViewDelegate:class {
func didSelectLayer(layerId:String)
}
class MapPinnedLayersView: UIView {
//Private
private let pinContainer = UIView()
private let pinImageView = UIImageView()
private let scrollView = UIScrollView()
private let stackView = UIStackView()
//Public
weak var delegate: MapPinnedLayersViewDelegate?
public var isEmpty:Bool {
return stackView.arrangedSubviews.count < 2
}
init() {
super.init(frame: .zero)
preparePin()
prepareScrollView()
prepareStackView()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
//Public
public func setPinnedLayers(layers:[RadarLayer]) {
stackView.removeAll()
//Add button
let addButton = PinnedlLayerButton()
addButton.configureForAdd()
addButton.addTarget(self, action: #selector(handleLayerButton(button:)), for: .touchUpInside)
stackView.addArrangedSubview(addButton)
layers.forEach {
let button = PinnedlLayerButton(radarLayer: $0)
button.isSelected = false
button.addTarget(self, action: #selector(handleLayerButton(button:)), for: .touchUpInside)
stackView.insertArrangedSubview(button, at: 0)
}
stackView.layoutIfNeeded()
}
public func selectLayer(radarLayer:RadarLayer) {
stackView.arrangedSubviews.forEach {
if let layerButton = $0 as? PinnedlLayerButton {
layerButton.isSelected = radarLayer.layer.id == layerButton.layerId
}
}
}
//Private
private func updateUI() {
self.backgroundColor = ThemeManager.currentTheme.containerBackgroundColor
}
@objc private func handleLayerButton(button:PinnedlLayerButton) {
delegate?.didSelectLayer(layerId: button.layerId)
}
}
//MARK:- Prepare Pinned Layers
private extension MapPinnedLayersView {
func preparePin() {
pinContainer.backgroundColor = ThemeManager.currentTheme.graphTintColor
pinContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
pinContainer.layer.cornerRadius = 20
addSubview(pinContainer)
pinContainer.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.height.equalTo(40)
make.top.equalToSuperview().inset(3)
make.width.equalTo(36)
}
//Image view
pinImageView.image = UIImage(named: "pin")
pinImageView.tintColor = .white
pinImageView.contentMode = .scaleAspectFit
pinContainer.addSubview(pinImageView)
pinImageView.snp.makeConstraints { (make) in
make.width.height.equalTo(20)
make.left.equalToSuperview().inset(6)
make.centerY.equalToSuperview()
}
}
func prepareScrollView() {
scrollView.clipsToBounds = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.contentInset = .init(top: 0, left: 0, bottom: 0, right: 6)
insertSubview(scrollView, belowSubview: pinContainer)
scrollView.snp.makeConstraints { (make) in
make.left.equalTo(pinContainer.snp.right).offset(6)
make.right.equalToSuperview().priority(999)
make.top.equalToSuperview().inset(8)
}
}
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.spacing = 6
stackView.clipsToBounds = false
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.height.equalToSuperview()
}
}
}
//MARK:- PinnedLayer Button
private class PinnedlLayerButton: UIControl {
//Private
private let gradient = CAGradientLayer()
private let nameLabel = UILabel()
private var configuredForAdd = false
//Public
let layerId:String
init(radarLayer:RadarLayer? = nil) {
self.layerId = radarLayer?.layer.id ?? ""
super.init(frame: .zero)
nameLabel.text = radarLayer?.layer.name
prepareView()
prepareGradient()
prepareLabel()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.bounds.height / 2
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
//Gradient
gradient.frame = self.bounds
gradient.cornerRadius = self.layer.cornerRadius
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
override var isSelected: Bool {
didSet {
if configuredForAdd {
return
}
if isSelected {
self.layer.borderWidth = 0
self.nameLabel.font = AppFont.SFPro.bold(size: 14)
self.layer.shadowOpacity = 1
self.gradient.isHidden = false
}
else {
self.layer.borderWidth = 1
self.nameLabel.font = AppFont.SFPro.regular(size: 14)
self.layer.shadowOpacity = 0
self.gradient.isHidden = true
}
}
}
func configureForAdd() {
self.layer.shadowOpacity = 0
self.layer.borderWidth = 1
self.nameLabel.textColor = ThemeManager.currentTheme.graphTintColor
self.nameLabel.text = "general.add".localized().capitalized + " +"
self.isSelected = false
self.configuredForAdd = true
}
private func updateUI() {
if configuredForAdd {
nameLabel.textColor = ThemeManager.currentTheme.graphTintColor
return
}
switch interfaceStyle {
case .light:
nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
gradient.colors = [UIColor(hex: 0xffffff).cgColor, UIColor(hex: 0xeceef6).cgColor]
case .dark:
nameLabel.textColor = ThemeManager.currentTheme.primaryTextColor
gradient.colors = [UIColor(hex: 0x49494d).cgColor, UIColor(hex: 0x29292d).cgColor]
}
}
private func prepareView() {
self.backgroundColor = .clear
self.layer.borderWidth = 1
self.layer.borderColor = UIColor(hex: 0x979797).cgColor
self.layer.shadowColor = UIColor.black.withAlphaComponent(0.12).cgColor
self.layer.shadowOpacity = 1
self.layer.shadowOffset = .init(width: 0, height: 3)
self.layer.shadowRadius = 8
}
private func prepareGradient() {
gradient.startPoint = .init(x: 0.5, y: 0)
gradient.endPoint = .init(x: 0.5, y: 1)
layer.addSublayer(gradient)
}
private func prepareLabel() {
nameLabel.isUserInteractionEnabled = false
addSubview(nameLabel)
nameLabel.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(9)
make.top.bottom.equalToSuperview().inset(6)
}
}
}
//
// MapTimeControlItem.swift
// 1Weather
//
// Created by Dmitry Stepanets on 22.04.2021.
//
import Foundation
struct MapTimeControlItem {
let value:String
let date:Date
let timeZone:TimeZone
}
//
// MapTimeControlView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 09.04.2021.
//
import UIKit
import SnapKit
protocol MapTimeControlViewDelegate:class {
func didSelectPlayButton()
func didSelectPauseButton()
func didSelect(item:MapTimeControlItem)
}
private enum PlayButtonState {
case play
case pause
}
class MapTimeControlView: UIView {
//Private
private let container = UIView()
private let playButton = UIButton()
private let playButtonGradient = CAGradientLayer()
private let stackView = UIStackView()
private let currenTimeView = MapCurrentTimeView()
private let currentTimeDashLine = CAShapeLayer()
private let progresView = UIView()
private var currentItems = [MapTimeControlItem]()
private var playButtonState = PlayButtonState.pause
//Public
weak var delegate:MapTimeControlViewDelegate?
init() {
super.init(frame: .zero)
prepareView()
prepareContainer()
preparePlayButton()
prepareStackView()
prepareProgressView()
prepareCurrentTimeView()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
container.layoutIfNeeded()
playButton.layoutIfNeeded()
playButton.layer.cornerRadius = (playButton.bounds.width / 2).rounded(.down)
playButtonGradient.frame = playButton.bounds
playButtonGradient.cornerRadius = playButton.layer.cornerRadius
container.layer.shadowPath = UIBezierPath(roundedRect: self.container.bounds,
cornerRadius: 14).cgPath
playButton.layer.shadowPath = UIBezierPath(roundedRect: self.playButton.bounds,
cornerRadius: self.playButton.layer.cornerRadius).cgPath
//Dash line
let dashPath = CGMutablePath()
let startPoint = CGPoint(x: currenTimeView.center.x, y: 0)
let endPoint = CGPoint(x: currenTimeView.center.x, y: container.frame.height)
dashPath.addLines(between: [startPoint, endPoint])
currentTimeDashLine.path = dashPath
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
public func configure(items:[MapTimeControlItem], timeZone:TimeZone) {
self.currentItems = items
stackView.removeAll()
//Reduce count if needed for displaying
let maxValues = 5
if items.count > maxValues {
let steps = (items.count - 1) / (maxValues - 1)
for index in 0..<maxValues {
let view = MapTimeView(item: items[index * steps])
stackView.addArrangedSubview(view)
}
}
else {
items.forEach {
let view = MapTimeView(item: $0)
stackView.addArrangedSubview(view)
}
}
stackView.layoutIfNeeded()
}
public func updateProgress(value:CGFloat) {
var progress = max(0, value)
progress = min(1, progress)
currenTimeView.isHidden = false
currentTimeDashLine.isHidden = false
progresView.layer.maskedCorners = progress < 1 ? [.layerMinXMinYCorner, .layerMinXMaxYCorner] : .all
updateCurrentViewWith(proress: progress)
currenTimeView.snp.updateConstraints { (update) in
switch progress {
case 0:
update.centerX.equalTo(progresView.snp.right).inset(-50)
case 1:
update.centerX.equalTo(progresView.snp.right).offset(-10)
default:
update.centerX.equalTo(progresView.snp.right)
}
}
if progress == 0 {
progresView.snp.remakeConstraints { (remake) in
remake.left.top.bottom.equalToSuperview()
remake.width.equalToSuperview().multipliedBy(0)
}
return
}
progresView.snp.remakeConstraints {(remake) in
remake.left.top.bottom.equalToSuperview()
remake.width.equalToSuperview().multipliedBy(progress)
}
}
private func updateCurrentViewWith(proress:CGFloat) {
let targetIndex = Int(CGFloat(currentItems.count - 1) * proress)
let item = currentItems[targetIndex]
currenTimeView.configure(timeItem: item)
}
private func updateUI() {
container.backgroundColor = ThemeManager.currentTheme.mapControlsColor
switch interfaceStyle {
case .light:
playButtonGradient.colors = [UIColor(hex: 0xffffff).cgColor, UIColor(hex: 0xeceef6).cgColor]
case .dark:
playButtonGradient.colors = [UIColor(hex: 0x49494d).cgColor, UIColor(hex: 0x29292d).cgColor]
}
}
@objc private func handlePlayButton() {
switch playButtonState {
case .play:
playButton.setImage(UIImage(named: "pause_icon"), for: .normal)
delegate?.didSelectPlayButton()
self.playButtonState = .pause
case .pause:
playButton.setImage(UIImage(named: "play_icon"), for: .normal)
delegate?.didSelectPauseButton()
self.playButtonState = .play
}
}
@objc private func handleTapGesture(gestureRecoginzer:UIGestureRecognizer) {
let location = gestureRecoginzer.location(in: self)
let progress = max(location.x - stackView.frame.origin.x, 0) / (self.bounds.width - stackView.frame.origin.x)
let targetIndex = Int(CGFloat(currentItems.count) * progress)
let selectedItem = currentItems[max(targetIndex, currentItems.count - 1)]
}
}
//MARK:- Prepare
private extension MapTimeControlView {
func prepareView() {
backgroundColor = .clear
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(gestureRecoginzer:)))
addGestureRecognizer(tapGesture)
}
func prepareContainer() {
container.layer.cornerRadius = 14
container.layer.shadowColor = UIColor(hex:0x0c192f).withAlphaComponent(0.06).cgColor
container.layer.shadowOffset = .init(width: 0, height: 5)
container.layer.shadowRadius = 20
container.layer.shadowOpacity = 1
addSubview(container)
container.snp.makeConstraints { (make) in
make.left.bottom.right.equalToSuperview()
make.height.equalTo(46)
}
}
func preparePlayButton() {
playButton.setImage(UIImage(named: "pause_icon"), for: .normal)
playButton.tintColor = ThemeManager.currentTheme.graphTintColor
playButton.layer.shadowColor = UIColor.black.withAlphaComponent(0.12).cgColor
playButton.layer.shadowOffset = .init(width: 0, height: 3)
playButton.layer.shadowRadius = 8
playButton.layer.shadowOpacity = 1
playButton.addTarget(self, action: #selector(handlePlayButton), for: .touchUpInside)
playButtonGradient.startPoint = .init(x: 0.5, y: 0)
playButtonGradient.endPoint = .init(x: 0.5, y: 1)
playButton.layer.insertSublayer(playButtonGradient, below: playButton.imageView?.layer)
container.addSubview(playButton)
playButton.snp.makeConstraints { (make) in
make.width.height.equalTo(24)
make.centerY.equalToSuperview()
make.left.equalToSuperview().inset(12)
}
}
func prepareStackView() {
stackView.axis = .horizontal
stackView.distribution = .fillProportionally
stackView.clipsToBounds = false
container.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview()
make.left.equalTo(playButton.snp.right).offset(8)
make.right.equalToSuperview().inset(8)
}
}
func prepareCurrentTimeView() {
currenTimeView.isHidden = true
addSubview(currenTimeView)
currenTimeView.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.bottom.equalTo(container.snp.top)
make.centerX.equalTo(progresView.snp.right)
}
//Dash line
currentTimeDashLine.isHidden = true
currentTimeDashLine.lineWidth = 1
currentTimeDashLine.strokeColor = ThemeManager.currentTheme.graphTintColor.cgColor
currentTimeDashLine.lineDashPattern = [2,2]
container.layer.addSublayer(currentTimeDashLine)
}
func prepareProgressView() {
progresView.layer.cornerRadius = container.layer.cornerRadius
progresView.backgroundColor = ThemeManager.currentTheme.graphTintColor.withAlphaComponent(0.2)
container.insertSubview(progresView, belowSubview: playButton)
progresView.snp.makeConstraints { (make) in
make.left.top.bottom.equalToSuperview()
make.width.equalToSuperview().multipliedBy(0)
}
}
}
//
// MapTimeView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 09.04.2021.
//
import UIKit
class MapTimeView: UIView {
private let valueLabel = UILabel()
private let timeLabel = UILabel()
private static var timeFormatter:DateFormatter = {
let fmt = DateFormatter()
fmt.dateFormat = "h:mm"
return fmt
}()
init(item:MapTimeControlItem) {
super.init(frame: .zero)
prepareLabels()
updateUI()
valueLabel.text = item.value
MapTimeView.timeFormatter.timeZone = item.timeZone
timeLabel.text = MapTimeView.timeFormatter.string(from: item.date)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
private func updateUI() {
switch interfaceStyle {
case .light:
valueLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
timeLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
valueLabel.textColor = ThemeManager.currentTheme.primaryTextColor
timeLabel.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
}
//MARK:- Prepare
private extension MapTimeView {
func prepareLabels() {
valueLabel.font = AppFont.SFPro.regular(size: 10)
addSubview(valueLabel)
timeLabel.textAlignment = .center
timeLabel.font = AppFont.SFPro.regular(size: 14)
timeLabel.setContentCompressionResistancePriority(.fittingSizeLevel, for: .vertical)
addSubview(timeLabel)
//Constraints
valueLabel.snp.makeConstraints { (make) in
make.top.equalToSuperview().inset(10)
make.centerX.equalToSuperview()
}
timeLabel.snp.makeConstraints { (make) in
make.bottom.equalToSuperview().inset(5)
make.top.equalTo(valueLabel.snp.bottom).offset(4)
make.left.right.equalToSuperview().inset(2)
}
}
}
//
// RadarMapLayersController.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
class RadarMapLayersController: UIViewController {
//Private
private let radarViewModel:RadarViewModel
private let cellFactory:RadarLayersCellFactory
private let tableView = UITableView(frame: .zero, style: .grouped)
init(radarViewModel:RadarViewModel) {
self.radarViewModel = radarViewModel
self.cellFactory = RadarLayersCellFactory(radarViewModel: radarViewModel)
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
transitioningDelegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
prepareController()
prepareTableView()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.layer.shadowPath = CGPath(rect: self.view.bounds, transform: nil)
}
@objc private func handleCloseButton() {
self.dismiss(animated: true)
}
}
//MARK:- Prepare
private extension RadarMapLayersController {
func prepareController() {
view.clipsToBounds = false
view.backgroundColor = .black
view.layer.shadowColor = UIColor.black.withAlphaComponent(0.12).cgColor
view.layer.shadowRadius = 8
view.layer.shadowOffset = .init(width: 0, height: 3)
view.layer.shadowOpacity = 1
}
func prepareTableView() {
cellFactory.registerCells(on: tableView)
tableView.clipsToBounds = false
tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
tableView.tableHeaderView = UIView(frame: .init(origin: .zero, size: CGSize(width: 0, height: 0.01)))
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = 46
tableView.tableFooterView = UIView()
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}
//MARK:- UITableView Data Source
extension RadarMapLayersController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return cellFactory.numberOfSections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellFactory.numberOfRows(inSection: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cellFactory.cellFromTableView(tableView: tableView, indexPath: indexPath)
}
}
//MARK:- UITableView Delegate
extension RadarMapLayersController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return .leastNormalMagnitude
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let container = UIView()
container.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
switch section {
case 0:
let closeButton = UIButton()
closeButton.setTitle("general.close".localized().capitalized, for: .normal)
closeButton.setTitleColor(ThemeManager.currentTheme.graphTintColor, for: .normal)
closeButton.addTarget(self, action: #selector(handleCloseButton), for: .touchUpInside)
closeButton.titleLabel?.font = AppFont.SFPro.regular(size: 14)
container.addSubview(closeButton)
closeButton.snp.makeConstraints { (make) in
make.height.equalTo(24)
make.right.equalToSuperview().inset(20)
make.top.equalToSuperview().inset(18)
}
let label = UILabel()
label.textColor = view.interfaceStyle == .light ? ThemeManager.currentTheme.secondaryTextColor :
ThemeManager.currentTheme.primaryTextColor
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = AppFont.SFPro.bold(size: 18)
label.text = "radar.layers.base".localized()
container.addSubview(label)
label.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(18)
make.top.bottom.equalToSuperview().inset(18)
make.right.equalTo(closeButton.snp.left).offset(8).priority(999)
}
case 1:
let label = UILabel()
label.textColor = view.interfaceStyle == .light ? ThemeManager.currentTheme.secondaryTextColor :
ThemeManager.currentTheme.primaryTextColor
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = AppFont.SFPro.bold(size: 18)
label.text = "radar.layers.severe".localized()
container.addSubview(label)
label.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.top.bottom.equalToSuperview().inset(18)
}
default:
return nil
}
return container
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
cellFactory.didSelectRow(indexPath: indexPath)
tableView.reloadData()
self.handleCloseButton()
}
}
//MARK:- Transitioning Delegate
extension RadarMapLayersController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MapLayersPresentationAnimator()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MapLayersDismissAnimator()
}
}
//
// RadarViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 13.04.2021.
//
import UIKit
import Swarm
protocol RadarViewModelDelegate: ViewModelDelegate {
func viewModel(model:RadarViewModel, didSelectLayer layer:RadarLayer)
func viewModelPinnedLayersDidChange(model:RadarViewModel)
}
class RadarViewModel: ViewModelProtocol {
//Public
public weak var delegate:RadarViewModelDelegate?
public private(set) var weatherLayers = [RadarLayer]()
public private(set) var severeLayers = [RadarLayer]()
public var pinnedLayers:[RadarLayer] {
return weatherLayers.filter{ $0.pinned } + severeLayers.filter{ $0.pinned }
}
public var selectedLayer:RadarLayer? {
let selectedLayerId = Settings.shared.selectedLayerId
if let weatherSelectedLayer = (weatherLayers.first{$0.layer.id == selectedLayerId}) {
return weatherSelectedLayer
}
if let severeSelectedLayer = (severeLayers.first{$0.layer.id == selectedLayerId}) {
return severeSelectedLayer
}
return weatherLayers.first
}
//Private
private var swarmDateFormatter:DateFormatter = {
let fmt = DateFormatter()
fmt.timeZone = TimeZone(secondsFromGMT: 0)
fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return fmt
}()
private let locationManager = LocationManager.shared
var location:Location? {
return locationManager.selectedLocation
}
init() {
locationManager.add(delegate: self)
}
public func updatePinnedState(forLayer layer:RadarLayer) {
//Weather
if let weatherIndex = (weatherLayers.firstIndex{$0.layer.id == layer.layer.id}) {
weatherLayers[weatherIndex].pinned = !weatherLayers[weatherIndex].pinned
}
//Severe
if let severeIndex = (severeLayers.firstIndex{$0.layer.id == layer.layer.id}) {
severeLayers[severeIndex].pinned = !severeLayers[severeIndex].pinned
}
//Save changes
Settings.shared.pinnedLayerIds = self.pinnedLayers.map{$0.layer.id}
delegate?.viewModelPinnedLayersDidChange(model: self)
}
public func select(layer:RadarLayer) {
Settings.shared.selectedLayerId = layer.layer.id
delegate?.viewModel(model: self, didSelectLayer: layer)
}
public func select(layerId:String) {
var layer:RadarLayer?
if let weatherLayer = (weatherLayers.firstIndex{ $0.layer.id == layerId }) {
layer = weatherLayers[weatherLayer]
}
if let severeLayer = (severeLayers.firstIndex{ $0.layer.id == layerId }) {
layer = severeLayers[severeLayer]
}
guard let selectedLayer = layer else { return }
Settings.shared.selectedLayerId = layerId
delegate?.viewModel(model: self, didSelectLayer: selectedLayer)
}
public func buildTimelineValuesFor(validTimes:[String]) -> [MapTimeControlItem] {
#warning("Comparing hourly & valid date disabled")
// guard let hourly = self.location?.hourly else {
// return []
// }
guard let locationTimeZone = location?.timeZone else {
return []
}
var items = [MapTimeControlItem]()
for validTime in validTimes {
guard let validDate = swarmDateFormatter.date(from: validTime) else {
continue
}
items.append(.init(value: "", date: validDate, timeZone: locationTimeZone))
// for hourlyWeather in hourly {
// if validDate == hourlyWeather.date {
// items.append(.init(value: hourlyWeather.temp?.shortString ?? "",
// date: validDate))
// }
// }
}
return items
}
//Private
private func rebuildLayers() {
let pinnedLayerIds = Settings.shared.pinnedLayerIds
//Fill weather layers
if locationManager.selectedLocation?.supportsRadar == true {
weatherLayers = WeatherLayerType.allCases.map{ RadarLayer(pinned: pinnedLayerIds.contains($0.id), layer: $0) }
severeLayers = SevereLayerType.allCases.map{ RadarLayer(pinned: pinnedLayerIds.contains($0.id), layer: $0) }
}
else {
weatherLayers = [RadarLayer(pinned: pinnedLayerIds.contains(WeatherLayerType.clouds.id), layer: WeatherLayerType.clouds),
RadarLayer(pinned: pinnedLayerIds.contains(WeatherLayerType.uvIndex.id), layer: WeatherLayerType.uvIndex)]
severeLayers = [RadarLayer(pinned: pinnedLayerIds.contains(SevereLayerType.hurricaneTropicalTracks.id),
layer: SevereLayerType.hurricaneTropicalTracks)]
}
self.delegate?.viewModelPinnedLayersDidChange(model: self)
}
}
//MARK:- LocationManager Delegate
extension RadarViewModel: LocationManagerDelegate {
func locationManager(_ locationManager: LocationManager, changedSelectedLocation newLocation: Location?) {
self.rebuildLayers()
DispatchQueue.main.async {
self.delegate?.viewModelDidChange(model: self)
}
}
func locationManager(_ locationManager: LocationManager, updatedLocationsList newList: [Location]) {
// do nothing
}
}
......@@ -10,6 +10,8 @@ import UIKit
class TodayViewModel: ViewModelProtocol {
//Public
public weak var delegate:ViewModelDelegate?
//Private
private let locationManager = LocationManager.shared
private(set) var location:Location?
......
import Foundation
import UIKit
let far = Measurement<UnitTemperature>(value: 50, unit: .fahrenheit)
let fmt = MeasurementFormatter()
fmt.unitStyle = .long
fmt.unitOptions = .providedUnit
fmt.string(from: far.converted(to: .kelvin))
var arr = [3, 0, 6, 22, 55, 45, 232, 534, 1, 7, 9, 10]
let maxValues = 6
let steps = (arr.count - 1) / (maxValues - 1)
var result = [Int]()
for index in 0..<maxValues {
print("Fraction: \(index * steps)")
result.append(arr[index * steps])
}
print("Orig: \(arr)")
print("Result: \(result)")
......@@ -15,6 +15,9 @@ target '1Weather' do
pod 'Flurry-iOS-SDK/FlurrySDK'
pod 'MoEngage-iOS-SDK'
# If updating the podspec, make sure to add a tag and push it to origin
pod 'Swarm', :git => 'git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git', :branch => 'develop'
pod 'Firebase/Crashlytics'
# Recommended: Add the Firebase pod for Google Analytics
pod 'Firebase/Analytics'
......
......@@ -159,6 +159,7 @@ PODS:
- nanopb/encode (2.30908.0)
- PromisesObjC (1.2.12)
- SnapKit (5.0.1)
- Swarm (1.0.7)
- XMLCoder (0.12.0)
DEPENDENCIES:
......@@ -177,6 +178,7 @@ DEPENDENCIES:
- Localize-Swift
- MoEngage-iOS-SDK
- SnapKit
- "Swarm (from `git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git`, branch `develop`)"
- XMLCoder (~> 0.12.0)
SPEC REPOS:
......@@ -217,11 +219,17 @@ SPEC REPOS:
EXTERNAL SOURCES:
Cirque:
:git: https://github.com/StepanetsDmtry/Cirque.git
Swarm:
:branch: develop
:git: "git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git"
CHECKOUT OPTIONS:
Cirque:
:commit: ceb7ba910a35973cbcd41c73a62be6305aed4d13
:git: https://github.com/StepanetsDmtry/Cirque.git
Swarm:
:commit: 461fe863e8ac2963fd16cbd109620a68e1931103
:git: "git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git"
SPEC CHECKSUMS:
AlgoliaSearchClient: bbafe7f014cc0b474646d794ae3675ef4e92f0d5
......@@ -256,8 +264,9 @@ SPEC CHECKSUMS:
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: d6c366e73c5ec7d77a2d12a74f84c71e32ac7cc9
PODFILE CHECKSUM: 80784a868562751ad1a0be9fcbc05b256f881ba7
COCOAPODS: 1.10.1
{
"name": "Swarm",
"version": "1.0.7",
"summary": "Swarm is a framework for 1Weather.",
"description": "OneLouder internal pod which creates a Universal iOS framework for WDT Radar.",
"homepage": "https://gitlab.pinsightmedia.com/oneweather/wdt-skywisetilekit-ios",
"license": "Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.",
"authors": "Justin Greenfield and Steve Pint",
"platforms": {
"ios": "10.0"
},
"source": {
"git": "https://gitlab.pinsightmedia.com/oneweather/wdt-skywisetilekit-ios",
"tag": "1.0.7"
},
"source_files": "Swarm/**/*.{h,swift}",
"resources": [
"Swarm/*.json",
"Swarm/Info.plist"
],
"swift_versions": "4.2",
"swift_version": "4.2"
}
......@@ -159,6 +159,7 @@ PODS:
- nanopb/encode (2.30908.0)
- PromisesObjC (1.2.12)
- SnapKit (5.0.1)
- Swarm (1.0.7)
- XMLCoder (0.12.0)
DEPENDENCIES:
......@@ -177,6 +178,7 @@ DEPENDENCIES:
- Localize-Swift
- MoEngage-iOS-SDK
- SnapKit
- "Swarm (from `git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git`, branch `develop`)"
- XMLCoder (~> 0.12.0)
SPEC REPOS:
......@@ -217,11 +219,17 @@ SPEC REPOS:
EXTERNAL SOURCES:
Cirque:
:git: https://github.com/StepanetsDmtry/Cirque.git
Swarm:
:branch: develop
:git: "git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git"
CHECKOUT OPTIONS:
Cirque:
:commit: ceb7ba910a35973cbcd41c73a62be6305aed4d13
:git: https://github.com/StepanetsDmtry/Cirque.git
Swarm:
:commit: 461fe863e8ac2963fd16cbd109620a68e1931103
:git: "git@gitlab.pinsightmedia.com:oneweather/wdt-skywisetilekit-ios.git"
SPEC CHECKSUMS:
AlgoliaSearchClient: bbafe7f014cc0b474646d794ae3675ef4e92f0d5
......@@ -256,8 +264,9 @@ SPEC CHECKSUMS:
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: d6c366e73c5ec7d77a2d12a74f84c71e32ac7cc9
PODFILE CHECKSUM: 80784a868562751ad1a0be9fcbc05b256f881ba7
COCOAPODS: 1.10.1
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>AlgoliaSearchClient.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>AmazonPublisherServicesSDK.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>BezierKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Cirque.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FBAudienceNetwork.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FBSDKCoreKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Firebase.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseABTesting.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseAnalytics.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseCore.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseCoreDiagnostics.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseCrashlytics.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseInstallations.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>FirebaseRemoteConfig.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Flurry-iOS-SDK.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Fyber_Marketplace_SDK.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Google-Mobile-Ads-SDK.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleAppMeasurement.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleDataTransport.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleMobileAdsMediationFacebook.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleMobileAdsMediationFyber.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleMobileAdsMediationMoPub.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleUserMessagingPlatform.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>GoogleUtilities.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Localize-Swift.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Logging.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>MORichNotification.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>MoEngage-iOS-SDK.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Pods-1Weather.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>PromisesObjC.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>SnapKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>XMLCoder.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>mopub-ios-sdk-MoPubResources.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>mopub-ios-sdk.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>nanopb.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict/>
</dict>
</plist>
//
// Swarm.h
// Swarm
//
// Created by Justin Greenfield on 5/4/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for Swarm.
FOUNDATION_EXPORT double SwarmVersionNumber;
//! Project version string for Swarm.
FOUNDATION_EXPORT const unsigned char SwarmVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Swarm/PublicHeader.h>
//
// SwarmAuthentication.swift
// SkywiseTilesDemo
//
// Created by Ross Kimes on 5/25/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
public protocol Authentication {
var baseURL: String { get }
var app_id: String { get }
var app_key: String { get }
func authenticatedURLRequest(forURL urlString: String) -> URLRequest?
}
@objc open class SkywiseAuthentication: NSObject, Authentication {
public init(app_id: String, app_key: String) {
self.app_id = app_id
self.app_key = app_key
super.init()
}
public let app_id: String
public let app_key: String
public let baseURL: String = "https://skywisetiles.wdtinc.com/"
fileprivate let userAgent = Bundle.main.userAgent
open func authenticatedURLRequest(forURL urlString: String) -> URLRequest? {
let fullString = baseURL + urlString
guard let url = URL(string: fullString) else { return nil }
let urlRequest = NSMutableURLRequest(url: url);
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent")
urlRequest.setValue(app_id, forHTTPHeaderField: "app_id")
urlRequest.setValue(app_key, forHTTPHeaderField: "app_key")
return urlRequest as URLRequest
}
}
//
// SwarmDownloadOperation.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/5/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
class SwarmDownloadOperation: Operation {
var task: URLSessionTask? = nil
override var isAsynchronous: Bool { return true }
fileprivate var operationExecuting: Bool = false
override var isExecuting: Bool {
get { return operationExecuting }
set {
if (operationExecuting != newValue) {
willChangeValue(forKey: "isExecuting")
operationExecuting = newValue
didChangeValue(forKey: "isExecuting")
}
}
}
fileprivate var operationFinished: Bool = false
override var isFinished: Bool {
get { return operationFinished }
set {
if operationFinished != newValue {
willChangeValue(forKey: "isFinished")
operationFinished = newValue
didChangeValue(forKey: "isFinished")
}
}
}
fileprivate var started = false
func operationDone() {
isFinished = started
isExecuting = false
}
override func start() {
guard !isCancelled else {
isFinished = true
return
}
isExecuting = true
started = true
main()
}
override func main() {
guard let task = task else { operationDone(); return }
task.resume()
}
override func cancel() {
super.cancel()
task?.cancel()
}
}
//
// SwarmHelpers.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/5/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
import MapKit
internal func onMainQueue(_ block: @escaping (()->Void)) { DispatchQueue.main.async(execute: block) }
extension Bundle {
static var SwarmBundle: Bundle { return Bundle(for: SwarmManager.self) }
}
extension OperationQueue {
class func queue(concurrentCount: Int) -> OperationQueue {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = concurrentCount
return queue
}
}
extension DateFormatter {
class func swarmDateFormatter() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return dateFormatter
}
}
private class TimerActor {
let block : ()->()
init(block: @escaping ()->()) {
self.block = block
}
@objc dynamic func fire(_ timer: Timer) { block() }
}
extension Timer {
class func repeatingBlockTimer(_ timeInterval: TimeInterval, block: @escaping ()->Void) -> Timer {
let actor = TimerActor(block: block)
return Timer.scheduledTimer(timeInterval: timeInterval, target: actor, selector: #selector(TimerActor.fire(_:)), userInfo: nil, repeats: true)
}
}
// MARK: MapKit extensions
extension MKZoomScale {
var zoomLevel: Int {
let realScale = self / UIScreen.main.scale
var z = Int((log(realScale)/log(2.0)+20.0))
z += Int((UIScreen.main.scale - 1.0))
return z;
}
var worldTileWidth: Int {
return Int(pow(Double(2), Double(self.zoomLevel)))
}
}
extension MKMapRect {
var mercatorTileOrigin: CGPoint {
let region = MKCoordinateRegion(self)
let x = (region.center.longitude) * (.pi/180.0)
let y = (region.center.latitude) * (.pi/180.0)
let logy = log(tan(y)+1.0/cos(y))
let px = (1.0 + (x / .pi)) * 0.5
let py = (1.0 - (logy / .pi)) * 0.5
return CGPoint(x: px, y: py)
}
func pathForZoom(_ scale: MKZoomScale, contentScale: CGFloat = 1.0) -> MKTileOverlayPath {
let mercatorPoint = self.mercatorTileOrigin
let tileWidth = scale.worldTileWidth
let worldTileWidth = CGFloat(tileWidth)
let x = Int(mercatorPoint.x * worldTileWidth)
let y = Int(mercatorPoint.y * worldTileWidth)
func adjustX(_ x:Int) -> Int { return (x >= tileWidth) ? x - tileWidth : x }
return MKTileOverlayPath(x: adjustX(x), y: y, z: scale.zoomLevel, contentScaleFactor: contentScale)
}
}
extension MKMapRect {
func tilePaths(_ zoomScale: MKZoomScale, tileSize: CGFloat = 256, contentScale: CGFloat = 1.0) -> [MKTileOverlayPath] {
return tilePaths(zoomScale.zoomLevel, tileSize: tileSize, contentScale: contentScale)
}
func tilePaths(_ zoomLevel: Int, tileSize: CGFloat = 256, contentScale: CGFloat = 1.0) -> [MKTileOverlayPath] {
let exponent = 20 - zoomLevel
let calculatedScale = 1/pow(Double(2), Double(exponent))
let minX = Int((self.minX * calculatedScale) / Double(tileSize))
let maxX = Int((self.maxX * calculatedScale) / Double(tileSize))
let minY = Int((self.minY * calculatedScale) / Double(tileSize))
let maxY = Int((self.maxY * calculatedScale) / Double(tileSize))
let width = Int(pow(Double(2), Double(zoomLevel)))
func adjustX(_ x: Int) -> Int { return (x >= width) ? x - width : x }
var paths: [MKTileOverlayPath] = []
for x in minX ... maxX {
for y in minY ... maxY {
let rect = MKMapRect(
origin: MKMapPoint(
x: (Double(x) * Double(tileSize)) / calculatedScale,
y: (Double(y) * Double(tileSize)) / calculatedScale
),
size: MKMapSize(
width: Double(tileSize) / calculatedScale,
height: Double(tileSize) / calculatedScale
)
)
if rect.intersects(self) {
paths.append(MKTileOverlayPath(x: adjustX(x), y: y, z: zoomLevel, contentScaleFactor: contentScale))
}
}
}
return paths
}
}
extension MKMapRect {
var northeastCoordinate: CLLocationCoordinate2D {
let point = MKMapPoint(x: origin.x + size.width, y: origin.y)
return point.coordinate
}
var southwestCoordinate: CLLocationCoordinate2D {
let point = MKMapPoint(x: origin.x, y: origin.y + size.height)
return point.coordinate
}
var northwestCoordinate: CLLocationCoordinate2D {
let point = MKMapPoint(x: origin.x, y: origin.y)
return point.coordinate
}
var southeastCoordinate: CLLocationCoordinate2D {
let point = MKMapPoint(x: origin.x + size.width, y: origin.y + size.height)
return point.coordinate
}
}
extension MKMapView {
var visibleSize: CGSize {
let nw = visibleMapRect.northwestCoordinate
let se = visibleMapRect.southeastCoordinate
let origin = convert(nw, toPointTo: self)
let pt = convert(se, toPointTo: self)
return CGSize(width: pt.x - origin.x, height: pt.y - origin.y);
}
}
extension MKTileOverlay {
func tilePaths(_ mapRect: MKMapRect, zoomScale: MKZoomScale, contentScale: CGFloat = 1.0) -> [MKTileOverlayPath] {
return mapRect.tilePaths(zoomScale, tileSize: self.tileSize.width, contentScale: contentScale)
}
func tilePaths(_ mapRect: MKMapRect, zoomLevel: Int, contentScale: CGFloat = 1.0) -> [MKTileOverlayPath] {
return mapRect.tilePaths(zoomLevel, tileSize: self.tileSize.width, contentScale: contentScale)
}
}
extension MKTileOverlayRenderer {
func drawDebug(_ path: MKTileOverlayPath, rect: CGRect, color: UIColor = UIColor.red, context: CGContext, note: String = "") {
UIGraphicsPushContext(context)
let string = note //"x:\(path.x) y:\(path.y) z:\(path.z) " + note
let attribs = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: rect.height * 0.1), NSAttributedString.Key.foregroundColor: color]
string.draw(with: rect, options: .usesLineFragmentOrigin, attributes: attribs, context: nil)
context.setStrokeColor(color.cgColor)
context.setLineWidth(rect.height / 256.0)
context.stroke(rect)
UIGraphicsPopContext();
}
}
extension MKTileOverlayPath {
var mapRect: MKMapRect {
let exponent = 20 - z
let calculatedScale = 1/pow(Double(2), Double(exponent))
let tileSize: Double = 256.0
return MKMapRect(
origin: MKMapPoint(
x: (Double(x) * tileSize) / calculatedScale,
y: (Double(y) * tileSize) / calculatedScale
),
size: MKMapSize(
width: tileSize / calculatedScale,
height: tileSize / calculatedScale
)
)
}
}
extension HTTPURLResponse {
var errorValue: NSError {
return NSError(domain: String(describing: SwarmOverlay.self), code: statusCode, userInfo: infoDictionary)
}
var infoDictionary: [String: Any] {
return [NSLocalizedDescriptionKey: HTTPURLResponse.localizedString(forStatusCode: statusCode)]
}
}
extension Bundle {
internal var userAgent: String {
let infoDictionary = self.infoDictionary
let appName = infoDictionary?["CFBundleName"] as? String ?? "MissingBundle"
let appID = infoDictionary?["CFBundleIdentifier"] as? String ?? "MissingBundleID"
let appVersion = infoDictionary?["CFBundleShortVersionString"] as? String ?? "MissingAppVersion"
let swarmPlist = Bundle.SwarmBundle.infoDictionary
let swarmVersion = swarmPlist?["CFBundleShortVersionString"] as? String ?? "MissingSwarmVersion"
let swarmExecutable = swarmPlist?["CFBundleExecutable"] as? String ?? "MissingSwarmExecutable"
let device = UIDevice.current
let agent = String(format:"%@ %@ (%@ %@ - %@, %@, %@ %@)", appName, appVersion, device.systemName , device.systemVersion, device.model, appID, swarmExecutable, swarmVersion)
return agent
}
}
//
// SwarmLoop.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/13/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
import MapKit
open class SwarmAnnotation: NSObject, MKAnnotation {
open dynamic var coordinate: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
let overlay: SwarmOverlay
init(overlay: SwarmOverlay) {
self.overlay = overlay
super.init()
}
}
public extension SwarmOverlay {
var loopAnnotation: SwarmAnnotation { return SwarmAnnotation(overlay: self) }
}
public extension SwarmAnnotation {
func positionOn(_ mapView: MKMapView) {
mapView.removeAnnotation(self)
coordinate = mapView.region.center
mapView.addAnnotation(self)
}
}
open class SwarmLoopView: MKAnnotationView {
var swarmOverlay: SwarmOverlay? { return (annotation as? SwarmAnnotation)?.overlay }
open weak var mapView: MKMapView? = nil
open var swarmImage: UIImage? = nil {
didSet {
alpha = swarmOverlay?.alpha ?? 1.0
imageView.image = swarmImage
}
}
let imageView: UIImageView = UIImageView(frame: CGRect())
// public override init(frame: CGRect) {
// super.init(frame: frame)
// }
public override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: nil, reuseIdentifier: "SwarmLoopView")
backgroundColor = UIColor.clear
imageView.alpha = 1.0
addSubview(imageView)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func draw(_ rect: CGRect) {
if let mapView = mapView {
let visibleRect = mapView.visibleMapRect
let origin = mapView.convert(visibleRect.northwestCoordinate, toPointTo: mapView)
let pt = mapView.convert(visibleRect.southeastCoordinate, toPointTo: mapView)
let size = CGSize(width: pt.x - origin.x, height: pt.y - origin.y);
var frame = CGRect.zero
frame.origin = mapView.convert(origin, to: self)
frame.size = size
// print(size)
imageView.frame = frame
}
if let swarmOverlay = swarmOverlay, swarmOverlay.debug {
if let context = UIGraphicsGetCurrentContext() {
context.setStrokeColor(UIColor.blue.cgColor)
let rect = bounds
context.setLineWidth(2.0)
context.stroke(rect)
context.move(to: CGPoint(x: 0.0, y: 0.0))
context.addLine(to: CGPoint(x: rect.width, y: rect.height))
context.strokePath()
context.move(to: CGPoint(x: 0.0, y: rect.height))
context.addLine(to: CGPoint(x: rect.width, y: 0.0))
context.strokePath()
}
}
}
}
extension MKMapRect {
func clipRect(_ imageRect: MKMapRect, imageSize: CGSize) -> CGRect {
let clipX = CGFloat(((origin.x - imageRect.origin.x) / imageRect.size.width) * Double(imageSize.width))
let clipY = CGFloat(((origin.y - imageRect.origin.y) / imageRect.size.height) * Double(imageSize.height))
let x2 = CGFloat((((origin.x + size.width) - imageRect.origin.x) / imageRect.size.width) * Double(imageSize.width))
let y2 = CGFloat((((origin.y + size.height) - imageRect.origin.y) / imageRect.size.height) * Double(imageSize.height))
return CGRect(x: floor(clipX), y: floor(clipY), width: x2-clipX, height: y2-clipY)
}
static func rectFromCoordinates(_ northeast: CLLocationCoordinate2D, southwest: CLLocationCoordinate2D) -> MKMapRect {
let swTilePoint = MKMapPoint(southwest)
let neTilePoint = MKMapPoint(northeast)
var northeastX = neTilePoint.x
if (swTilePoint.x > neTilePoint.x) {
northeastX = neTilePoint.x + MKMapRect.world.size.width;
}
return MKMapRect(
origin: MKMapPoint(x: swTilePoint.x, y: neTilePoint.y),
size: MKMapSize(width: northeastX - swTilePoint.x, height: swTilePoint.y - neTilePoint.y)
)
}
}
class SwarmCompositor {
let swarmOverlay: SwarmOverlay
let visibleMapRect: MKMapRect
var tileSize: CGFloat = 256.0
init(overlay: SwarmOverlay, mapRect: MKMapRect) {
self.swarmOverlay = overlay
self.visibleMapRect = mapRect
}
func image(_ timestamp: String, paths: [MKTileOverlayPath]) -> UIImage {
let z = paths.reduce(0) { $1.z }
let xpaths = paths.map { $0.x }
let ypaths = paths.map { $0.y }
let west = xpaths.first!
let east = xpaths.last!
let north = ypaths.first!
let south = ypaths.last!
let worldWidth = Int(pow(Double(2), Double(z)))
let tiledImageSize = CGSize(width: CGFloat(Set(xpaths).count) * tileSize, height: CGFloat(Set(ypaths).count) * tileSize)
let tileNortheast = MKTileOverlayPath(x: east, y: north, z: z, contentScaleFactor: 1.0).mapRect.northeastCoordinate
let tileSouthwest = MKTileOverlayPath(x: west, y: south, z: z, contentScaleFactor: 1.0).mapRect.southwestCoordinate
let tiledMapRect = MKMapRect.rectFromCoordinates(tileNortheast, southwest: tileSouthwest)
let clipRect = visibleMapRect.clipRect(tiledMapRect, imageSize: tiledImageSize)
UIGraphicsBeginImageContext(clipRect.size)
defer { UIGraphicsEndImageContext() }
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: -clipRect.origin.x, y: -clipRect.origin.y);
paths.forEach { (path) in
let url = swarmOverlay.urlForTile(path, timestamp: timestamp)
if let image = SwarmManager.sharedManager.cache.object(forKey: url as AnyObject) {
let x = (path.x < west) ? path.x - west + worldWidth : path.x - west
let y = path.y - north
let rect = CGRect(x: CGFloat(x) * tileSize, y: CGFloat(y) * tileSize, width: tileSize, height: tileSize)
image.draw(in: rect)
//CGContextStrokeRect(context, rect)
}
}
let retImage = UIGraphicsGetImageFromCurrentImageContext()
// let data = UIImagePNGRepresentation(retImage)
// try! data?.writeToFile("/Users/justin/Desktop/test.png", options: .AtomicWrite)
return retImage!
}
}
@objc open class SwarmOverlayCoordinator: NSObject {
open var animating: Bool { return overlay.animating }
open var frameDate: Date? { return overlay.frameDate as Date? }
var alpha: CGFloat = 1.0 {
didSet {
overlay.alpha = alpha
loopView?.alpha = alpha
renderer.setNeedsDisplay()
}
}
public let overlay: SwarmOverlay
fileprivate weak var mapView: MKMapView? = nil
public init(overlay: SwarmOverlay, mapView: MKMapView) {
self.overlay = overlay
self.mapView = mapView
super.init()
annotation.positionOn(mapView)
}
deinit {
overlay.stopAnimating()
overlay.stopUpdating()
mapView?.removeAnnotation(annotation)
mapView?.removeOverlay(overlay)
}
lazy open var renderer: SwarmTileOverlayRenderer = {
return SwarmTileOverlayRenderer(overlay: self.overlay)
}()
lazy open var annotation: SwarmAnnotation = { self.overlay.loopAnnotation }()
open fileprivate(set) var loopView: SwarmLoopView? = nil
open func fetchTilesForAnimation(_ mapRect: MKMapRect, readyBlock:@escaping ()->Void) -> Progress {
return overlay.fetchFramesForAnimation(mapRect, readyBlock: readyBlock)
}
open func regionWillChange() {
overlay.pauseForMove()
renderer.hidden = false
if let mapView = mapView {
annotation.positionOn(mapView)
}
}
open func regionDidChange(_ restartAnimation:(()->Void)) {
overlay.unpauseForMove(restartAnimation)
if let mapView = mapView {
annotation.positionOn(mapView)
}
renderer.setNeedsDisplay()
}
open func overlayUpdated(_ restartAnimation:(()->Void)) {
regionWillChange()
regionDidChange(restartAnimation)
}
open func updateImage(_ image:UIImage?) {
loopView?.swarmImage = image
renderer.hidden = (image != nil)
}
open func annotationView(_ frame: CGRect) -> SwarmLoopView {
let view = SwarmLoopView(annotation: annotation, reuseIdentifier: nil)
view.mapView = mapView
view.frame = frame
// view.annotation = annotation
self.loopView = view
return view
}
open func stop() {
overlay.stopUpdating()
overlay.stopAnimating(jumpToLastFrame: true)
updateImage(nil)
}
open func startAnimating(_ onFrameChanged:@escaping ()->Void) {
overlay.startAnimating { [weak self] (image) in
self?.updateImage(image)
onFrameChanged()
}
}
open func stopAnimating() {
overlay.stopAnimating(jumpToLastFrame: true)
updateImage(nil)
}
open func startUpdating(_ now: Bool = false, block:@escaping (Bool, NSError?)->()) {
overlay.startUpdating(now, block: block)
}
open func stopUpdating() { overlay.stopUpdating() }
}
//
// SwarmManager.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/25/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
@objc open class SwarmManager: NSObject {
open var authentication: Authentication!
public static let sharedManager = SwarmManager()
override fileprivate init() {
super.init()
}
let cache = NSCache<AnyObject, AnyObject>()
let session: URLSession = {
let sessionConfig = URLSessionConfiguration.default
sessionConfig.urlCache = URLCache(memoryCapacity: 10*1024*1024, diskCapacity: 15*1024*1025, diskPath: "SwarmCache")
return URLSession.init(configuration: sessionConfig)
}()
internal let userAgent = Bundle.main.userAgent
let groups: [[String:String]] = {
let data = try? Data(contentsOf: URL(fileURLWithPath: Bundle.SwarmBundle.path(forResource: "swarmLayerGroups", ofType: "json")!))
let groups: [[String:String]] = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [[String:String]]
return groups
}()
let mappedAlerts: [String] = {
let data = try? Data(contentsOf: URL(fileURLWithPath: Bundle.SwarmBundle.path(forResource: "swarmAlertTypes", ofType: "json")!))
var alerts: [String] = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String]
return alerts
}()
// SwarmGroup accessors for ObjC
open var layerIdentifiers: [String] { return SwarmBaseLayer.allLayers.map { $0.description } }
open func localizeLayer(_ identifier: String) -> String {
let layer = SwarmBaseLayer(string: identifier)
return SwarmBaseLayer.localize(layer)
}
open func layer(_ identifier: String) -> Int {
return SwarmBaseLayer(string: identifier).rawValue
}
open func layerIdentifier(_ baseLayer: Int) -> String {
return SwarmBaseLayer(rawValue: baseLayer)?.description ?? SwarmBaseLayer.radar.description
}
open var groupIdentifiers: [String] { return SwarmGroup.allGroups.map { $0.description } }
open func localizeGroup(_ identifier: String) -> String {
let group = SwarmGroup(string: identifier)
return SwarmGroup.localize(group)
}
open func group(_ identifier: String) -> Int {
return SwarmGroup(string: identifier).rawValue
}
open func groupIdentifier(_ group: Int) -> String {
return SwarmGroup(rawValue: group)?.description ?? SwarmGroup.none.description
}
open var radarOverlay : SwarmOverlay { return overlayForBaseLayer(.radar) }
open var satelliteOverlay : SwarmOverlay { return overlayForBaseLayer(.satellite) }
open func overlayForBaseLayer(_ layer: SwarmBaseLayer) -> SwarmOverlay {
assertSetup()
return SwarmOverlay(name: layer.description, baseLayer: layer.description, style: "")
}
open func overlayForGroup(_ group: SwarmGroup, baseLayer: SwarmBaseLayer = .radar) -> SwarmOverlay {
assertSetup()
let groupId = group.description
if let matchingGroup = self.groups.filter({ $0["id"] == groupId }).last {
let name = matchingGroup["layerName"].flatMap { String(format: $0, baseLayer.description)} ?? baseLayer.description
return SwarmOverlay(name: name, baseLayer: baseLayer.description, style: matchingGroup["layerStyleDefault"] ?? "")
}
return overlayForBaseLayer(baseLayer)
}
open func overlayForType(_ alertType: String, baseLayer: SwarmBaseLayer) -> SwarmOverlay {
assertSetup()
if mappedAlerts.contains(alertType) {
let name = alertType + "," + baseLayer.description + "," + alertType
let style = "default,default,wapo"
return SwarmOverlay(name: name, baseLayer: baseLayer.description, style: style)
} else {
return overlayForBaseLayer(baseLayer)
}
}
fileprivate var errorTimer: Timer? = nil
fileprivate struct URLError: Error {
let url: URL
let date: Date
}
fileprivate var errors = [URLError]()
internal func cacheError(_ url: URL, error: NSError) {
cache.setObject(error, forKey: url as AnyObject)
let urlError = URLError(url: url, date: Date())
onMainQueue {
self.errors.append(urlError)
guard self.errorTimer == nil else { return }
self.errorTimer = Timer.repeatingBlockTimer(30.0) { [weak self] in
self?.clearErrors()
}
}
}
fileprivate func clearErrors(_ interval: TimeInterval = 45.0) {
let now = Date()
errors
.filter { (now.timeIntervalSince($0.date)) > interval }
.forEach { self.cache.removeObject(forKey: $0.url as AnyObject) }
onMainQueue {
self.errors = self.errors.filter { now.timeIntervalSince($0.date) <= interval }
if self.errors.isEmpty {
self.errorTimer?.invalidate()
self.errorTimer = nil
}
}
}
internal func assertSetup() {
guard authentication != nil else {
fatalError("ERROR: Skywise authentication has not been set. Get your app ID and key at https://skywise.wdtinc.com.")
}
}
}
//
// SwarmProducts.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/5/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import Foundation
@objc public enum SwarmBaseLayer: Int { case radar, satellite, surfaceTemp, dewPoint, humidiy, wind, uvIndex }
extension SwarmBaseLayer: CustomStringConvertible {
public var description: String {
switch self {
case .radar: return "lowaltradarcontours"
case .satellite: return "globalirgrid"
case .surfaceTemp: return "sfctempcontours"
case .dewPoint: return "sfcdwptcontours"
case .humidiy: return "sfcrhcontours"
case .wind: return "sfcwspdcontours"
case .uvIndex: return "uvcontours"
}
}
}
extension SwarmBaseLayer {
public static func localize(_ layer: SwarmBaseLayer) -> String {
return NSLocalizedString(layer.description, tableName: "Swarm", bundle: Bundle.SwarmBundle, value: "", comment: "")
}
public static var allLayers: [SwarmBaseLayer] {
var n = 0
var layers = [SwarmBaseLayer]()
while let layer = SwarmBaseLayer(rawValue: n) {
layers.append(layer)
n += 1
}
return layers
}
var localizedString: String { return SwarmBaseLayer.localize(self) }
init(string: String) {
let layers = SwarmBaseLayer.allLayers
guard let layer = layers.filter({ $0.description == string }).first else {
self = .radar
return
}
self = layer
}
}
@objc public enum SwarmGroup: Int {
case none, stormAndTornadoes, flood, winter, snow, ice, freezing, fog, fire, wind, hurricaneAndTropical, hurricaneTracks
}
extension SwarmGroup: CustomStringConvertible {
public var description: String {
switch self {
case .none: return "none"
case .stormAndTornadoes: return "stormandtornadoes"
case .flood: return "flood"
case .winter: return "winter"
case .snow: return "snow"
case .ice: return "ice"
case .freezing: return "freezing"
case .fog: return "fog"
case .fire: return "fire"
case .wind: return "wind"
case .hurricaneAndTropical: return "hurricaneandtropical"
case .hurricaneTracks: return "hurricanetracks"
}
}
}
extension SwarmGroup {
public static func localize(_ group: SwarmGroup) -> String {
return NSLocalizedString(group.description, tableName: "Swarm", bundle: Bundle.SwarmBundle, value: "", comment: "")
}
public static var allGroups: [SwarmGroup] {
var n = 0
var groups = [SwarmGroup]()
while let group = SwarmGroup(rawValue: n) {
groups.append(group)
n = n + 1
}
return groups
}
var localizedString: String { return SwarmGroup.localize(self) }
init(string: String) {
let groups = SwarmGroup.allGroups
guard let group = groups.filter({ $0.description == string }).first else {
self = .none
return
}
self = group
}
}
//
// SwarmTileOverlayRenderer.swift
// SkywiseTilesDemo
//
// Created by Justin Greenfield on 5/25/16.
// Copyright © 2016 Weather Decision Technologies, Inc. All rights reserved.
//
import MapKit
open class SwarmTileOverlayRenderer: MKTileOverlayRenderer {
open var hidden: Bool = false {
didSet {
guard oldValue != hidden else { return }
setNeedsDisplay()
}
}
fileprivate var swarmOverlay: SwarmOverlay { return self.overlay as! SwarmOverlay }
override open func canDraw(_ mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {
let swarm = swarmOverlay
guard swarm.isTimestampValid else { return false }
swarm.zoomScale = zoomScale
swarm.contentScale = contentScaleFactor
let path = mapRect.pathForZoom(zoomScale, contentScale: contentScaleFactor)
let timeStamp = swarm.currentFrameTimestamp
let url = swarm.urlForTile(path, timestamp: timeStamp)
let object = swarm.manager.cache.object(forKey: url as AnyObject)
if object is UIImage || object is NSError {
return true
}
swarmOverlay.downloadTileAtURL(url) { [weak self] error in
guard error == nil else { return }
self?.setNeedsDisplay(mapRect, zoomScale: zoomScale)
}
return false
}
override open func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard !hidden else { return }
let swarm = swarmOverlay
guard swarm.isTimestampValid else { return }
let path = mapRect.pathForZoom(zoomScale, contentScale: contentScaleFactor)
let timeStamp = swarm.currentFrameTimestamp
let url = swarm.urlForTile(path, timestamp: timeStamp)
let rect = self.rect(for: mapRect)
if let image = swarm.manager.cache.object(forKey: url as AnyObject) as? UIImage {
UIGraphicsPushContext(context);
image.draw(in: rect, blendMode: .normal, alpha: swarm.alpha * alpha)
UIGraphicsPopContext();
} else {
let color = UIColor.gray
context.setFillColor(color.withAlphaComponent(0.1 * alpha).cgColor)
context.fill(rect)
}
if swarmOverlay.debug { drawDebug(path, rect: rect, color: UIColor.blue.withAlphaComponent(0.6), context: context, note: "\(url.path)") }
}
}
["tsw",
"tow",
"eww",
"svw",
"ffw",
"spw",
"evi",
"cdw",
"nuw",
"rhw",
"hmw",
"frw",
"cem",
"lem",
"hfw",
"hiw",
"huw",
"tyw",
"maw",
"bzw",
"isw",
"tiw",
"hsw",
"wsw",
"hww",
"trw",
"srw",
"tsa",
"avw",
"eqw",
"vow",
"faw",
"cfw",
"lsw",
"flw",
"suw",
"ipw",
"lew",
"ehw",
"dsw",
"toa",
"sva",
"ffa",
"tos",
"ffs",
"fay",
"glw",
"fls",
"hys",
"tsy",
"wcw",
"ecw",
"hzw",
"fzw",
"fww",
"hua",
"tya",
"hus",
"tys",
"sby",
"zry",
"upy",
"ipy",
"wwy",
"lby",
"ley",
"wcy",
"hty",
"hyy",
"lsy",
"cfy",
"fly",
"suy",
"bsy",
"sny",
"upw",
"smy",
"msy",
"scy",
"bwy",
"sew",
"fgy",
"mfy",
"lwy",
"wiy",
"duy",
"fry",
"afy",
"mhy",
"zfy",
"zyy",
"asy",
"loy",
"lae",
"ava",
"bza",
"tia",
"tra",
"hia",
"wsa",
"faa",
"cfa",
"lsa",
"fla",
"hwa",
"eha",
"eca",
"wca",
"lea",
"fza",
"fwa",
"cae",
"toe",
"cfs",
"lss",
"sps",
"mas",
"hza",
"hfa",
"sra",
"gla",
"sea",
"upa",
"afw",
"mhw",
"hyo",
"adr",
"fgw",
"fzy",
"hwy",
"isa",
"sbw",
"wwa",
"www",
"zra",
"zrw"]
[{
"id": "none",
"layerName": "%@"
}, {
"id": "stormandtornadoes",
"layerName": "sva,toa,svw,tow,%@,svw,tow",
"layerStyle": "default,,,,,wapo,wapo"
}, {
"id": "flood",
"layerName": "ffa,flw,ffw,%@,flw,ffw",
"layerStyle": "default,,,,wapo,wapo"
}, {
"id": "winter",
"layerName": "bza,wwy,wsa,bzw,wsw,%@,bzw,wsw",
"layerStyle": "default,,,,,,wapo,wapo"
}, {
"id": "snow",
"layerName": "sby,sny,sbw,hsw,%@,sbw,hsw",
"layerStyle": "default,,,,,wapo,wapo"
}, {
"id": "ice",
"layerName": "ipy,zry,isa,ipw,isw,%@,ipw,isw",
"layerStyle": "default,,,,,,wapo,wapo"
}, {
"id": "freezing",
"layerName": "fry,frw,fzw,%@,frw,fzw",
"layerStyle": "default,,,,wapo,wapo"
}, {
"id": "fog",
"layerName": "zfy,fgy,%@",
"layerStyle": "default,default,default"
}, {
"id": "fire",
"layerName": "fwa,fww,%@,fww",
"layerStyle": "default,default,default,wapo"
}, {
"id": "wind",
"layerName": "duy,wiy,hwa,hwy,dsw,hww,%@,dsw,hww",
"layerStyle": "default,,,,,,,wapo,wapo"
}, {
"id": "hurricaneandtropical",
"layerName": "tra,hua,trw,huw,%@,trw,huw",
"layerStyle": "default,,,,,wapo,wapo"
}, {
"id": "hurricanetracks",
"layerName": "%@,tropfcstfan,troppasttrack,tropfcsttrack,tropcurpos,tropinvest"
}]
\ No newline at end of file
......@@ -21,6 +21,7 @@ ${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOAnalytics.framework
${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOMessaging.framework
${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/Swarm/Swarm.framework
${BUILT_PRODUCTS_DIR}/XMLCoder/XMLCoder.framework
${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPubSDK.framework
${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework
......
......@@ -20,6 +20,7 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MOAnalytics.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MOMessaging.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swarm.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/XMLCoder.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoPubSDK.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework
......
......@@ -21,6 +21,7 @@ ${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOAnalytics.framework
${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOMessaging.framework
${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
${BUILT_PRODUCTS_DIR}/Swarm/Swarm.framework
${BUILT_PRODUCTS_DIR}/XMLCoder/XMLCoder.framework
${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPubSDK.framework
${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework
......
......@@ -20,6 +20,7 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MOAnalytics.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MOMessaging.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swarm.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/XMLCoder.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoPubSDK.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework
......
......@@ -197,6 +197,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOMessaging.framework"
install_framework "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Swarm/Swarm.framework"
install_framework "${BUILT_PRODUCTS_DIR}/XMLCoder/XMLCoder.framework"
install_framework "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPubSDK.framework"
install_framework "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework"
......@@ -225,6 +226,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${PODS_ROOT}/MoEngage-iOS-SDK/Frameworks/MOMessaging.framework"
install_framework "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Swarm/Swarm.framework"
install_framework "${BUILT_PRODUCTS_DIR}/XMLCoder/XMLCoder.framework"
install_framework "${BUILT_PRODUCTS_DIR}/mopub-ios-sdk/MoPubSDK.framework"
install_framework "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework"
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Swarm : NSObject
@end
@implementation PodsDummy_Swarm
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "Swarm.h"
FOUNDATION_EXPORT double SwarmVersionNumber;
FOUNDATION_EXPORT const unsigned char SwarmVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Swarm
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Swarm
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module Swarm {
umbrella header "Swarm-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Swarm
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Swarm
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment