From 1a8dc2fb195dd3419e07a3d2f84186b21b0f39db Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:08:38 -0400 Subject: [PATCH 01/10] Replaced appID with class func on NSBundle called bundleID. App is now able to fetch udpates without the developer explicitly adding an appID --- .../Sample App.xcodeproj/project.pbxproj | 4 +-- Sample App/Sample App/AppDelegate.swift | 4 +-- Siren/Siren.swift | 26 +++++++------------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Sample App/Sample App.xcodeproj/project.pbxproj b/Sample App/Sample App.xcodeproj/project.pbxproj index 09438be9..063d73e0 100755 --- a/Sample App/Sample App.xcodeproj/project.pbxproj +++ b/Sample App/Sample App.xcodeproj/project.pbxproj @@ -419,7 +419,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sample App/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.sabintsev.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.apple.itunesconnect.mobile; PRODUCT_NAME = "Sample App"; }; name = Debug; @@ -432,7 +432,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sample App/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.sabintsev.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.apple.itunesconnect.mobile; PRODUCT_NAME = "Sample App"; }; name = Release; diff --git a/Sample App/Sample App/AppDelegate.swift b/Sample App/Sample App/AppDelegate.swift index dcd50e4d..5d8c5a67 100755 --- a/Sample App/Sample App/AppDelegate.swift +++ b/Sample App/Sample App/AppDelegate.swift @@ -28,8 +28,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let siren = Siren.sharedInstance // Required - siren.appID = "376771144" // For this example, we're using the iTunes Connect App (https://itunes.apple.com/us/app/itunes-connect/id376771144?mt=8) - +// siren.appID = "376771144" // For this example, we're using the iTunes Connect App (https://itunes.apple.com/us/app/itunes-connect/id376771144?mt=8) + // Optional siren.delegate = self diff --git a/Siren/Siren.swift b/Siren/Siren.swift index e5451f44..0addf7f8 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -216,12 +216,6 @@ public final class Siren: NSObject { */ public lazy var revisionUpdateAlertType = SirenAlertType.Option - // Required Vars - /** - The App Store / iTunes Connect ID for your app. - */ - public var appID: String? - // Optional Vars /** The name of your app. @@ -282,11 +276,6 @@ public final class Siren: NSObject { */ public func checkVersion(checkType: SirenVersionCheckType) { - guard let _ = appID else { - printMessage("Please make sure that you have set 'appID' before calling checkVersion.") - return - } - if checkType == .Immediately { performVersionCheck() } else { @@ -368,7 +357,7 @@ public final class Siren: NSObject { return } - if results.isEmpty == false { // Conditional that avoids crash when app not in App Store or appID mistyped + if results.isEmpty == false { // Conditional that avoids crash when app not in App Store currentAppStoreVersion = results[0]["version"] as? String guard let _ = currentAppStoreVersion else { self.postError(.AppStoreVersionArrayFailure, underlyingError: nil) @@ -519,7 +508,7 @@ private extension Siren { components.host = "itunes.apple.com" components.path = "/lookup" - var items: [NSURLQueryItem] = [NSURLQueryItem(name: "id", value: appID)] + var items: [NSURLQueryItem] = [NSURLQueryItem(name: "bundleId", value: NSBundle.bundleID())] if let countryCode = countryCode { let item = NSURLQueryItem(name: "country", value: countryCode) @@ -594,9 +583,9 @@ private extension Siren { } func launchAppStore() { - let iTunesString = "https://itunes.apple.com/app/id\(appID!)" - let iTunesURL = NSURL(string: iTunesString) - UIApplication.sharedApplication().openURL(iTunesURL!) +// let iTunesString = "https://itunes.apple.com/app/id\(appID!)" +// let iTunesURL = NSURL(string: iTunesString) +// UIApplication.sharedApplication().openURL(iTunesURL!) } func printMessage(message: String) { @@ -627,6 +616,11 @@ private extension UIAlertController { // MARK: - NSBundle Extension private extension NSBundle { + + class func bundleID() -> String? { + return NSBundle.mainBundle().bundleIdentifier + } + func currentInstalledVersion() -> String? { return NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as? String } From 6bb2aa9d971eef9440a44ab095f753ebe1ab3b55 Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:13:55 -0400 Subject: [PATCH 02/10] Re-added guard at beginning of checkVersion --- Siren/Siren.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Siren/Siren.swift b/Siren/Siren.swift index 0addf7f8..007e82fe 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -276,6 +276,11 @@ public final class Siren: NSObject { */ public func checkVersion(checkType: SirenVersionCheckType) { + guard let _ = NSBundle.bundleID() else { + printMessage("Please make sure that you have set a `Bundle Identifier` in your project.") + return + } + if checkType == .Immediately { performVersionCheck() } else { From a3248f2f9e28f026fd05cbdec9350059e2b297f3 Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:26:57 -0400 Subject: [PATCH 03/10] Added default/empty implementation of protocol functions --- Siren/Siren.swift | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Siren/Siren.swift b/Siren/Siren.swift index 007e82fe..73582716 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -11,13 +11,13 @@ import UIKit // MARK: - SirenDelegate Protocol -@objc public protocol SirenDelegate { - optional func sirenDidShowUpdateDialog() // User presented with update dialog - optional func sirenUserDidLaunchAppStore() // User did click on button that launched App Store.app - optional func sirenUserDidSkipVersion() // User did click on button that skips version update - optional func sirenUserDidCancel() // User did click on button that cancels update dialog - optional func sirenDidFailVersionCheck(error: NSError) // Siren failed to perform version check (may return system-level error) - optional func sirenDidDetectNewVersionWithoutAlert(message: String) // Siren performed version check and did not display alert +public protocol SirenDelegate: class { + func sirenDidShowUpdateDialog() // User presented with update dialog + func sirenUserDidLaunchAppStore() // User did click on button that launched App Store.app + func sirenUserDidSkipVersion() // User did click on button that skips version update + func sirenUserDidCancel() // User did click on button that cancels update dialog + func sirenDidFailVersionCheck(error: NSError) // Siren failed to perform version check (may return system-level error) + func sirenDidDetectNewVersionWithoutAlert(message: String) // Siren performed version check and did not display alert } @@ -424,12 +424,12 @@ private extension Siren { alertController.addAction(updateAlertAction()) alertController.addAction(skipAlertAction()) case .None: - delegate?.sirenDidDetectNewVersionWithoutAlert?(newVersionMessage) + delegate?.sirenDidDetectNewVersionWithoutAlert(newVersionMessage) } if alertType != .None { alertController.show() - delegate?.sirenDidShowUpdateDialog?() + delegate?.sirenDidShowUpdateDialog() } } @@ -438,7 +438,7 @@ private extension Siren { let action = UIAlertAction(title: title, style: .Default) { (alert: UIAlertAction) in self.hideWindow() self.launchAppStore() - self.delegate?.sirenUserDidLaunchAppStore?() + self.delegate?.sirenUserDidLaunchAppStore() return } @@ -449,7 +449,7 @@ private extension Siren { let title = localizedNextTimeButtonTitle() let action = UIAlertAction(title: title, style: .Default) { (alert: UIAlertAction) in self.hideWindow() - self.delegate?.sirenUserDidCancel?() + self.delegate?.sirenUserDidCancel() return } @@ -464,7 +464,7 @@ private extension Siren { NSUserDefaults.standardUserDefaults().synchronize() } self.hideWindow() - self.delegate?.sirenUserDidSkipVersion?() + self.delegate?.sirenUserDidSkipVersion() return } @@ -601,7 +601,6 @@ private extension Siren { } - // MARK: - UIAlertController Extensions private extension UIAlertController { @@ -685,9 +684,22 @@ private extension Siren { let error = NSError(domain: SirenErrorDomain, code: code.rawValue, userInfo: userInfo) - delegate?.sirenDidFailVersionCheck?(error) + delegate?.sirenDidFailVersionCheck(error) printMessage(error.localizedDescription) } } + +// MARK: - SirenDelegate + +extension SirenDelegate { + + func sirenDidShowUpdateDialog() {} + func sirenUserDidLaunchAppStore() {} + func sirenUserDidSkipVersion() {} + func sirenUserDidCancel() {} + func sirenDidFailVersionCheck(error: NSError) {} + func sirenDidDetectNewVersionWithoutAlert(message: String) {} + +} From b70ce2979b90b72d8cf5151abae989b96c698b4c Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:43:35 -0400 Subject: [PATCH 04/10] Extracted trackId as new, private appID. Hooked it all up --- Siren/Siren.swift | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Siren/Siren.swift b/Siren/Siren.swift index 73582716..b37bf3dd 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -101,6 +101,7 @@ private enum SirenErrorCode: Int { case AppStoreJSONParsingFailure case AppStoreVersionNumberFailure case AppStoreVersionArrayFailure + case AppStoreAppIDFailure } /** @@ -251,6 +252,7 @@ public final class Siren: NSObject { public private(set) var currentAppStoreVersion: String? // Private + private var appID: Int? private var lastVersionCheckPerformedOnDate: NSDate? private var updaterWindow: UIWindow? @@ -346,8 +348,8 @@ public final class Siren: NSObject { task.resume() - } catch _ { - postError(.MalformedURL, underlyingError: nil) + } catch let error as NSError { + postError(.MalformedURL, underlyingError: error) } } @@ -363,6 +365,14 @@ public final class Siren: NSObject { } if results.isEmpty == false { // Conditional that avoids crash when app not in App Store + + guard let appID = results[0]["trackId"] as? Int else { + self.postError(.AppStoreAppIDFailure, underlyingError: nil) + return + } + + self.appID = appID + currentAppStoreVersion = results[0]["version"] as? String guard let _ = currentAppStoreVersion else { self.postError(.AppStoreVersionArrayFailure, underlyingError: nil) @@ -588,9 +598,13 @@ private extension Siren { } func launchAppStore() { -// let iTunesString = "https://itunes.apple.com/app/id\(appID!)" -// let iTunesURL = NSURL(string: iTunesString) -// UIApplication.sharedApplication().openURL(iTunesURL!) + guard let appID = appID else { + return + } + + let iTunesString = "https://itunes.apple.com/app/id\(appID)" + let iTunesURL = NSURL(string: iTunesString) + UIApplication.sharedApplication().openURL(iTunesURL!) } func printMessage(message: String) { @@ -674,6 +688,8 @@ private extension Siren { description = "Error retrieving App Store verson number as there was no data returned." case .AppStoreVersionArrayFailure: description = "Error retrieving App Store verson number as results[0] does not contain a 'version' key." + case .AppStoreAppIDFailure: + description = "Error retrieving trackId as results[0] does not contain a 'trackId' key." } var userInfo: [String: AnyObject] = [NSLocalizedDescriptionKey: description] From 442da19ef1efd93cc655d87c1e4a82d4773b2c0f Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:45:13 -0400 Subject: [PATCH 05/10] Fixed appDelegate in sample project --- Sample App/Sample App/AppDelegate.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sample App/Sample App/AppDelegate.swift b/Sample App/Sample App/AppDelegate.swift index 5d8c5a67..c9a84873 100755 --- a/Sample App/Sample App/AppDelegate.swift +++ b/Sample App/Sample App/AppDelegate.swift @@ -26,9 +26,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func setupSiren() { let siren = Siren.sharedInstance - - // Required -// siren.appID = "376771144" // For this example, we're using the iTunes Connect App (https://itunes.apple.com/us/app/itunes-connect/id376771144?mt=8) // Optional siren.delegate = self @@ -38,7 +35,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Optional - Defaults to .Option // siren.alertType = .Option // or .Force, .Skip, .None - + // Optional - Can set differentiated Alerts for Major, Minor, Patch, and Revision Updates (Must be called AFTER siren.alertType, if you are using siren.alertType) siren.majorUpdateAlertType = .Option siren.minorUpdateAlertType = .Option From 4eea6ce5a35104f699c801231a78e0d173dd18e2 Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Sun, 24 Apr 2016 22:50:56 -0400 Subject: [PATCH 06/10] Updated sirenDidShowUpdateDialog delegate method to return alertType --- Sample App/Sample App/AppDelegate.swift | 4 ++-- Siren/Siren.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sample App/Sample App/AppDelegate.swift b/Sample App/Sample App/AppDelegate.swift index c9a84873..c6da880b 100755 --- a/Sample App/Sample App/AppDelegate.swift +++ b/Sample App/Sample App/AppDelegate.swift @@ -62,8 +62,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { extension AppDelegate: SirenDelegate { - func sirenDidShowUpdateDialog() { - print(#function) + func sirenDidShowUpdateDialog(alertType: SirenAlertType) { + print(#function, alertType) } func sirenUserDidCancel() { diff --git a/Siren/Siren.swift b/Siren/Siren.swift index b37bf3dd..f3bc8f7a 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -12,7 +12,7 @@ import UIKit // MARK: - SirenDelegate Protocol public protocol SirenDelegate: class { - func sirenDidShowUpdateDialog() // User presented with update dialog + func sirenDidShowUpdateDialog(alertType: SirenAlertType) // User presented with update dialog func sirenUserDidLaunchAppStore() // User did click on button that launched App Store.app func sirenUserDidSkipVersion() // User did click on button that skips version update func sirenUserDidCancel() // User did click on button that cancels update dialog @@ -439,7 +439,7 @@ private extension Siren { if alertType != .None { alertController.show() - delegate?.sirenDidShowUpdateDialog() + delegate?.sirenDidShowUpdateDialog(alertType) } } @@ -711,7 +711,7 @@ private extension Siren { extension SirenDelegate { - func sirenDidShowUpdateDialog() {} + func sirenDidShowUpdateDialog(alertType: SirenAlertType) {} func sirenUserDidLaunchAppStore() {} func sirenUserDidSkipVersion() {} func sirenUserDidCancel() {} From 9260bcb93f1f1d9e1ceb732d7a088902a8813064 Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Wed, 27 Apr 2016 02:25:31 -0400 Subject: [PATCH 07/10] Updated Podspec --- Siren.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Siren.podspec b/Siren.podspec index 6a18bbac..d031602c 100755 --- a/Siren.podspec +++ b/Siren.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Siren" - s.version = "0.7.0" + s.version = "0.8.0" s.summary = "Notify users when a new version of your iOS app is available, and prompt them with the App Store link.." s.description = <<-DESC From 2a5d6830fe13bcb55dc9cf48b79cb551f05f677b Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Wed, 27 Apr 2016 02:27:06 -0400 Subject: [PATCH 08/10] Changed scoping to public of default extension definition --- Siren/Siren.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Siren/Siren.swift b/Siren/Siren.swift index f3bc8f7a..1e439369 100755 --- a/Siren/Siren.swift +++ b/Siren/Siren.swift @@ -707,9 +707,10 @@ private extension Siren { } + // MARK: - SirenDelegate -extension SirenDelegate { +public extension SirenDelegate { func sirenDidShowUpdateDialog(alertType: SirenAlertType) {} func sirenUserDidLaunchAppStore() {} From a909585926c8151dd7235bf22d46265738dcd02d Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Wed, 27 Apr 2016 02:46:30 -0400 Subject: [PATCH 09/10] Updated README --- README.md | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5fcef456..406e3378 100755 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ### Notify users when a new version of your app is available, and prompt them with the App Store link. --- + ## About **Siren** checks a user's currently installed version of your iOS app against the version that is currently available in the App Store. @@ -14,10 +15,18 @@ If a new version is available, an alert can be presented to the user informing t - Siren also supports four-number versioning (e.g., 1.0.0.0) - Siren is actively maintained by [**Arthur Sabintsev**](http://github.com/ArtSabintsev) and [**Aaron Brager**](http://twitter.com/getaaron) +## Important Notice (as of v0.8.0) + +### TL;DR +Siren uses your app's bundle identifier to perform a version check. The `appID` param is now used by Siren under the hood. It'll throw an error if you try to set it as it's privately scoped. + +### Long Version +As of v0.8.0, Siren is now able to check if your users are using the current version of your app using the app's bundle identifier, which is accessible through the `NSBundle` API. Instead of removing/deprecating `appID`, it is still storing your app's iTunes Connect `appID`, but it is now determined the `appID` dynamically from the results of the iTunes Lookup API call. Once it's determined, the `appID` is stored within Siren and used to launch the App Store page of your app when needed. + ## Ports -- Siren is a Swift language port of [**Harpy**](http://github.com/ArtSabintsev/Harpy), an Objective-C library that achieves the same functionality. -- Siren and Harpy are maintained by the same developers and have full parity with one another. -- This library was the inspiration for [**Egghead Games' Siren library**](https://github.com/eggheadgames/Siren), which achieves the same functionality with the the Google Play store on the Android platform. +- Siren is a Swift language port of [**Harpy**](http://github.com/ArtSabintsev/Harpy), an Objective-C library that achieves the same functionality. +- Siren and Harpy are maintained by the same developers. Siren is more feature-laden than Harpy as of v0.7.0. +- This library was the inspiration for [**Egghead Games' Siren library**](https://github.com/eggheadgames/Siren), which achieves the same functionality with the the Google Play store on the Android platform. ## Features - [x] CocoaPods Support @@ -75,9 +84,6 @@ func application(application: UIApplication, didFinishLaunchingWithOptions launc // Siren is a singleton let siren = Siren.sharedInstance - // Required: Your app's iTunes App Store ID - siren.appID = <#Your_App_ID#> - // Optional: Defaults to .Option siren.alertType = <#SirenAlertType_Enum_Value#> @@ -151,16 +157,16 @@ If you would like to set a different type of alert for revision, patch, minor, a ``` ## Optional Delegate and Delegate Methods -Six delegate methods allow you to handle or track the user's behavior: +Six delegate methods allow you to handle or track the user's behavior. Each method has a default, empty implementation, effectively making each of these methods optional. ``` swift -@objc protocol SirenDelegate { - optional func sirenDidShowUpdateDialog() // User presented with update dialog - optional func sirenUserDidLaunchAppStore() // User did click on button that launched App Store.app - optional func sirenUserDidSkipVersion() // User did click on button that skips version update - optional func sirenUserDidCancel() // User did click on button that cancels update dialog - optional func sirenDidFailVersionCheck(error: NSError) // Siren failed to perform version check (may return system-level error) - optional func sirenDidDetectNewVersionWithoutAlert(message: String) // Siren performed version check and did not display alert +public protocol SirenDelegate: class { + func sirenDidShowUpdateDialog(alertType: SirenAlertType) // User presented with update dialog + func sirenUserDidLaunchAppStore() // User did click on button that launched App Store.app + func sirenUserDidSkipVersion() // User did click on button that skips version update + func sirenUserDidCancel() // User did click on button that cancels update dialog + func sirenDidFailVersionCheck(error: NSError) // Siren failed to perform version check (may return system-level error) + func sirenDidDetectNewVersionWithoutAlert(message: String) // Siren performed version check and did not display alert } ``` From f735dfb75b3c57b9183b0474834c86412f53a8ea Mon Sep 17 00:00:00 2001 From: Arthur Ariel Sabintsev Date: Wed, 27 Apr 2016 02:48:17 -0400 Subject: [PATCH 10/10] Updated CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b640548b..fdc8bdae 100755 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -17,6 +17,7 @@ - [Vahan Margaryan](https://github.com/VahanMargaryan) for [Pull Request #37](https://github.com/ArtSabintsev/Siren/pull/37) - [Justus Kandzi](https://github.com/jkandzi) for [Pull Request #108 (Harpy)](https://github.com/ArtSabintsev/Harpy/pull/108) - [Maxim-Inv](https://github.com/Maxim-Inv) for [Pull Request #40](https://github.com/ArtSabintsev/Siren/pull/40) +- [Dirk van Oosterbosch](https://github.com/irlabs) for [Pull Request #54](https://github.com/ArtSabintsev/Siren/pull/54) and [Pull Request #55](https://github.com/ArtSabintsev/Siren/pull/55) ### Harpy Project Contributors This repo is a Swift language port of [Harpy](http://github.com/ArtSabintsev/Harpy). We couldn't have built this port without acknowledging the following developers who were instrumental in getting Harpy to v3.2.1, the version of Harpy that Siren was based on.