├── .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 | [](https://travis-ci.org/Martin Stemmle/VersionsTracker)
4 | [](http://cocoapods.org/pods/VersionsTracker)
5 | [](http://cocoapods.org/pods/VersionsTracker)
6 | [](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 |
--------------------------------------------------------------------------------