diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json index b8236c6..19882d5 100755 --- a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -39,6 +39,11 @@ "idiom" : "iphone", "size" : "60x60", "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/LaunchGate.xcodeproj/project.pbxproj b/LaunchGate.xcodeproj/project.pbxproj index cb94d3e..8000acd 100755 --- a/LaunchGate.xcodeproj/project.pbxproj +++ b/LaunchGate.xcodeproj/project.pbxproj @@ -504,6 +504,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -555,6 +556,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -579,7 +581,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -599,7 +600,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.dtrenz.LaunchGate; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -611,7 +611,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.dtrenz.LaunchGateTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -623,7 +622,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.dtrenz.LaunchGateTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/LaunchGate.xcodeproj/xcshareddata/xcschemes/LaunchGate.xcscheme b/LaunchGate.xcodeproj/xcshareddata/xcschemes/LaunchGate.xcscheme index 8693ead..d2e7eb8 100755 --- a/LaunchGate.xcodeproj/xcshareddata/xcschemes/LaunchGate.xcscheme +++ b/LaunchGate.xcodeproj/xcshareddata/xcschemes/LaunchGate.xcscheme @@ -43,10 +43,10 @@ + BlueprintIdentifier = "C5A878881C6A7A95004599C2" + BuildableName = "Example.app" + BlueprintName = "Example" + ReferencedContainer = "container:Example/Example.xcodeproj"> @@ -62,15 +62,6 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - - - - diff --git a/Podfile b/Podfile index d72d3d1..6ac182d 100755 --- a/Podfile +++ b/Podfile @@ -1,8 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' use_frameworks! -pod 'SwiftLint' - target 'LaunchGateTests' do platform :ios, '8.3' inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index 7323484..566362e 100755 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - Nimble (7.1.3) - - Quick (1.3.1) - - SwiftLint (0.26.0) + - Nimble (7.3.1) + - Quick (1.3.2) + - SwiftLint (0.27.0) DEPENDENCIES: - Nimble @@ -15,10 +15,10 @@ SPEC REPOS: - SwiftLint SPEC CHECKSUMS: - Nimble: 2839b01d1b31f6a6a7777a221f0d91cf52e8e27b - Quick: d17304d58d0d169dd0bd1c6e5c28e3318de32a1a - SwiftLint: f6b83e8d95ee1e91e11932d843af4fdcbf3fc764 + Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae + Quick: 2623cb30d7a7f41ca62f684f679586558f483d46 + SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 -PODFILE CHECKSUM: 80b19f99959846e7855de4619381077e49a4e498 +PODFILE CHECKSUM: c66a903b4dd3d64e68e8dcb4170d38ac84ba0147 COCOAPODS: 1.5.3 diff --git a/Source/AlertConfiguration.swift b/Source/AlertConfiguration.swift index 6528f1a..81717b4 100755 --- a/Source/AlertConfiguration.swift +++ b/Source/AlertConfiguration.swift @@ -7,10 +7,10 @@ import Foundation -public struct AlertConfiguration: Dialogable, Rememberable { +public struct AlertConfiguration: Decodable, Dialogable, Rememberable { - var message = "" - var blocking = false + let message: String + let blocking: Bool init?(message: String, blocking: Bool) { guard !message.isEmpty else { return nil } diff --git a/Source/DefaultParser.swift b/Source/DefaultParser.swift index 610071b..efccd9f 100755 --- a/Source/DefaultParser.swift +++ b/Source/DefaultParser.swift @@ -10,83 +10,14 @@ import Foundation class DefaultParser: LaunchGateParser { - typealias JSON = [String: AnyObject] - - enum Error: LaunchGateError { - case unableToParseConfigurationObject - case unableToParseAlert - case unableToParseOptionalUpdate - case unableToParseRequiredUpdate - - var description: String { - switch self { - case .unableToParseConfigurationObject: - return "Unable to parse the configuration object (\"ios\") from JSON file." - case .unableToParseAlert: - return "Unable to parse the alert configuration from JSON file." - case .unableToParseOptionalUpdate: - return "Unable to parse the optional update configuration from JSON file." - case .unableToParseRequiredUpdate: - return "Unable to parse the required update configuration from JSON file." + func parse(_ jsonData: Data) -> LaunchGateConfiguration? { + do { + let decoder = JSONDecoder() + let result = try decoder.decode(LaunchGateConfiguration.self, from: jsonData) + return result + } catch let error { + print("LaunchGate — Error: \(error)") } + return nil } - } - - func parse(_ jsonData: Data) -> LaunchGateConfiguration? { - do { - let jsonData = try JSONSerialization.jsonObject(with: jsonData, options: []) - guard let json = jsonData as? JSON else { throw Error.unableToParseConfigurationObject } - guard let config = json["ios"] else { throw Error.unableToParseConfigurationObject } - - var alert: AlertConfiguration? - var optionalUpdate: UpdateConfiguration? - var requiredUpdate: UpdateConfiguration? - - if let alertJSON = config["alert"] as? JSON { - alert = try DefaultParser.parseAlert(alertJSON) - } - - if let optionalUpdateJSON = config["optionalUpdate"] as? JSON { - optionalUpdate = try DefaultParser.parseOptionalUpdate(optionalUpdateJSON) - } - - if let requiredUpdateJSON = config["requiredUpdate"] as? JSON { - requiredUpdate = try DefaultParser.parseRequiredUpdate(requiredUpdateJSON) - } - - return LaunchGateConfiguration(alert: alert, optionalUpdate: optionalUpdate, requiredUpdate: requiredUpdate) - } catch let error as DefaultParser.Error { - print("LaunchGate — Error: \(error)") - } catch let error as NSError { - print("LaunchGate — Error: \(error.localizedDescription)") - - if let recoverySuggestion = error.localizedRecoverySuggestion { - print(recoverySuggestion) - } - } - - return nil - } - - private static func parseAlert(_ json: JSON) throws -> AlertConfiguration? { - guard let message = json["message"] as? String else { throw Error.unableToParseAlert } - guard let blocking = json["blocking"] as? Bool else { throw Error.unableToParseAlert } - - return AlertConfiguration(message: message, blocking: blocking) - } - - private static func parseOptionalUpdate(_ json: JSON) throws -> UpdateConfiguration? { - guard let version = json["optionalVersion"] as? String else { throw Error.unableToParseOptionalUpdate } - guard let message = json["message"] as? String else { throw Error.unableToParseOptionalUpdate } - - return UpdateConfiguration(version: version, message: message) - } - - private static func parseRequiredUpdate(_ json: JSON) throws -> UpdateConfiguration? { - guard let version = json["minimumVersion"] as? String else { throw Error.unableToParseRequiredUpdate } - guard let message = json["message"] as? String else { throw Error.unableToParseRequiredUpdate } - - return UpdateConfiguration(version: version, message: message) - } - } diff --git a/Source/DialogManager.swift b/Source/DialogManager.swift index ea68953..048c7cc 100755 --- a/Source/DialogManager.swift +++ b/Source/DialogManager.swift @@ -7,6 +7,7 @@ // import Foundation +import UIKit protocol Dialogable { var message: String { get } @@ -40,7 +41,6 @@ class DialogManager { func displayOptionalUpdateDialog(_ updateConfig: RememberableDialogSubject, updateURL: URL) { let dialog = createAlertController(.optionalUpdate(updateURL: updateURL), message: updateConfig.message) - displayAlertController(dialog) { () -> Void in Memory.remember(updateConfig) } diff --git a/Source/LaunchGate.swift b/Source/LaunchGate.swift index e2337a6..53e0950 100755 --- a/Source/LaunchGate.swift +++ b/Source/LaunchGate.swift @@ -25,6 +25,7 @@ public class LaunchGate { /// Manager object for the various alert dialogs var dialogManager: DialogManager! + //var config: LaunchGateConfiguration! // MARK: - Public API /** @@ -50,7 +51,7 @@ public class LaunchGate { /// Check the configuration file and perform any appropriate action. public func check() { - performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL as URL))) + performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL))) } // MARK: - Internal API @@ -63,7 +64,7 @@ public class LaunchGate { */ func performCheck(_ remoteFileManager: RemoteFileManager) { remoteFileManager.fetchRemoteFile { (jsonData) -> Void in - if let config = self.parser.parse(jsonData) { + if let config: LaunchGateConfiguration = self.parser.parse(jsonData) { self.displayDialogIfNecessary(config, dialogManager: self.dialogManager) } } diff --git a/Source/LaunchGateConfiguration.swift b/Source/LaunchGateConfiguration.swift index 19aa168..77c1ceb 100755 --- a/Source/LaunchGateConfiguration.swift +++ b/Source/LaunchGateConfiguration.swift @@ -9,7 +9,7 @@ import Foundation /// The configuration object that should be created as the result of parsing the remote configuration file. -public struct LaunchGateConfiguration { +public struct LaunchGateConfiguration: Decodable { /// An `AlertConfiguration`, parsed from the configuration file. var alert: AlertConfiguration? @@ -20,4 +20,17 @@ public struct LaunchGateConfiguration { /// A required `UpdateConfiguration`, parsed from the configuration file. var requiredUpdate: UpdateConfiguration? + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: IOSCodingKeys.self) + let iosContainer = try container.nestedContainer(keyedBy: IOSCodingKeys.self, forKey: .ios) + alert = try iosContainer.decodeIfPresent(AlertConfiguration.self, forKey: .alert) + optionalUpdate = try iosContainer.decodeIfPresent(UpdateConfiguration.self, forKey: .optionalUpdate) + requiredUpdate = try iosContainer.decodeIfPresent(UpdateConfiguration.self, forKey: .requiredUpdate) + } + enum IOSCodingKeys: String, CodingKey { + case ios + case alert + case optionalUpdate + case requiredUpdate + } } diff --git a/Source/RemoteFileManager.swift b/Source/RemoteFileManager.swift index 0e4f797..567b51a 100755 --- a/Source/RemoteFileManager.swift +++ b/Source/RemoteFileManager.swift @@ -23,7 +23,7 @@ class RemoteFileManager { func performRemoteFileRequest(_ session: URLSession, url: URL, responseHandler: @escaping (_ data: Data) -> Void) { let task = session.dataTask(with: url) { data, response, error in if let error = error { - print("LaunchGate — Error: \(error.localizedDescription)") + print("LaunchGate — Error2: \(error.localizedDescription)") } guard response != nil else { print("LaunchGate - Error because there is no response") diff --git a/Source/UpdateConfiguration.swift b/Source/UpdateConfiguration.swift index de4088a..5a59f27 100755 --- a/Source/UpdateConfiguration.swift +++ b/Source/UpdateConfiguration.swift @@ -8,10 +8,9 @@ import Foundation -public struct UpdateConfiguration: Dialogable, Rememberable { - - var version = "" - var message = "" +public struct UpdateConfiguration: Decodable, Dialogable, Rememberable { + var version: String + var message: String init?(version: String, message: String) { guard !version.isEmpty else { return nil } @@ -20,6 +19,29 @@ public struct UpdateConfiguration: Dialogable, Rememberable { self.version = version self.message = message } + public init(from decoder: Decoder) throws { + let optionalKeyedContainer = try decoder.container(keyedBy: OptionalCodingKeys.self) + let requiredKeyedContainer = try decoder.container(keyedBy: RequiredCodingKeys.self) + do { + version = try optionalKeyedContainer.decode(String.self, forKey: .version) + message = try optionalKeyedContainer.decode(String.self, forKey: .message) + } catch { + do { + version = try requiredKeyedContainer.decode(String.self, forKey: .version) + message = try requiredKeyedContainer.decode(String.self, forKey: .message) + } catch { + throw error + } + } + } + enum OptionalCodingKeys: String, CodingKey { + case version = "optionalVersion" + case message + } + enum RequiredCodingKeys: String, CodingKey { + case version = "minimumVersion" + case message + } // MARK: Rememberable Protocol Methods