├── .gitignore ├── .swift-version ├── .travis.yml ├── Cartfile.private ├── Cartfile.resolved ├── Example App ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── LICENSE ├── README.md ├── Source ├── Info-iOS.plist ├── Version.swift ├── VersionTracker.swift ├── VersionsTracker.h └── VersionsTracker.swift ├── Tests ├── Info-iOS.plist ├── VersionTests.swift └── VersionTrackerTests.swift ├── VersionsTracker.podspec └── VersionsTracker.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcshareddata └── xcschemes ├── Example App.xcscheme └── VersionsTracker.xcscheme /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | 27 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: objective-c 6 | # cache: cocoapods 7 | # podfile: Example/Podfile 8 | # before_install: 9 | # - gem install cocoapods # Since Travis is not always on latest version 10 | # - pod install --project-directory=Example 11 | script: 12 | - set -o pipefail && xcodebuild test -workspace Example/VersionsTracker.xcworkspace -scheme VersionsTracker-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty 13 | - pod lib lint 14 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" 2 | github "Quick/Quick" 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v7.3.2" 2 | github "Quick/Quick" "v1.3.2" 3 | -------------------------------------------------------------------------------- /Example App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VersionsTracker 4 | // 5 | // Copyright (c) 2016 Martin Stemmle 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | import VersionsTracker 27 | 28 | let iDontMindSingletons = false 29 | 30 | @UIApplicationMain 31 | class AppDelegate: UIResponder, UIApplicationDelegate { 32 | 33 | var window: UIWindow? 34 | 35 | 36 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 37 | // Override point for customization after application launch. 38 | 39 | guard NSClassFromString("XCTest") == nil else { 40 | // skip AppVersionTracker setup to unit tests take care of it 41 | return true 42 | } 43 | 44 | print("☀️ W E L C O M E 🤗") 45 | 46 | if iDontMindSingletons { 47 | // initialize AppVersionTracker once 48 | VersionsTracker.initialize(trackAppVersion: true, trackOSVersion: true) 49 | // access the AppVersionTracker.sharedInstance anywhere you need 50 | printVersionInfo(VersionsTracker.sharedInstance.osVersion, headline: "OS VERSION") 51 | printVersionInfo(VersionsTracker.sharedInstance.appVersion, headline: "APP VERSION (via AppDelegate)") 52 | } 53 | else { 54 | // make sure to update the version history once once during you app's life time 55 | VersionsTracker.updateVersionHistories(trackAppVersion: true, trackOSVersion: true) 56 | // see ViewController.viewDidLoad() for usage of tracked version 57 | } 58 | 59 | return true 60 | } 61 | 62 | func applicationWillResignActive(_ application: UIApplication) { 63 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 64 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 65 | } 66 | 67 | func applicationDidEnterBackground(_ application: UIApplication) { 68 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 69 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 70 | } 71 | 72 | func applicationWillEnterForeground(_ application: UIApplication) { 73 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 74 | } 75 | 76 | func applicationDidBecomeActive(_ application: UIApplication) { 77 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 78 | } 79 | 80 | func applicationWillTerminate(_ application: UIApplication) { 81 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 82 | } 83 | 84 | } 85 | 86 | // MARK: - Helpers 87 | 88 | func printVersionInfo(_ versionTracker: VersionTracker, headline: String) { 89 | print("") 90 | print("") 91 | print(headline) 92 | print([String](repeating: "-", count: headline.count).joined(separator: "")) 93 | print("") 94 | printVersionChange(versionTracker) 95 | print("⌚️Current version is from \(versionTracker.currentVersion.installDate)") 96 | print(" previous version is from \((versionTracker.previousVersion?.installDate.description ?? "- oh, there is no")!)") 97 | printVersionHistory(versionTracker) 98 | } 99 | 100 | func printVersionChange(_ versionTracker: VersionTracker) { 101 | switch versionTracker.changeState { 102 | case .installed: 103 | print("🆕 Congratulations, the app is launched for the very first time") 104 | case .notChanged: 105 | print("🔄 Welcome back, nothing as changed since the last time") 106 | case .updated(let previousVersion): 107 | print("🆙 The app was updated making small changes: \(previousVersion) -> \(versionTracker.currentVersion)") 108 | case .upgraded(let previousVersion): 109 | print("⬆️ Cool, its a new version: \(previousVersion) -> \(versionTracker.currentVersion)") 110 | case .downgraded(let previousVersion): 111 | print("⬇️ Oohu, looks like something is wrong with the current version to make you come back here: \(previousVersion) -> \(versionTracker.currentVersion)") 112 | } 113 | } 114 | 115 | func printVersionHistory(_ versionTracker: VersionTracker) { 116 | let clocks = ["🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛"] 117 | print("") 118 | print("Version history:") 119 | for (index, version) in versionTracker.versionHistory.enumerated() { 120 | print("\(clocks[index % clocks.count]) \(version.installDate) \(version)") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Example App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example App/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example App/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VersionsTracker 4 | // 5 | // Copyright (c) 2016 Martin Stemmle 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | import VersionsTracker 27 | 28 | class ViewController: UIViewController { 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | if !iDontMindSingletons { 34 | let versionsTracker = VersionsTracker() 35 | printVersionInfo(versionsTracker.appVersion, headline: "APP VERSION (via ViewController)") 36 | } 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Martin Stemmle 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VersionsTracker 2 | 3 | [![CI Status](http://img.shields.io/travis/maremmle/VersionsTracker.svg?style=flat)](https://travis-ci.org/Martin Stemmle/VersionsTracker) 4 | [![Version](https://img.shields.io/cocoapods/v/VersionsTracker.svg?style=flat)](http://cocoapods.org/pods/VersionsTracker) 5 | [![License](https://img.shields.io/cocoapods/l/VersionsTracker.svg?style=flat)](http://cocoapods.org/pods/VersionsTracker) 6 | [![Platform](https://img.shields.io/cocoapods/p/VersionsTracker.svg?style=flat)](http://cocoapods.org/pods/VersionsTracker) 7 | 8 | 9 | 10 | **Keeping track of version installation history made easy.** 11 | 12 | 13 | ## Features 14 | 15 | - Track not just marketing version, but also build number and install date 16 | - Track both App and OS version 17 | - Access current version 18 | - Check for version updates and first launch 19 | - No use as Singleton required 20 | - Ability to use custom NSUserDefaults (e.g. for [sharing it with extensions](https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html) ) 21 | 22 | 23 | ## Example 24 | 25 | To run the example project, clone the repo, and run `pod install` from the Example directory first. Play with the Sample app's version and build numbers. 26 | 27 | ## Requirements 28 | - Xcode 7.0+ 29 | - iOS 8.0+ 30 | - [Semantic Versioning](http://semver.org/) 31 | 32 | ## Installation 33 | 34 | ### CocoaPods 35 | 36 | VersionsTracker is available through [CocoaPods](http://cocoapods.org). To install 37 | it, simply add the following line to your `Podfile`: 38 | 39 | ```ruby 40 | pod "VersionsTracker" 41 | ``` 42 | 43 | ### Carthage 44 | 45 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. Add the following line to your `Cartfile`: 46 | 47 | ```ogdl 48 | github "martnst/VersionsTracker" 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### Initialization 54 | 55 | ```swift 56 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 57 | 58 | if iDontMindSingletons { 59 | VersionsTracker.initialize(trackAppVersion: true, trackOSVersion: true) 60 | } 61 | else { 62 | // make sure to update the version history once once during you app's life time 63 | VersionsTracker.updateVersionHistories(trackAppVersion: true, trackOSVersion: true) 64 | } 65 | 66 | return true 67 | } 68 | ``` 69 | 70 | ### Get the current versions with build and install date 71 | 72 | ```swift 73 | let versionsTracker = iDontMindSingletons ? VersionsTracker.sharedInstance : VersionsTracker() 74 | 75 | let curAppVersion : Version = versionsTracker.appVersion.currentVersion 76 | print("Current marketing version is \(curAppVersion.versionString) (Build \(curAppVersion.buildString)) and was first launched \(curAppVersion.installDate)") 77 | ``` 78 | 79 | 80 | ### Get the version history 81 | 82 | ```swift 83 | let versionsTracker = iDontMindSingletons ? VersionsTracker.sharedInstance : VersionsTracker() 84 | 85 | let appVersions: [Version] = versionsTracker.appVersion.versionHistory 86 | let firstOSVerion : Version = versionsTracker.osVersion.versionHistory.first! 87 | print("The very first time the app was launched on iOS \(firstOSVerion.versionString) on \(firstOSVerion.installDate)") 88 | 89 | ``` 90 | 91 | 92 | ### Check version changes easily 93 | 94 | ```swift 95 | let versionsTracker = iDontMindSingletons ? VersionsTracker.sharedInstance : VersionsTracker() 96 | 97 | switch versionsTracker.appVersion.changeState { 98 | case .installed: 99 | // 🎉 Sweet, a new user just installed your app 100 | // ... start tutorial / intro 101 | print("🆕 Congratulations, the app is launched for the very first time") 102 | 103 | case .notChanged: 104 | // 😴 nothing as changed 105 | // ... nothing to do 106 | print("🔄 Welcome back, nothing as changed since the last time") 107 | 108 | case .updated(let previousVersion: Version): 109 | // 🙃 new build of the same version 110 | // ... hopefully it fixed those bugs the QA guy reported 111 | print("🆙 The app was updated making small changes: \(previousVersion) -> \(versionTracker.currentVersion)") 112 | 113 | case .upgraded(let previousVersion): 114 | // 😄 marketing version increased 115 | // ... migrate old app data 116 | print("⬆️ Cool, its a new version: \(previousVersion) -> \(versionTracker.currentVersion)") 117 | 118 | case .downgraded(let previousVersion): 119 | // 😵 marketing version decreased (hopefully we are not on production) 120 | // ... purge app data and start over 121 | print("⬇️ Oohu, looks like something is wrong with the current version to make you come back here: \(previousVersion) -> \(versionTracker.currentVersion)") 122 | } 123 | ``` 124 | 125 | Since the build is also kept track off, it enables detecting updates of it as well. However, the build fraction of a version is treated as an arbitrary string. This is to support any build number, from integer counts to git commit hashes. Therefor there is no way to determine the direction of a build change. 126 | 127 | ### Compare Version 128 | 129 | ```swift 130 | let versionsTracker = iDontMindSingletons ? VersionsTracker.sharedInstance : VersionsTracker() 131 | let curAppVersion : Version = versionsTracker.appVersion.currentVersion 132 | 133 | curAppVersion >= Version("1.2.3") 134 | curAppVersion >= "1.2.3" 135 | Version("1.2.3") < Version("3.2.1") 136 | curAppVersion != Version("1.2.3", buildString: "B19") 137 | ``` 138 | 139 | Versions with the same marketing version but different build are not equal. 140 | 141 | 142 | 143 | 144 | ## Author 145 | 146 | Martin Stemmle, hi@martn.st 147 | 148 | ## License 149 | 150 | VersionsTracker is available under the MIT license. See the LICENSE file for more info. 151 | -------------------------------------------------------------------------------- /Source/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/Version.swift: -------------------------------------------------------------------------------- 1 | // Version.swift 2 | // 3 | // Copyright (c) 2016 Martin Stemmle 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | 24 | import Foundation 25 | 26 | private func parseVersion(_ lhs: Version, rhs: Version) -> Zip2Sequence<[Int], [Int]> { 27 | 28 | let lhs = lhs.versionString.split(separator: ".").map { (String($0) as NSString).integerValue } 29 | let rhs = rhs.versionString.split(separator: ".").map { (String($0) as NSString).integerValue } 30 | let count = max(lhs.count, rhs.count) 31 | return zip( 32 | lhs + Array(repeating: 0, count: count - lhs.count), 33 | rhs + Array(repeating: 0, count: count - rhs.count)) 34 | } 35 | 36 | public func == (lhs: Version, rhs: Version) -> Bool { 37 | 38 | var result: Bool = true 39 | for (l, r) in parseVersion(lhs, rhs: rhs) { 40 | 41 | if l != r { 42 | result = false 43 | } 44 | } 45 | 46 | if result == true { 47 | result = lhs.buildString == rhs.buildString 48 | } 49 | 50 | return result 51 | } 52 | 53 | public func < (lhs: Version, rhs: Version) -> Bool { 54 | 55 | for (l, r) in parseVersion(lhs, rhs: rhs) { 56 | if l < r { 57 | return true 58 | } else if l > r { 59 | return false 60 | } 61 | } 62 | return false 63 | } 64 | 65 | open class Version: ExpressibleByStringLiteral, Comparable { 66 | 67 | internal class var currentAppVersion: Version { 68 | guard let infoDict = Bundle.main.infoDictionary else { 69 | fatalError() 70 | } 71 | return Version(infoDict["CFBundleShortVersionString"] as! String, buildString: infoDict[kCFBundleVersionKey as String] as? String, installDate: nil) 72 | } 73 | 74 | internal class var currentOSVersion : Version { 75 | let systemVersion = ProcessInfo.processInfo.operatingSystemVersion 76 | let systemVersionString = [systemVersion.majorVersion, systemVersion.minorVersion, systemVersion.patchVersion].map({String($0)}).joined(separator: ".") 77 | let systemVersionStringScanner = Scanner(string: ProcessInfo.processInfo.operatingSystemVersionString) 78 | var build: NSString? 79 | systemVersionStringScanner.scanUpTo("(Build", into: nil) 80 | systemVersionStringScanner.scanUpTo(" ", into: nil) 81 | systemVersionStringScanner.scanUpTo(")", into: &build) 82 | return Version(systemVersionString, buildString: build as String?) 83 | } 84 | 85 | public let versionString: String 86 | public let buildString: String 87 | internal(set) public var installDate: Date 88 | 89 | public init(_ versionString: String, buildString build: String? = nil, installDate date: Date? = nil) { 90 | self.versionString = versionString 91 | self.buildString = build ?? "" 92 | self.installDate = date ?? Date() 93 | } 94 | 95 | // MARK: NSUserDefaults serialization 96 | 97 | private static let versionStringKey = "versionString" 98 | private static let buildStringKey = "buildString" 99 | private static let installDateKey = "installDate" 100 | 101 | internal convenience init(dict: NSDictionary) { 102 | self.init(dict[Version.versionStringKey] as! String, 103 | buildString: dict[Version.buildStringKey] as? String, 104 | installDate: dict[Version.installDateKey] as? Date) 105 | } 106 | 107 | internal static func versionFromDictionary(_ dict: NSDictionary?) -> Version? { 108 | if let dictionary = dict { 109 | return Version(dict: dictionary) 110 | } 111 | return nil 112 | } 113 | 114 | internal var asDictionary: NSDictionary { 115 | get { 116 | return [ 117 | Version.versionStringKey : self.versionString, 118 | Version.buildStringKey : self.buildString, 119 | Version.installDateKey : self.installDate 120 | ] 121 | } 122 | } 123 | 124 | 125 | // MARK: StringLiteralConvertible 126 | 127 | public required init(stringLiteral value: String) { 128 | self.versionString = value 129 | self.buildString = "" 130 | self.installDate = Date() 131 | } 132 | 133 | public required init(unicodeScalarLiteral value: String) { 134 | self.versionString = value 135 | self.buildString = "" 136 | self.installDate = Date() 137 | } 138 | 139 | public required init(extendedGraphemeClusterLiteral value: String) { 140 | self.versionString = value 141 | self.buildString = "" 142 | self.installDate = Date() 143 | } 144 | } 145 | 146 | extension Version: CustomStringConvertible { 147 | public var description: String { 148 | return "\(self.versionString) (\(self.buildString))" 149 | } 150 | } 151 | 152 | extension Version { 153 | 154 | /** 155 | The app version state indicates version changes since the last launch of the app. 156 | - Installed: clean install, very first launch 157 | - NotChanged: version not changed 158 | - Update: build string changed, but marketing version stayed the same 159 | - Upgraded: marketing version increased 160 | - Downgraded: markting version decreased 161 | */ 162 | public enum ChangeState { 163 | case installed 164 | case notChanged 165 | case updated(previousVersion: Version) 166 | case upgraded(previousVersion: Version) 167 | case downgraded(previousVersion: Version) 168 | } 169 | 170 | /** 171 | Determines the change state from one version to another. 172 | */ 173 | internal static func changeStateForFromVersion(_ olderVersion: Version?, toVersion newerVersion: Version) -> ChangeState { 174 | guard let olderVersion = olderVersion else { 175 | return .installed 176 | } 177 | 178 | if olderVersion < newerVersion { 179 | return .upgraded(previousVersion: olderVersion) 180 | } 181 | else if olderVersion > newerVersion { 182 | return .downgraded(previousVersion: olderVersion) 183 | } 184 | else if olderVersion != newerVersion { 185 | return .updated(previousVersion: olderVersion) 186 | } 187 | return .notChanged 188 | } 189 | 190 | } 191 | 192 | 193 | extension Version.ChangeState: Equatable { 194 | } 195 | 196 | public func ==(lhs: Version.ChangeState, rhs: Version.ChangeState) -> Bool { 197 | switch (lhs, rhs) { 198 | case (.installed, .installed): 199 | return true 200 | case (.notChanged, .notChanged): 201 | return true 202 | case (let .updated(previousVersionLHS), let .updated(previousVersionRHS)): 203 | return previousVersionLHS == previousVersionRHS 204 | case (let .upgraded(previousVersionLHS), let .upgraded(previousVersionRHS)): 205 | return previousVersionLHS == previousVersionRHS 206 | case (let .downgraded(previousVersionLHS), let .downgraded(previousVersionRHS)): 207 | return previousVersionLHS == previousVersionRHS 208 | default: 209 | return false 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Source/VersionTracker.swift: -------------------------------------------------------------------------------- 1 | // VersionTracker.swift 2 | // 3 | // Copyright (c) 2016 Martin Stemmle 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | 24 | import Foundation 25 | 26 | open class VersionTracker { 27 | 28 | /** 29 | Sorted array of all versions which the user has had installed. New versions are appended at the end of the array. The last element is the current version. 30 | 31 | *Unless using the Singleton, the version history is lazy loaded from NSUserDefaults. If you only need to access the `currentVersion` 32 | or the `previousVersion` use the corresponding properties to save loading the entire history.* 33 | */ 34 | private(set) public lazy var versionHistory: [Version] = self.userDefaults.versionsInScope(self.userDefaultsScope) 35 | 36 | /** 37 | The previous version or `nil` if the user did not updated the app yet. 38 | 39 | *The absence of the previous version does not mean the app is running for the very first time. Therefor check if the* `state` *is set to* `Installed`. 40 | 41 | - returns: The previousVersion version or `nil`. 42 | */ 43 | private(set) public lazy var previousVersion: Version? = { 44 | if self._previousVersion == self.currentVersion { 45 | return nil 46 | } 47 | return self._previousVersion 48 | }() 49 | 50 | /** 51 | The previous version as stored in NSUserDefaults. Might be equal to the currentVersion in which case `previousVersion` should return `nil`. Hence the need for a 2nd property. 52 | */ 53 | private(set) lazy var _previousVersion: Version? = self.userDefaults.previousVersionForKey(self.userDefaultsScope) 54 | 55 | /** 56 | - returns: The current version. 57 | */ 58 | private(set) public lazy var currentVersion: Version = self.userDefaults.lastLaunchedVersionForkey(self.userDefaultsScope)! 59 | 60 | 61 | /** 62 | The app version state indicates version changes since the last launch of the app. 63 | */ 64 | private(set) public lazy var changeState: Version.ChangeState = Version.changeStateForFromVersion(self._previousVersion, toVersion: self.currentVersion) 65 | 66 | 67 | /** 68 | The user defaults to store the version history in. 69 | */ 70 | private let userDefaults: UserDefaults 71 | 72 | /** 73 | A string to build keys for storing version history of a particular verion to track. 74 | */ 75 | private let userDefaultsScope : String 76 | 77 | 78 | /** 79 | Initializes and returns a newly allocated `VersionTracker` instance. 80 | When `VersionTracker.updateVersionHistory()` was called before, all properties will be lazy loaded to keep the memory footprint low. 81 | 82 | - parameter currentVersion: The current version. 83 | - parameter userDefaults: Pass in a NSUserDefaults object for storing and retrieving the version history. Defaults to `NSUserDefaults.standardUserDefaults()`. 84 | - parameter scope: A string to build keys for storing version history of a particular verion to track. 85 | */ 86 | public init(currentVersion: Version, inScope scope: String, userDefaults: UserDefaults? = nil) { 87 | self.userDefaults = userDefaults ?? UserDefaults.standard 88 | self.userDefaultsScope = scope 89 | // when initialize the first instance or the singleton instance, everything loaded in order to update the version history can be used 90 | if let stuff = VersionTracker.updateVersionHistoryOnce( 91 | withVersion: currentVersion, 92 | inScope: userDefaultsScope, 93 | onUserDefaults: self.userDefaults) { 94 | self.versionHistory = stuff.installedVersions 95 | self._previousVersion = stuff.previousVersion 96 | self.currentVersion = stuff.currentVerstion 97 | self.changeState = Version.changeStateForFromVersion(stuff.previousVersion, toVersion: stuff.currentVerstion) 98 | } 99 | // elsewise those properties will be lazy loaded 100 | else { 101 | // check if NSUserDefaults contain an entry for the current version 102 | // otherwise another NSUserDefaults object was used already, which is not supported 103 | if !self.userDefaults.hasLastLaunchedVersionSetInScope(userDefaultsScope) { 104 | fatalError("❗️VersionTracker was already initialized with another NSUserDefaults object before.") 105 | } 106 | } 107 | } 108 | 109 | 110 | fileprivate struct DidUpdateOnceTracking { 111 | fileprivate static var appVersion: Bool = false 112 | static var osVersion: Bool = false 113 | } 114 | 115 | /** 116 | Updates the version history once per session. To do so it loads the version history and creates a new version. This objects will be returned in a tuple. 117 | However, if it was already called befor it will return `nil` as the version history gets updates only once per app session. 118 | */ 119 | internal static func updateVersionHistoryOnce(withVersion newVersion: Version, inScope userDefaultsScope: String, onUserDefaults userDefaults: UserDefaults) -> (installedVersions: [Version], previousVersion: Version?, currentVerstion: Version)? { 120 | 121 | var result : (installedVersions: [Version], previousVersion: Version?, currentVerstion: Version)? 122 | 123 | // FIXME: find a better solution, e.g. storing a map of dispatch_once_t by scopes 124 | if userDefaultsScope == VersionsTracker.appVersionScope { 125 | if !DidUpdateOnceTracking.appVersion { 126 | result = updateVersionHistory(withVersion: newVersion, inScope: userDefaultsScope, onUserDefaults: userDefaults) 127 | DidUpdateOnceTracking.appVersion = true 128 | } 129 | } 130 | else if userDefaultsScope == VersionsTracker.osVersionScope { 131 | if !DidUpdateOnceTracking.osVersion { 132 | result = updateVersionHistory(withVersion: newVersion, inScope: userDefaultsScope, onUserDefaults: userDefaults) 133 | DidUpdateOnceTracking.appVersion = true 134 | } 135 | } 136 | else { 137 | fatalError("unsupported version scope '\(userDefaultsScope)'") 138 | } 139 | 140 | return result 141 | } 142 | 143 | private static func updateVersionHistory(withVersion newVersion: Version, inScope userDefaultsScope: String, onUserDefaults userDefaults: UserDefaults) -> (installedVersions: [Version], previousVersion: Version?, currentVerstion: Version) { 144 | var installedVersions = userDefaults.versionsInScope(userDefaultsScope) 145 | 146 | let currentVersion : Version 147 | if let knownCurrentVersion = installedVersions.filter({$0 == newVersion}).first { 148 | currentVersion = knownCurrentVersion 149 | } else { 150 | newVersion.installDate = Date() 151 | installedVersions.append(newVersion) 152 | userDefaults.setVersions(installedVersions, inScope: userDefaultsScope) 153 | currentVersion = newVersion 154 | } 155 | 156 | userDefaults.setLastLaunchedVersion(currentVersion, inScope: userDefaultsScope) 157 | 158 | return (installedVersions: installedVersions, 159 | previousVersion: userDefaults.previousVersionForKey(userDefaultsScope), 160 | currentVerstion: currentVersion) 161 | } 162 | 163 | } 164 | 165 | private extension UserDefaults { 166 | 167 | static let prefiex = "VersionsTracker" 168 | 169 | /** 170 | key for storing the last launched version in NSUserDefaults: 171 | - if the version stayed the same: holds the current version 172 | - if the version has changed: 173 | - holds the previous version before updateVersionHistoryOnce() 174 | - becomes the current version after updateVersionHistoryOnce() 175 | */ 176 | static let lastLaunchedVersionKey = "lastLaunchedVersion" 177 | 178 | /** 179 | key for storing the antecessor version of the last launched version in NSUserDefaults 180 | - holds the version of the previous launch after updateVersionHistory() 181 | */ 182 | static let previousLaunchedVersionKey = "previousLaunchedVersion" 183 | 184 | /* 185 | key for stroing the entire version history in NSUserDefaults 186 | */ 187 | static let installedVersionsKey = "installedVersions" 188 | 189 | func versionsInScope(_ scope: String) -> [Version] { 190 | let key = buildKeyForProperty(UserDefaults.installedVersionsKey, inScope: scope) 191 | return (self.object(forKey: key) as? [NSDictionary])?.map { Version(dict: $0) } ?? [] 192 | } 193 | 194 | func setVersions(_ versions: [Version], inScope scope: String) { 195 | let key = buildKeyForProperty(UserDefaults.installedVersionsKey, inScope: scope) 196 | self.set(versions.map{ $0.asDictionary }, forKey: key) 197 | } 198 | 199 | func previousVersionForKey(_ key: String) -> Version? { 200 | return versionForKey(key, property: UserDefaults.previousLaunchedVersionKey) 201 | } 202 | 203 | func hasLastLaunchedVersionSetInScope(_ scope: String) -> Bool { 204 | let key = buildKeyForProperty(UserDefaults.lastLaunchedVersionKey, inScope: scope) 205 | return self.dictionary(forKey: key) != nil 206 | } 207 | 208 | func lastLaunchedVersionForkey(_ key: String) -> Version? { 209 | return self.versionForKey(key, property: UserDefaults.lastLaunchedVersionKey) 210 | } 211 | 212 | func versionForKey(_ key: String, property: String) -> Version? { 213 | let versionDict = self.dictionary(forKey: buildKeyForProperty(property, inScope: key)) 214 | return Version.versionFromDictionary(versionDict as NSDictionary?) 215 | } 216 | 217 | func buildKeyForProperty(_ property: String, inScope scope: String) -> String { 218 | return [UserDefaults.prefiex, scope, property].joined(separator: ".") 219 | } 220 | 221 | /** 222 | Updates the last launched version with the given version. 223 | 224 | **It should only be called once per scope with the current version.** 225 | 226 | It will move the current stored last launched version to the previous launched version slot. 227 | This allow retrieving the previous version at any time during the session. 228 | */ 229 | func setLastLaunchedVersion(_ version: Version, inScope scope: String) { 230 | let lastLaunchedKey = buildKeyForProperty(UserDefaults.lastLaunchedVersionKey, inScope: scope) 231 | let prevLaunchedKey = buildKeyForProperty(UserDefaults.previousLaunchedVersionKey, inScope: scope) 232 | let lastLaunchedDictionary = self.dictionary(forKey: lastLaunchedKey) 233 | self.set(lastLaunchedDictionary, forKey: prevLaunchedKey) // move the last version to the previous slot 234 | self.set(version.asDictionary, forKey: lastLaunchedKey) // update last version with the current 235 | } 236 | } 237 | 238 | 239 | 240 | 241 | 242 | // MARK: - Testing helpers - 243 | 244 | internal extension VersionTracker { 245 | 246 | internal static func resetUpdateVersionHistoryOnceToken() { 247 | guard NSClassFromString("XCTest") != nil else { fatalError("this method shall only be called in unit tests") } 248 | DidUpdateOnceTracking.appVersion = false 249 | DidUpdateOnceTracking.osVersion = false 250 | } 251 | } 252 | 253 | internal extension UserDefaults { 254 | 255 | internal func resetInScope(_ scope: String) { 256 | self.removeObject(forKey: buildKeyForProperty(UserDefaults.previousLaunchedVersionKey, inScope: scope)) 257 | self.removeObject(forKey: buildKeyForProperty(UserDefaults.lastLaunchedVersionKey, inScope: scope)) 258 | self.removeObject(forKey: buildKeyForProperty(UserDefaults.installedVersionsKey, inScope: scope)) 259 | } 260 | 261 | } 262 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /Source/VersionsTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // VersionsTracker.h 3 | // VersionsTracker 4 | // 5 | // Created by Justus Kandzi on 09/02/2017. 6 | // 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for VersionsTracker. 12 | FOUNDATION_EXPORT double VersionsTrackerVersionNumber; 13 | 14 | //! Project version string for VersionsTracker. 15 | FOUNDATION_EXPORT const unsigned char VersionsTrackerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/VersionsTracker.swift: -------------------------------------------------------------------------------- 1 | // VersionsTracker.swift 2 | // 3 | // Copyright (c) 2016 Martin Stemmle 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | 24 | import Foundation 25 | 26 | open class VersionsTracker { 27 | 28 | internal static let appVersionScope = "appVersion"; 29 | internal static let osVersionScope = "osVersion"; 30 | 31 | 32 | /** 33 | Shared instance, for those who prefer using `VersionsTracker` as a singleton. 34 | */ 35 | public static var sharedInstance : VersionsTracker { 36 | get { 37 | if _sharedInstance == nil { 38 | fatalError("❗️VersionsTracker.initialize() musted be called befor accessing the singleton") 39 | } 40 | return _sharedInstance! 41 | } 42 | } 43 | 44 | private static var _sharedInstance : VersionsTracker? 45 | 46 | 47 | public lazy var appVersion : VersionTracker = VersionTracker(currentVersion: Version.currentAppVersion, 48 | inScope: VersionsTracker.appVersionScope, 49 | userDefaults: self.userDefaults) 50 | 51 | 52 | public lazy var osVersion : VersionTracker = VersionTracker(currentVersion: Version.currentOSVersion, 53 | inScope: VersionsTracker.osVersionScope, 54 | userDefaults: self.userDefaults) 55 | 56 | /** 57 | The user defaults to store the version history in. 58 | */ 59 | private let userDefaults: UserDefaults 60 | 61 | 62 | 63 | /** 64 | **When using `VersionTracker` as a singleton, this should be called on each app launch.** 65 | 66 | Initializes the singleton causing it to load and update the version history. 67 | 68 | - parameter userDefaults: Pass in a NSUserDefaults object for storing and retrieving the version history. Defaults to `NSUserDefaults.standardUserDefaults()`. 69 | 70 | */ 71 | public static func initialize(trackAppVersion: Bool, trackOSVersion: Bool, withUserDefaults userDefaults: UserDefaults? = nil) { 72 | if _sharedInstance != nil { 73 | fatalError("❗️VersionsTracker.initialize() was already called before and must be called only once.") 74 | } 75 | _sharedInstance = VersionsTracker(trackAppVersion: trackAppVersion, 76 | trackOSVersion: trackOSVersion, 77 | withUserDefaults: userDefaults) 78 | } 79 | 80 | /** 81 | **When NOT using `VersionTracker` this should be called on each app launch.** 82 | 83 | Updates the version history once per app session. 84 | 85 | *It should but does not has to be called befor instantiating any instance. Any new instance will make sure the version history got updated internally.* 86 | *However, calling* `updateVersionHistory()` *after the app is launched is recommended to not lose version updates if no instance gets ever initialized.* 87 | 88 | - parameter userDefaults: Pass in a NSUserDefaults object for storing and retrieving the version history. Defaults to `NSUserDefaults.standardUserDefaults()`. 89 | */ 90 | public static func updateVersionHistories(trackAppVersion: Bool, trackOSVersion: Bool, withUserDefaults userDefaults: UserDefaults? = nil) { 91 | let defaults = userDefaults ?? UserDefaults.standard 92 | if trackAppVersion { 93 | let versionInfo = VersionTracker.updateVersionHistoryOnce( 94 | withVersion: Version.currentAppVersion, 95 | inScope: VersionsTracker.appVersionScope, 96 | onUserDefaults: defaults) 97 | if versionInfo == nil { 98 | print("[VersionsTracker] ⚠️ App version history was already updated") 99 | } 100 | } 101 | if trackOSVersion { 102 | let versionInfo = VersionTracker.updateVersionHistoryOnce( 103 | withVersion: Version.currentOSVersion, 104 | inScope: VersionsTracker.osVersionScope, 105 | onUserDefaults: defaults) 106 | if versionInfo == nil { 107 | print("[VersionsTracker] ⚠️ OS Version history was already updated") 108 | } 109 | } 110 | } 111 | 112 | public init(trackAppVersion: Bool = false, trackOSVersion: Bool = false, withUserDefaults userDefaults: UserDefaults? = nil) { 113 | self.userDefaults = userDefaults ?? UserDefaults.standard 114 | 115 | if (trackAppVersion) { 116 | // trigger version histroy update 117 | _ = self.appVersion.currentVersion 118 | } 119 | 120 | if (trackOSVersion) { 121 | // trigger version histroy update 122 | _ = self.osVersion.currentVersion 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /Tests/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/VersionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionTests.swift 3 | // VersionTracker 4 | // 5 | // Copyright (c) 2016 Martin Stemmle 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | // https://github.com/Quick/Quick 26 | 27 | import Quick 28 | import Nimble 29 | @testable import VersionsTracker 30 | 31 | class VersionTests: QuickSpec { 32 | override func spec() { 33 | describe("Version") { 34 | 35 | if Bundle.main.bundleIdentifier == nil { 36 | print("⚠️ Skipping currentAppVersion tests -> set Host Application for test target to `Example App`") 37 | } 38 | else { 39 | describe("currentAppVersion") { 40 | let infoDict = Bundle.main.infoDictionary! 41 | it("versionString is set to CFBundleShortVersionString from info.plist") { 42 | expect(Version.currentAppVersion.versionString).to(equal(infoDict["CFBundleShortVersionString"] as? String)) 43 | } 44 | it("buildString is set to kCFBundleVersionKey from info.plist") { 45 | expect(Version.currentAppVersion.buildString).to(equal(infoDict[kCFBundleVersionKey as String] as? String)) 46 | } 47 | } 48 | } 49 | 50 | describe("can compare") { 51 | describe("with another Version") { 52 | it("can handle smaller or equal") { 53 | let version = Version("1.2.3") 54 | // positive tests 55 | expect(version).to(beLessThanOrEqualTo(Version("1.2.3"))) 56 | expect(version).to(beLessThan(Version("1.2.3.1"))) 57 | expect(version).to(beLessThan(Version("1.2.3.1.2.3"))) 58 | expect(version).to(beLessThan(Version("1.2.3.1111"))) 59 | expect(version).to(beLessThan(Version("1.2.3.4.5.6.7.8.9"))) 60 | expect(version).to(beLessThan(Version("1.2.33"))) 61 | expect(version).to(beLessThan(Version("1.2.4"))) 62 | expect(version).to(beLessThan(Version("1.22"))) 63 | expect(version).to(beLessThan(Version("1.22.0"))) 64 | expect(version).to(beLessThan(Version("1.222.0"))) 65 | expect(version).to(beLessThan(Version("1.3"))) 66 | expect(version).to(beLessThan(Version("1.3.0"))) 67 | expect(version).to(beLessThan(Version("1.3.1"))) 68 | expect(version).to(beLessThan(Version("1.3.3"))) 69 | expect(version).to(beLessThan(Version("2.0"))) 70 | expect(version).to(beLessThan(Version("2.0.0"))) 71 | expect(version).to(beLessThan(Version("2.2"))) 72 | expect(version).to(beLessThan(Version("2.2.2"))) 73 | // negative tests 74 | expect(version).toNot(beLessThan(Version("1.0"))) 75 | expect(version).toNot(beLessThan(Version("1.1"))) 76 | expect(version).toNot(beLessThan(Version("1.2"))) 77 | expect(version).toNot(beLessThan(Version("1.2.1"))) 78 | expect(version).toNot(beLessThan(Version("1.2.1.2"))) 79 | expect(version).toNot(beLessThan(Version("1.2.1.2.1"))) 80 | expect(version).toNot(beLessThan(Version("1.2.1.222.1"))) 81 | } 82 | 83 | it("treats same semantic versions as equal") { 84 | let version = Version("2.0.0") 85 | expect(version).to(equal(Version("2.0"))) 86 | expect(version).to(equal(Version("2.0.0"))) 87 | expect(version).to(equal(Version("2.0.0.0"))) 88 | expect(version).to(equal(Version("2.00.00.0.0"))) 89 | } 90 | 91 | it("treats diffenrent semantic versions not equal") { 92 | let version = Version("2.0.0") 93 | expect(version).toNot(equal(Version("2.1"))) 94 | expect(version).toNot(equal(Version("2.0.1"))) 95 | expect(version).toNot(equal(Version("2.0.0.1"))) 96 | expect(version).toNot(equal(Version("2.00.00.0.1"))) 97 | } 98 | 99 | it("treats same semantic versions but different builds as not equal") { 100 | let version = Version("2.0.0", buildString: "B0815") 101 | expect(version).to(equal(Version("2.0", buildString: "B0815"))) 102 | expect(version).to(equal(Version("2.0.0", buildString: "B0815"))) 103 | expect(version).to(equal(Version("2.0.0.0", buildString: "B0815"))) 104 | expect(version).toNot(equal(Version("2.00.00.0.0", buildString: "B4711"))) 105 | expect(version).toNot(equal(Version("2.0", buildString: "B4711"))) 106 | expect(version).toNot(equal(Version("2.0.0", buildString: "B4711"))) 107 | expect(version).toNot(equal(Version("2.0.0.0", buildString: "B4711"))) 108 | expect(version).toNot(equal(Version("2.00.00.0.0", buildString: "B4711"))) 109 | } 110 | 111 | it("does not care about installDate when semantic versions and builds are equal") { 112 | let version = Version("2.0.0", buildString: "B0815", installDate: Date()) 113 | let laterDate = Date(timeIntervalSinceNow: 30) 114 | let earlierDate = Date(timeIntervalSinceNow: -30) 115 | expect(version).to(equal(Version("2.0", buildString: "B0815", installDate: earlierDate))) 116 | expect(version).to(equal(Version("2.0.0", buildString: "B0815", installDate: earlierDate))) 117 | expect(version).to(equal(Version("2.0.0.0", buildString: "B0815", installDate: earlierDate))) 118 | expect(version).to(equal(Version("2.00.00.0.0", buildString: "B0815", installDate: earlierDate))) 119 | expect(version).to(equal(Version("2.0", buildString: "B0815", installDate: laterDate))) 120 | expect(version).to(equal(Version("2.0.0", buildString: "B0815", installDate: laterDate))) 121 | expect(version).to(equal(Version("2.0.0.0", buildString: "B0815", installDate: laterDate))) 122 | expect(version).to(equal(Version("2.00.00.0.0", buildString: "B0815", installDate: laterDate))) 123 | } 124 | } 125 | 126 | 127 | describe("with another Strings") { 128 | it("can handle smaller or equal") { 129 | let version = Version("1.2.3") 130 | // positive tests 131 | expect(version).to(beLessThanOrEqualTo("1.2.3")) 132 | expect(version).to(beLessThan("1.2.3.1")) 133 | expect(version).to(beLessThan("1.2.3.1.2.3")) 134 | expect(version).to(beLessThan("1.2.3.1111")) 135 | expect(version).to(beLessThan("1.2.3.4.5.6.7.8.9")) 136 | expect(version).to(beLessThan("1.2.33")) 137 | expect(version).to(beLessThan("1.2.4")) 138 | expect(version).to(beLessThan("1.22")) 139 | expect(version).to(beLessThan("1.22.0")) 140 | expect(version).to(beLessThan("1.222.0")) 141 | expect(version).to(beLessThan("1.3")) 142 | expect(version).to(beLessThan("1.3.0")) 143 | expect(version).to(beLessThan("1.3.1")) 144 | expect(version).to(beLessThan("1.3.3")) 145 | expect(version).to(beLessThan("2.0")) 146 | expect(version).to(beLessThan("2.0.0")) 147 | expect(version).to(beLessThan("2.2")) 148 | expect(version).to(beLessThan("2.2.2")) 149 | // negative tests 150 | expect(version).toNot(beLessThan("1.0")) 151 | expect(version).toNot(beLessThan("1.1")) 152 | expect(version).toNot(beLessThan("1.2")) 153 | expect(version).toNot(beLessThan("1.2.1")) 154 | expect(version).toNot(beLessThan("1.2.1.2")) 155 | expect(version).toNot(beLessThan("1.2.1.2.1")) 156 | expect(version).toNot(beLessThan("1.2.1.222.1")) 157 | } 158 | 159 | it("treats same semantic versions as equal") { 160 | let version = Version("2.0.0") 161 | expect(version).to(equal("2.0")) 162 | expect(version).to(equal("2.0.0")) 163 | expect(version).to(equal("2.0.0.0")) 164 | expect(version).to(equal("2.00.00.0.0")) 165 | } 166 | 167 | it("treats diffenrent semantic versions not equal") { 168 | let version = Version("2.0.0") 169 | expect(version).toNot(equal("2.1")) 170 | expect(version).toNot(equal("2.0.1")) 171 | expect(version).toNot(equal("2.0.0.1")) 172 | expect(version).toNot(equal("2.00.00.0.1")) 173 | } 174 | } 175 | } 176 | 177 | it("can comepare app version strings being smaller or equal") { 178 | expect(Version("1.4.5") <= "1.5.5") == true 179 | expect(Version("1.4.5") <= "1.5.5") == true 180 | expect(Version("1.4.5") <= "1.55.0") == true 181 | expect(Version("1.4.5") <= "1.555.0") == true 182 | expect(Version("1.4.5") <= "1.4.5.4") == true 183 | expect(Version("1.4.5") <= "1.4.5.3.4.6") == true 184 | expect(Version("1.4.5") <= "2.0.0") == true 185 | 186 | expect(Version("1.5.5") <= "1.4.5") == false 187 | expect(Version("1.55.0") <= "1.4.5") == false 188 | expect(Version("1.555.0") <= "1.4.5") == false 189 | expect(Version("1.4.5.4") <= "1.4.5") == false 190 | expect(Version("1.4.5.3.4.6.") <= "1.4.5") == false 191 | expect(Version("2.0.0") <= "1.4.5") == false 192 | } 193 | 194 | 195 | describe("can determine version changes") { 196 | it("detects clean installs") { 197 | expect(Version.changeStateForFromVersion(nil, toVersion: Version("1.0", buildString: "19", installDate: Date()))).to(equal(Version.ChangeState.installed)) 198 | } 199 | it("detects same version as not changed") { 200 | let prevVersion = Version("1.0", buildString: "19", installDate: Date()) 201 | let curVersion = Version("1.0", buildString: "19", installDate: Date()) 202 | expect(Version.changeStateForFromVersion(prevVersion, toVersion: curVersion)).to(equal(Version.ChangeState.notChanged)) 203 | } 204 | it("detects updates") { 205 | let prevVersion = Version("1.0", buildString: "19", installDate: Date()) 206 | let curVersion = Version("1.0", buildString: "20", installDate: Date()) 207 | expect(Version.changeStateForFromVersion(prevVersion, toVersion: curVersion)).to(equal(Version.ChangeState.updated(previousVersion: prevVersion))) 208 | } 209 | it("detects upgrades") { 210 | let prevVersion = Version("1.0", buildString: "19", installDate: Date()) 211 | let curVersion = Version("1.1", buildString: "20", installDate: Date()) 212 | expect(Version.changeStateForFromVersion(prevVersion, toVersion: curVersion)).to(equal(Version.ChangeState.upgraded(previousVersion: prevVersion))) 213 | } 214 | it("detects downgrades") { 215 | let prevVersion = Version("1.1", buildString: "19", installDate: Date()) 216 | let curVersion = Version("1.0", buildString: "19", installDate: Date()) 217 | expect(Version.changeStateForFromVersion(prevVersion, toVersion: curVersion)).to(equal(Version.ChangeState.downgraded(previousVersion: prevVersion))) 218 | } 219 | } 220 | 221 | } 222 | 223 | } 224 | } 225 | 226 | 227 | -------------------------------------------------------------------------------- /Tests/VersionTrackerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionTrackerTests.swift 3 | // VersionsTracker 4 | // 5 | // Copyright (c) 2016 Martin Stemmle 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | // https://github.com/Quick/Quick 26 | 27 | import Quick 28 | import Nimble 29 | @testable import VersionsTracker 30 | 31 | class VersionTrackerTests: QuickSpec { 32 | override func spec() { 33 | describe("VersionTracker") { 34 | context("used with custom NSUserDefaults") { 35 | let scope = "appVersion" 36 | let userDefaults = UserDefaults(suiteName: "AppVersionTrackerTests")! 37 | userDefaults.resetInScope(scope) 38 | 39 | let versions = [ 40 | Version("1.0", buildString: "1"), // install 41 | Version("1.0", buildString: "2"), // update 42 | Version("1.1", buildString: "3"), // upgrade 43 | Version("1.0", buildString: "2") // downgrade 44 | ] 45 | 46 | beforeEach { 47 | VersionTracker.resetUpdateVersionHistoryOnceToken() 48 | } 49 | 50 | it("detects the very first app launch as Installed state") { 51 | let versionTracker = VersionTracker(currentVersion: versions[0], inScope: scope, userDefaults: userDefaults) 52 | expect(versionTracker.changeState).to(equal(Version.ChangeState.installed)); 53 | } 54 | 55 | it("it will notice NotChanged for the second launch") { 56 | let versionTracker = VersionTracker(currentVersion: versions[0], inScope: scope, userDefaults: userDefaults) 57 | expect(versionTracker.changeState).to(equal(Version.ChangeState.notChanged)); 58 | } 59 | 60 | it("it will notice build Updates") { 61 | let versionTracker = VersionTracker(currentVersion: versions[1], inScope: scope, userDefaults: userDefaults) 62 | expect(versionTracker.changeState).to(equal(Version.ChangeState.updated(previousVersion: Version("1.0", buildString: "1")))); 63 | } 64 | 65 | it("it will notice markting version upgrades") { 66 | let versionTracker = VersionTracker(currentVersion: versions[2], inScope: scope, userDefaults: userDefaults) 67 | expect(versionTracker.changeState).to(equal(Version.ChangeState.upgraded(previousVersion: Version("1.0", buildString: "2")))); 68 | } 69 | 70 | it("it will notice version downgrades") { 71 | let versionTracker = VersionTracker(currentVersion: versions[3], inScope: scope, userDefaults: userDefaults) 72 | expect(versionTracker.changeState).to(equal(Version.ChangeState.downgraded(previousVersion: Version("1.1", buildString: "3")))); 73 | } 74 | 75 | it("update version history only once") { 76 | let result1 = VersionTracker.updateVersionHistoryOnce(withVersion: versions[0], inScope: scope, onUserDefaults: userDefaults) 77 | let result2 = VersionTracker.updateVersionHistoryOnce(withVersion: versions[0], inScope: scope, onUserDefaults: userDefaults) 78 | expect(result1).toNot(beNil()) 79 | expect(result2).to(beNil()) // nil indicated the history was already updated 80 | } 81 | 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /VersionsTracker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint VersionsTracker.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "VersionsTracker" 11 | s.version = "0.3.0" 12 | s.summary = "Keeping track of version installation history made easy." 13 | s.description = <<-DESC 14 | VersionsTracker is a Swift Library, which tracks install version history of app and os version. 15 | It includes not just the marketing version, but also build number and install date. 16 | DESC 17 | 18 | s.homepage = "https://github.com/martnst/VersionsTracker" 19 | s.license = 'MIT' 20 | s.author = { "Martin Stemmle" => "hi@martn.st" } 21 | s.source = { :git => "https://github.com/martnst/VersionsTracker.git", :tag => s.version.to_s } 22 | s.social_media_url = 'https://twitter.com/martn_st' 23 | 24 | s.platform = :ios, '8.0' 25 | s.requires_arc = true 26 | 27 | s.source_files = 'Source/*.swift' 28 | 29 | end 30 | -------------------------------------------------------------------------------- /VersionsTracker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5BEB406D1E53C8A10079FDC3 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB40681E53C8A10079FDC3 /* Version.swift */; }; 11 | 5BEB406E1E53C8A10079FDC3 /* VersionsTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BEB40691E53C8A10079FDC3 /* VersionsTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 5BEB406F1E53C8A10079FDC3 /* VersionsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB406A1E53C8A10079FDC3 /* VersionsTracker.swift */; }; 13 | 5BEB40701E53C8A10079FDC3 /* VersionTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB406B1E53C8A10079FDC3 /* VersionTracker.swift */; }; 14 | 5BEB40821E53CACE0079FDC3 /* VersionsTracker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A06669F81E4C856F00B922DC /* VersionsTracker.framework */; }; 15 | 5BEB40881E53CAF80079FDC3 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB40741E53C9DD0079FDC3 /* VersionTests.swift */; }; 16 | 5BEB40891E53CAFA0079FDC3 /* VersionTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB40751E53C9DD0079FDC3 /* VersionTrackerTests.swift */; }; 17 | 5BEB408D1E53CD0B0079FDC3 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BEB408B1E53CD0B0079FDC3 /* Nimble.framework */; }; 18 | 5BEB408E1E53CD0B0079FDC3 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BEB408C1E53CD0B0079FDC3 /* Quick.framework */; }; 19 | 5BEB409F1E53CEE70079FDC3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB409E1E53CEE70079FDC3 /* AppDelegate.swift */; }; 20 | 5BEB40A11E53CEE70079FDC3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEB40A01E53CEE70079FDC3 /* ViewController.swift */; }; 21 | 5BEB40A41E53CEE70079FDC3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BEB40A21E53CEE70079FDC3 /* Main.storyboard */; }; 22 | 5BEB40A61E53CEE70079FDC3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BEB40A51E53CEE70079FDC3 /* Assets.xcassets */; }; 23 | 5BEB40B01E53D0E10079FDC3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEB40AE1E53D0E10079FDC3 /* LaunchScreen.xib */; }; 24 | 5BEB40B71E53D78B0079FDC3 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5BEB408B1E53CD0B0079FDC3 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 25 | 5BEB40B81E53D78B0079FDC3 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5BEB408C1E53CD0B0079FDC3 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 5BEB40831E53CACE0079FDC3 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = A06669EF1E4C856F00B922DC /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = A06669F71E4C856F00B922DC; 34 | remoteInfo = "VersionsTracker-iOS"; 35 | }; 36 | 5BEB40B91E53DA980079FDC3 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = A06669EF1E4C856F00B922DC /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 5BEB409B1E53CEE70079FDC3; 41 | remoteInfo = "Example App"; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | 5BEB40B61E53D77F0079FDC3 /* CopyFiles */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = ""; 50 | dstSubfolderSpec = 10; 51 | files = ( 52 | 5BEB40B71E53D78B0079FDC3 /* Nimble.framework in CopyFiles */, 53 | 5BEB40B81E53D78B0079FDC3 /* Quick.framework in CopyFiles */, 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXCopyFilesBuildPhase section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 5BEB40671E53C8A10079FDC3 /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 61 | 5BEB40681E53C8A10079FDC3 /* Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; 62 | 5BEB40691E53C8A10079FDC3 /* VersionsTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VersionsTracker.h; sourceTree = ""; }; 63 | 5BEB406A1E53C8A10079FDC3 /* VersionsTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionsTracker.swift; sourceTree = ""; }; 64 | 5BEB406B1E53C8A10079FDC3 /* VersionTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionTracker.swift; sourceTree = ""; }; 65 | 5BEB40731E53C9DD0079FDC3 /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Info-iOS.plist"; path = "Tests/Info-iOS.plist"; sourceTree = ""; }; 66 | 5BEB40741E53C9DD0079FDC3 /* VersionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VersionTests.swift; path = Tests/VersionTests.swift; sourceTree = ""; }; 67 | 5BEB40751E53C9DD0079FDC3 /* VersionTrackerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VersionTrackerTests.swift; path = Tests/VersionTrackerTests.swift; sourceTree = ""; }; 68 | 5BEB407D1E53CACE0079FDC3 /* VersionsTracker iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "VersionsTracker iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 5BEB408B1E53CD0B0079FDC3 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = ""; }; 70 | 5BEB408C1E53CD0B0079FDC3 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = ""; }; 71 | 5BEB409C1E53CEE70079FDC3 /* Example App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 5BEB409E1E53CEE70079FDC3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 73 | 5BEB40A01E53CEE70079FDC3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 74 | 5BEB40A31E53CEE70079FDC3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 75 | 5BEB40A51E53CEE70079FDC3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 76 | 5BEB40AA1E53CEE70079FDC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | 5BEB40AF1E53D0E10079FDC3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 78 | A06669F81E4C856F00B922DC /* VersionsTracker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VersionsTracker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 5BEB407A1E53CACE0079FDC3 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | 5BEB40821E53CACE0079FDC3 /* VersionsTracker.framework in Frameworks */, 87 | 5BEB408D1E53CD0B0079FDC3 /* Nimble.framework in Frameworks */, 88 | 5BEB408E1E53CD0B0079FDC3 /* Quick.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | 5BEB40991E53CEE70079FDC3 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | A06669F41E4C856F00B922DC /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXFrameworksBuildPhase section */ 107 | 108 | /* Begin PBXGroup section */ 109 | 5BEB40711E53C8C50079FDC3 /* Supporting Files */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 5BEB40671E53C8A10079FDC3 /* Info-iOS.plist */, 113 | 5BEB40691E53C8A10079FDC3 /* VersionsTracker.h */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | 5BEB40721E53C97B0079FDC3 /* Tests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 5BEB40731E53C9DD0079FDC3 /* Info-iOS.plist */, 122 | 5BEB40741E53C9DD0079FDC3 /* VersionTests.swift */, 123 | 5BEB40751E53C9DD0079FDC3 /* VersionTrackerTests.swift */, 124 | ); 125 | name = Tests; 126 | sourceTree = ""; 127 | }; 128 | 5BEB408A1E53CD0B0079FDC3 /* Frameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 5BEB408B1E53CD0B0079FDC3 /* Nimble.framework */, 132 | 5BEB408C1E53CD0B0079FDC3 /* Quick.framework */, 133 | ); 134 | name = Frameworks; 135 | sourceTree = ""; 136 | }; 137 | 5BEB409D1E53CEE70079FDC3 /* Example App */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 5BEB409E1E53CEE70079FDC3 /* AppDelegate.swift */, 141 | 5BEB40A01E53CEE70079FDC3 /* ViewController.swift */, 142 | 5BEB40A21E53CEE70079FDC3 /* Main.storyboard */, 143 | 5BEB40A51E53CEE70079FDC3 /* Assets.xcassets */, 144 | 5BEB40AE1E53D0E10079FDC3 /* LaunchScreen.xib */, 145 | 5BEB40AA1E53CEE70079FDC3 /* Info.plist */, 146 | ); 147 | path = "Example App"; 148 | sourceTree = ""; 149 | }; 150 | A06669EE1E4C856F00B922DC = { 151 | isa = PBXGroup; 152 | children = ( 153 | 5BEB409D1E53CEE70079FDC3 /* Example App */, 154 | A06669FA1E4C856F00B922DC /* Source */, 155 | 5BEB40721E53C97B0079FDC3 /* Tests */, 156 | A06669F91E4C856F00B922DC /* Products */, 157 | 5BEB408A1E53CD0B0079FDC3 /* Frameworks */, 158 | ); 159 | sourceTree = ""; 160 | }; 161 | A06669F91E4C856F00B922DC /* Products */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | A06669F81E4C856F00B922DC /* VersionsTracker.framework */, 165 | 5BEB407D1E53CACE0079FDC3 /* VersionsTracker iOS Tests.xctest */, 166 | 5BEB409C1E53CEE70079FDC3 /* Example App.app */, 167 | ); 168 | name = Products; 169 | sourceTree = ""; 170 | }; 171 | A06669FA1E4C856F00B922DC /* Source */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 5BEB40681E53C8A10079FDC3 /* Version.swift */, 175 | 5BEB406A1E53C8A10079FDC3 /* VersionsTracker.swift */, 176 | 5BEB406B1E53C8A10079FDC3 /* VersionTracker.swift */, 177 | 5BEB40711E53C8C50079FDC3 /* Supporting Files */, 178 | ); 179 | path = Source; 180 | sourceTree = SOURCE_ROOT; 181 | }; 182 | /* End PBXGroup section */ 183 | 184 | /* Begin PBXHeadersBuildPhase section */ 185 | A06669F51E4C856F00B922DC /* Headers */ = { 186 | isa = PBXHeadersBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | 5BEB406E1E53C8A10079FDC3 /* VersionsTracker.h in Headers */, 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | /* End PBXHeadersBuildPhase section */ 194 | 195 | /* Begin PBXNativeTarget section */ 196 | 5BEB407C1E53CACE0079FDC3 /* VersionsTracker iOS Tests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = 5BEB40851E53CACE0079FDC3 /* Build configuration list for PBXNativeTarget "VersionsTracker iOS Tests" */; 199 | buildPhases = ( 200 | 5BEB40791E53CACE0079FDC3 /* Sources */, 201 | 5BEB407A1E53CACE0079FDC3 /* Frameworks */, 202 | 5BEB407B1E53CACE0079FDC3 /* Resources */, 203 | 5BEB40B61E53D77F0079FDC3 /* CopyFiles */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | 5BEB40841E53CACE0079FDC3 /* PBXTargetDependency */, 209 | 5BEB40BA1E53DA980079FDC3 /* PBXTargetDependency */, 210 | ); 211 | name = "VersionsTracker iOS Tests"; 212 | productName = "VersionsTracker iOS Tests"; 213 | productReference = 5BEB407D1E53CACE0079FDC3 /* VersionsTracker iOS Tests.xctest */; 214 | productType = "com.apple.product-type.bundle.unit-test"; 215 | }; 216 | 5BEB409B1E53CEE70079FDC3 /* Example App */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 5BEB40AB1E53CEE70079FDC3 /* Build configuration list for PBXNativeTarget "Example App" */; 219 | buildPhases = ( 220 | 5BEB40981E53CEE70079FDC3 /* Sources */, 221 | 5BEB40991E53CEE70079FDC3 /* Frameworks */, 222 | 5BEB409A1E53CEE70079FDC3 /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = "Example App"; 229 | productName = "Example App"; 230 | productReference = 5BEB409C1E53CEE70079FDC3 /* Example App.app */; 231 | productType = "com.apple.product-type.application"; 232 | }; 233 | A06669F71E4C856F00B922DC /* VersionsTracker iOS */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = A0666A001E4C856F00B922DC /* Build configuration list for PBXNativeTarget "VersionsTracker iOS" */; 236 | buildPhases = ( 237 | A06669F31E4C856F00B922DC /* Sources */, 238 | A06669F41E4C856F00B922DC /* Frameworks */, 239 | A06669F51E4C856F00B922DC /* Headers */, 240 | A06669F61E4C856F00B922DC /* Resources */, 241 | ); 242 | buildRules = ( 243 | ); 244 | dependencies = ( 245 | ); 246 | name = "VersionsTracker iOS"; 247 | productName = VersionsTracker; 248 | productReference = A06669F81E4C856F00B922DC /* VersionsTracker.framework */; 249 | productType = "com.apple.product-type.framework"; 250 | }; 251 | /* End PBXNativeTarget section */ 252 | 253 | /* Begin PBXProject section */ 254 | A06669EF1E4C856F00B922DC /* Project object */ = { 255 | isa = PBXProject; 256 | attributes = { 257 | LastSwiftUpdateCheck = 0820; 258 | LastUpgradeCheck = 1010; 259 | TargetAttributes = { 260 | 5BEB407C1E53CACE0079FDC3 = { 261 | CreatedOnToolsVersion = 8.2.1; 262 | ProvisioningStyle = Automatic; 263 | }; 264 | 5BEB409B1E53CEE70079FDC3 = { 265 | CreatedOnToolsVersion = 8.2.1; 266 | DevelopmentTeam = 3BA778LRK8; 267 | ProvisioningStyle = Automatic; 268 | }; 269 | A06669F71E4C856F00B922DC = { 270 | CreatedOnToolsVersion = 8.2.1; 271 | LastSwiftMigration = 0820; 272 | ProvisioningStyle = Automatic; 273 | }; 274 | }; 275 | }; 276 | buildConfigurationList = A06669F21E4C856F00B922DC /* Build configuration list for PBXProject "VersionsTracker" */; 277 | compatibilityVersion = "Xcode 3.2"; 278 | developmentRegion = English; 279 | hasScannedForEncodings = 0; 280 | knownRegions = ( 281 | en, 282 | Base, 283 | ); 284 | mainGroup = A06669EE1E4C856F00B922DC; 285 | productRefGroup = A06669F91E4C856F00B922DC /* Products */; 286 | projectDirPath = ""; 287 | projectRoot = ""; 288 | targets = ( 289 | 5BEB409B1E53CEE70079FDC3 /* Example App */, 290 | A06669F71E4C856F00B922DC /* VersionsTracker iOS */, 291 | 5BEB407C1E53CACE0079FDC3 /* VersionsTracker iOS Tests */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | 5BEB407B1E53CACE0079FDC3 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 5BEB409A1E53CEE70079FDC3 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 5BEB40A61E53CEE70079FDC3 /* Assets.xcassets in Resources */, 309 | 5BEB40B01E53D0E10079FDC3 /* LaunchScreen.xib in Resources */, 310 | 5BEB40A41E53CEE70079FDC3 /* Main.storyboard in Resources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | A06669F61E4C856F00B922DC /* Resources */ = { 315 | isa = PBXResourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXResourcesBuildPhase section */ 322 | 323 | /* Begin PBXSourcesBuildPhase section */ 324 | 5BEB40791E53CACE0079FDC3 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | 5BEB40881E53CAF80079FDC3 /* VersionTests.swift in Sources */, 329 | 5BEB40891E53CAFA0079FDC3 /* VersionTrackerTests.swift in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | 5BEB40981E53CEE70079FDC3 /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | 5BEB40A11E53CEE70079FDC3 /* ViewController.swift in Sources */, 338 | 5BEB409F1E53CEE70079FDC3 /* AppDelegate.swift in Sources */, 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | A06669F31E4C856F00B922DC /* Sources */ = { 343 | isa = PBXSourcesBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | 5BEB40701E53C8A10079FDC3 /* VersionTracker.swift in Sources */, 347 | 5BEB406F1E53C8A10079FDC3 /* VersionsTracker.swift in Sources */, 348 | 5BEB406D1E53C8A10079FDC3 /* Version.swift in Sources */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | /* End PBXSourcesBuildPhase section */ 353 | 354 | /* Begin PBXTargetDependency section */ 355 | 5BEB40841E53CACE0079FDC3 /* PBXTargetDependency */ = { 356 | isa = PBXTargetDependency; 357 | target = A06669F71E4C856F00B922DC /* VersionsTracker iOS */; 358 | targetProxy = 5BEB40831E53CACE0079FDC3 /* PBXContainerItemProxy */; 359 | }; 360 | 5BEB40BA1E53DA980079FDC3 /* PBXTargetDependency */ = { 361 | isa = PBXTargetDependency; 362 | target = 5BEB409B1E53CEE70079FDC3 /* Example App */; 363 | targetProxy = 5BEB40B91E53DA980079FDC3 /* PBXContainerItemProxy */; 364 | }; 365 | /* End PBXTargetDependency section */ 366 | 367 | /* Begin PBXVariantGroup section */ 368 | 5BEB40A21E53CEE70079FDC3 /* Main.storyboard */ = { 369 | isa = PBXVariantGroup; 370 | children = ( 371 | 5BEB40A31E53CEE70079FDC3 /* Base */, 372 | ); 373 | name = Main.storyboard; 374 | sourceTree = ""; 375 | }; 376 | 5BEB40AE1E53D0E10079FDC3 /* LaunchScreen.xib */ = { 377 | isa = PBXVariantGroup; 378 | children = ( 379 | 5BEB40AF1E53D0E10079FDC3 /* Base */, 380 | ); 381 | name = LaunchScreen.xib; 382 | sourceTree = ""; 383 | }; 384 | /* End PBXVariantGroup section */ 385 | 386 | /* Begin XCBuildConfiguration section */ 387 | 5BEB40861E53CACE0079FDC3 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | DEVELOPMENT_TEAM = ""; 391 | FRAMEWORK_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "$(PROJECT_DIR)/Carthage/Build/iOS", 394 | ); 395 | INFOPLIST_FILE = "Tests/Info-iOS.plist"; 396 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 397 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 398 | PRODUCT_BUNDLE_IDENTIFIER = "st.martn.VersionsTracker-iOS-Tests"; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | SWIFT_VERSION = 4.2; 401 | }; 402 | name = Debug; 403 | }; 404 | 5BEB40871E53CACE0079FDC3 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | DEVELOPMENT_TEAM = ""; 408 | FRAMEWORK_SEARCH_PATHS = ( 409 | "$(inherited)", 410 | "$(PROJECT_DIR)/Carthage/Build/iOS", 411 | ); 412 | INFOPLIST_FILE = "Tests/Info-iOS.plist"; 413 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 415 | PRODUCT_BUNDLE_IDENTIFIER = "st.martn.VersionsTracker-iOS-Tests"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SWIFT_VERSION = 4.2; 418 | }; 419 | name = Release; 420 | }; 421 | 5BEB40AC1E53CEE70079FDC3 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 425 | DEVELOPMENT_TEAM = 3BA778LRK8; 426 | INFOPLIST_FILE = "Example App/Info.plist"; 427 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = "st.martn.VersionsTracker.Example-App"; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | SWIFT_VERSION = 4.2; 432 | }; 433 | name = Debug; 434 | }; 435 | 5BEB40AD1E53CEE70079FDC3 /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | DEVELOPMENT_TEAM = 3BA778LRK8; 440 | INFOPLIST_FILE = "Example App/Info.plist"; 441 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 442 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 443 | PRODUCT_BUNDLE_IDENTIFIER = "st.martn.VersionsTracker.Example-App"; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | SWIFT_VERSION = 4.2; 446 | }; 447 | name = Release; 448 | }; 449 | A06669FE1E4C856F00B922DC /* Debug */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ALWAYS_SEARCH_USER_PATHS = NO; 453 | CLANG_ANALYZER_NONNULL = YES; 454 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 455 | CLANG_CXX_LIBRARY = "libc++"; 456 | CLANG_ENABLE_MODULES = YES; 457 | CLANG_ENABLE_OBJC_ARC = YES; 458 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 459 | CLANG_WARN_BOOL_CONVERSION = YES; 460 | CLANG_WARN_COMMA = YES; 461 | CLANG_WARN_CONSTANT_CONVERSION = YES; 462 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 463 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 464 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 465 | CLANG_WARN_EMPTY_BODY = YES; 466 | CLANG_WARN_ENUM_CONVERSION = YES; 467 | CLANG_WARN_INFINITE_RECURSION = YES; 468 | CLANG_WARN_INT_CONVERSION = YES; 469 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 470 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 471 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 473 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 474 | CLANG_WARN_STRICT_PROTOTYPES = YES; 475 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 476 | CLANG_WARN_UNREACHABLE_CODE = YES; 477 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | COPY_PHASE_STRIP = NO; 480 | CURRENT_PROJECT_VERSION = 1; 481 | DEBUG_INFORMATION_FORMAT = dwarf; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | ENABLE_TESTABILITY = YES; 484 | GCC_C_LANGUAGE_STANDARD = gnu99; 485 | GCC_DYNAMIC_NO_PIC = NO; 486 | GCC_NO_COMMON_BLOCKS = YES; 487 | GCC_OPTIMIZATION_LEVEL = 0; 488 | GCC_PREPROCESSOR_DEFINITIONS = ( 489 | "DEBUG=1", 490 | "$(inherited)", 491 | ); 492 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 493 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 494 | GCC_WARN_UNDECLARED_SELECTOR = YES; 495 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 496 | GCC_WARN_UNUSED_FUNCTION = YES; 497 | GCC_WARN_UNUSED_VARIABLE = YES; 498 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 499 | MTL_ENABLE_DEBUG_INFO = YES; 500 | ONLY_ACTIVE_ARCH = YES; 501 | SDKROOT = iphoneos; 502 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 503 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 504 | SWIFT_VERSION = 4.2; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | VERSIONING_SYSTEM = "apple-generic"; 507 | VERSION_INFO_PREFIX = ""; 508 | }; 509 | name = Debug; 510 | }; 511 | A06669FF1E4C856F00B922DC /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_SEARCH_USER_PATHS = NO; 515 | CLANG_ANALYZER_NONNULL = YES; 516 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 517 | CLANG_CXX_LIBRARY = "libc++"; 518 | CLANG_ENABLE_MODULES = YES; 519 | CLANG_ENABLE_OBJC_ARC = YES; 520 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 521 | CLANG_WARN_BOOL_CONVERSION = YES; 522 | CLANG_WARN_COMMA = YES; 523 | CLANG_WARN_CONSTANT_CONVERSION = YES; 524 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 525 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 526 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 527 | CLANG_WARN_EMPTY_BODY = YES; 528 | CLANG_WARN_ENUM_CONVERSION = YES; 529 | CLANG_WARN_INFINITE_RECURSION = YES; 530 | CLANG_WARN_INT_CONVERSION = YES; 531 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 532 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 533 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 534 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 535 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 536 | CLANG_WARN_STRICT_PROTOTYPES = YES; 537 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 538 | CLANG_WARN_UNREACHABLE_CODE = YES; 539 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 540 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 541 | COPY_PHASE_STRIP = NO; 542 | CURRENT_PROJECT_VERSION = 1; 543 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 544 | ENABLE_NS_ASSERTIONS = NO; 545 | ENABLE_STRICT_OBJC_MSGSEND = YES; 546 | GCC_C_LANGUAGE_STANDARD = gnu99; 547 | GCC_NO_COMMON_BLOCKS = YES; 548 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 549 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 550 | GCC_WARN_UNDECLARED_SELECTOR = YES; 551 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 552 | GCC_WARN_UNUSED_FUNCTION = YES; 553 | GCC_WARN_UNUSED_VARIABLE = YES; 554 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 555 | MTL_ENABLE_DEBUG_INFO = NO; 556 | SDKROOT = iphoneos; 557 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 558 | SWIFT_VERSION = 4.2; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | VALIDATE_PRODUCT = YES; 561 | VERSIONING_SYSTEM = "apple-generic"; 562 | VERSION_INFO_PREFIX = ""; 563 | }; 564 | name = Release; 565 | }; 566 | A0666A011E4C856F00B922DC /* Debug */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | CLANG_ENABLE_MODULES = YES; 570 | CODE_SIGN_IDENTITY = ""; 571 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 572 | DEFINES_MODULE = YES; 573 | DEVELOPMENT_TEAM = ""; 574 | DYLIB_COMPATIBILITY_VERSION = 1; 575 | DYLIB_CURRENT_VERSION = 1; 576 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 577 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 578 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info-iOS.plist"; 579 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 580 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 581 | PRODUCT_BUNDLE_IDENTIFIER = st.martn.VersionsTracker; 582 | PRODUCT_NAME = VersionsTracker; 583 | SKIP_INSTALL = YES; 584 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 585 | SWIFT_VERSION = 4.2; 586 | }; 587 | name = Debug; 588 | }; 589 | A0666A021E4C856F00B922DC /* Release */ = { 590 | isa = XCBuildConfiguration; 591 | buildSettings = { 592 | CLANG_ENABLE_MODULES = YES; 593 | CODE_SIGN_IDENTITY = ""; 594 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 595 | DEFINES_MODULE = YES; 596 | DEVELOPMENT_TEAM = ""; 597 | DYLIB_COMPATIBILITY_VERSION = 1; 598 | DYLIB_CURRENT_VERSION = 1; 599 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 600 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS"; 601 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info-iOS.plist"; 602 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 603 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 604 | PRODUCT_BUNDLE_IDENTIFIER = st.martn.VersionsTracker; 605 | PRODUCT_NAME = VersionsTracker; 606 | SKIP_INSTALL = YES; 607 | SWIFT_VERSION = 4.2; 608 | }; 609 | name = Release; 610 | }; 611 | /* End XCBuildConfiguration section */ 612 | 613 | /* Begin XCConfigurationList section */ 614 | 5BEB40851E53CACE0079FDC3 /* Build configuration list for PBXNativeTarget "VersionsTracker iOS Tests" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | 5BEB40861E53CACE0079FDC3 /* Debug */, 618 | 5BEB40871E53CACE0079FDC3 /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | 5BEB40AB1E53CEE70079FDC3 /* Build configuration list for PBXNativeTarget "Example App" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | 5BEB40AC1E53CEE70079FDC3 /* Debug */, 627 | 5BEB40AD1E53CEE70079FDC3 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | A06669F21E4C856F00B922DC /* Build configuration list for PBXProject "VersionsTracker" */ = { 633 | isa = XCConfigurationList; 634 | buildConfigurations = ( 635 | A06669FE1E4C856F00B922DC /* Debug */, 636 | A06669FF1E4C856F00B922DC /* Release */, 637 | ); 638 | defaultConfigurationIsVisible = 0; 639 | defaultConfigurationName = Release; 640 | }; 641 | A0666A001E4C856F00B922DC /* Build configuration list for PBXNativeTarget "VersionsTracker iOS" */ = { 642 | isa = XCConfigurationList; 643 | buildConfigurations = ( 644 | A0666A011E4C856F00B922DC /* Debug */, 645 | A0666A021E4C856F00B922DC /* Release */, 646 | ); 647 | defaultConfigurationIsVisible = 0; 648 | defaultConfigurationName = Release; 649 | }; 650 | /* End XCConfigurationList section */ 651 | }; 652 | rootObject = A06669EF1E4C856F00B922DC /* Project object */; 653 | } 654 | -------------------------------------------------------------------------------- /VersionsTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VersionsTracker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VersionsTracker.xcodeproj/xcshareddata/xcschemes/Example App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /VersionsTracker.xcodeproj/xcshareddata/xcschemes/VersionsTracker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | --------------------------------------------------------------------------------