-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7f6bf8f
Showing
7 changed files
with
718 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Created by https://www.gitignore.io/api/xcode | ||
|
||
### macOS ### | ||
*.DS_Store | ||
.AppleDouble | ||
.LSOverride | ||
|
||
# Icon must end with two \r | ||
Icon | ||
|
||
# Thumbnails | ||
._* | ||
|
||
# Files that might appear in the root of a volume | ||
.DocumentRevisions-V100 | ||
.fseventsd | ||
.Spotlight-V100 | ||
.TemporaryItems | ||
.Trashes | ||
.VolumeIcon.icns | ||
.com.apple.timemachine.donotpresent | ||
|
||
# Directories potentially created on remote AFP share | ||
.AppleDB | ||
.AppleDesktop | ||
Network Trash Folder | ||
Temporary Items | ||
.apdisk | ||
|
||
# End of https://www.gitignore.io/api/macos | ||
|
||
### Xcode ### | ||
# Xcode | ||
# | ||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore | ||
|
||
## Build generated | ||
build/ | ||
DerivedData/ | ||
|
||
## Various settings | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata/ | ||
|
||
## Other | ||
*.moved-aside | ||
*.xccheckout | ||
*.xcscmblueprint | ||
|
||
### Xcode Patch ### | ||
*.xcodeproj/* | ||
!*.xcodeproj/project.pbxproj | ||
!*.xcodeproj/xcshareddata/ | ||
!*.xcworkspace/contents.xcworkspacedata | ||
/*.gcno | ||
|
||
# End of https://www.gitignore.io/api/xcode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// | ||
// GoogleReporter.swift | ||
// GoogleReporter | ||
// | ||
// Created by Kristian Andersen on 22/05/2017. | ||
// Copyright © 2017 Kristian Co. All rights reserved. | ||
// | ||
|
||
import UIKit | ||
|
||
extension Dictionary { | ||
func combinedWith(_ other: [Key: Value]) -> [Key: Value] { | ||
var dict = self | ||
for (key, value) in other { | ||
dict[key] = value | ||
} | ||
return dict | ||
} | ||
} | ||
|
||
public class GoogleReporter { | ||
public static let shared = GoogleReporter() | ||
|
||
public var quietMode = true | ||
|
||
private static let baseURL = URL(string: "https://www.google-analytics.com/")! | ||
private static let identifierKey = "co.kristian.GoogleReporter.uniqueUserIdentifier" | ||
|
||
private var trackerId: String? | ||
|
||
private init() {} | ||
|
||
public func configure(withTrackerId trackerId: String) { | ||
self.trackerId = trackerId | ||
} | ||
|
||
public func screenView(_ name: String, parameters: [String: String] = [:]) { | ||
let data = parameters.combinedWith(["cd": name]) | ||
send("screenView", parameters: data) | ||
} | ||
|
||
public func event(_ category: String, action: String, label: String = "", | ||
parameters: [String: String] = [:]) { | ||
let data = parameters.combinedWith([ | ||
"ec": category, | ||
"ea": action, | ||
"el": label | ||
]) | ||
|
||
send("event", parameters: data) | ||
} | ||
|
||
public func exception(_ description: String, isFatal: Bool, | ||
parameters: [String: String] = [:]) { | ||
let data = parameters.combinedWith([ | ||
"exd": description, | ||
"exf": String(isFatal) | ||
]) | ||
|
||
send("exception", parameters: data) | ||
} | ||
|
||
private func send(_ type: String, parameters: [String: String]) { | ||
guard let trackerId = trackerId else { | ||
fatalError("You must set your tracker ID UA-XXXXX-XX with GoogleReporter.configure()") | ||
} | ||
|
||
let queryArguments: [String: String] = [ | ||
"tid": trackerId, | ||
"aid": appIdentifier, | ||
"cid": uniqueUserIdentifier, | ||
"an": appName, | ||
"av": formattedVersion, | ||
"ua": userAgent, | ||
"ul": userLanguage, | ||
"sr": screenResolution, | ||
"v": "1", | ||
"t": type | ||
] | ||
|
||
let arguments = queryArguments.combinedWith(parameters) | ||
let url = GoogleReporter.generateUrl(with: arguments) | ||
|
||
if !quietMode { | ||
print("Sending GA Report: ", url.absoluteString) | ||
} | ||
|
||
let session = URLSession.shared | ||
let task = session.dataTask(with: url) { _, _, error in | ||
if let errorResponse = error?.localizedDescription { | ||
print("Failed to deliver GA Request. ", errorResponse) | ||
} | ||
} | ||
|
||
task.resume() | ||
} | ||
|
||
private static func generateUrl(with parameters: [String: String]) -> URL { | ||
let characterSet = CharacterSet.urlPathAllowed | ||
|
||
let joined = parameters.reduce("collect?") { path, query in | ||
let value = query.value.addingPercentEncoding(withAllowedCharacters: characterSet) | ||
return String(format: "%@%@=%@&", path, query.key, value ?? "") | ||
} | ||
|
||
// Trim the trailing & | ||
let path = joined.substring(to: joined.characters.index(before: joined.endIndex)) | ||
|
||
// Make sure we generated a valid URL | ||
guard let url = URL(string: path, relativeTo: baseURL) else { | ||
fatalError("Failed to generate a valid GA url") | ||
} | ||
|
||
return url | ||
} | ||
|
||
private lazy var uniqueUserIdentifier: String = { | ||
let defaults = UserDefaults.standard | ||
guard let identifier = defaults.string(forKey: GoogleReporter.identifierKey) else { | ||
let identifier = UUID().uuidString | ||
defaults.set(identifier, forKey: GoogleReporter.identifierKey) | ||
|
||
if !self.quietMode { | ||
print("New GA user with identifier: ", identifier) | ||
} | ||
|
||
return identifier | ||
} | ||
|
||
return identifier | ||
}() | ||
|
||
private lazy var userAgent: String = { | ||
let currentDevice = UIDevice.current | ||
let osVersion = currentDevice.systemVersion.replacingOccurrences(of: ".", with: "_") | ||
return "Mozilla/5.0 (\(currentDevice.model); CPU iPhone OS \(osVersion) like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13T534YI" | ||
}() | ||
|
||
private lazy var appName: String = { | ||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String | ||
}() | ||
|
||
private lazy var appIdentifier: String = { | ||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleIdentifier") as! String | ||
}() | ||
|
||
private lazy var appVersion: String = { | ||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String | ||
}() | ||
|
||
private lazy var appBuild: String = { | ||
return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String | ||
}() | ||
|
||
private lazy var formattedVersion: String = { | ||
return "\(self.appVersion) (\(self.appBuild))" | ||
}() | ||
|
||
private lazy var userLanguage: String = { | ||
guard let locale = Locale.preferredLanguages.first, locale.characters.count > 0 else { | ||
return "(not set)" | ||
} | ||
|
||
return locale | ||
}() | ||
|
||
private lazy var screenResolution: String = { | ||
let size = UIScreen.main.bounds.size | ||
return "\(size.width)x\(size.height)" | ||
}() | ||
} |
Oops, something went wrong.