I'm trying to setup Google analytics for my app but I do not know how to make a distinction between Production and Testing environments. I also do not how to track a specific user. Are these points even possible?
How I'm currently able to measure if ANYONE lands on a screen in ANY environment is through the following code:
let tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIScreenName, value: "LandingViewController")
let builder = GAIDictionaryBuilder.createScreenView()
tracker.send(builder.build() as [NSObject : AnyObject])
What I want to do is:
- post a user id along with the information logged by GA
- post the APIs base URL that the environment is using back to GA
One way to do this is to send the user id and the api base URL as custom dimensions. See https://support.google.com/analytics/answer/2709828?hl=en
tracker.set(GAIFields.customDimensionForIndex(1), value: userId)
tracker.set(GAIFields.customDimensionForIndex(2), value: baseURL)
Another way to distinguish between Production and Testing is to set up an entirely separate tracking id for Testing - as mentioned by Marta
You should use the attribute trackingId from GAI in order to difference environments.
Using analytics.google.com you need to create a project and inside it you must create two properties. One for test environment and another for production. You will use these ids to difference it.
To post the userId:
let eventTracker: NSObject = GAIDictionaryBuilder.createEvent(
withCategory: "SomeCategory",
action: "SomeAction",
label: "someLabel",
value: userId).build()
tracker.send(eventTracker as [NSObject : AnyObject]!)
Hope it helps
Related
I develop an iOS App called Swordy Quest:
https://apps.apple.com/us/app/swordy-quest-an-rpg-adventure/id1446641513
It contains Game Center integration for Leaderboards, Achievements, Player vs Player (PVP) matchmaking and Clans.
I have a local test version that I use when developing (with a test bundleID). I also have a production version of my game that I use to play the game and progress as if I was a customer. However, in order to upgrade/implement the Game Center functionality above, I need to use my production bundleID for testing. This then overwrites my 'customer game' with all my test data (ruining my 'natural' progress).
So I am wondering, is it possible to have a 'clean' production version of an app and still have a separate test version that allows me to test Game Center functionality. Or is there some way to restore a previous app state in Xcode so I could save my production clean version before polluting it with test data? I know in Mac Apps you can change the custom working directory, but I don't think you can in iOS?
I have looked into backing up my Production version of the app before working on Game Center upgrades, but it looks like this is probably not possible? Has anyone come up with a clever way around this?
Please note I have stored both CoreData and UserDefaults in the app.
Custom working directory is something only command-line tool projects. ChangeCurrentDirectoryPath option is no longer available at this place as the screenshot below in XCode 4.6.1. Sounds crazy but you can try downgrade to Xcode 4 and make it happen.
Or you will need load files using Cocoa’s NSBundle class or Core Foundation’s CFBundle functions. So make duplicate target for your Swordy Quest test. It will not affect your clean copy.
Manage schemes:
Finally click the little gear button create a clean copy to avoid touch your production code.
After you set up your keys both product and test where
Build Settings > Packaging ( write to filter Packaging )
Implement as a code below to your logic function ( for example implement in it to a function which trigger a GameHomeVC from LoginPlayerVC )
var key: String?
#if TARGET_PROD || TARGET_STORE
key = #"prodKey";
#else
key = #"testKey";
as a precursor, i'm not familiar with Game Center, so there may be concerns there that i haven't accounted for. so, with that, my instinct in solving this starts out with launch arguments. there is a great article on how to do this here: https://www.swiftbysundell.com/articles/launch-arguments-in-swift/.
Now that you're able to start changing behavior based off of launch arguments from different schemes, you can start to look at how to segment your test / prod data.
As I'm not a CoreData expert, i can't say with 100% confidence that this is possible (or easy), but i would investigate how to setup separate persistent stores based off of a launch argument. using this article as a reference, it seems like you could roughly do something like the below after creating a -testGameCenter launch argument to a new TestGameCenter scheme to create an in-memory data store when testing Game Center
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "YourDataStore")
if CommandLine.arguments.contains("-testGameCenter") {
let description = NSPersistentStoreDescription()
description.url = URL(fileURLWithPath: "/dev/null")
container.persistentStoreDescriptions = [description]
}
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
fatalError("Failed to load stores: \(error), \(error.userInfo)")
}
})
return container
}()
if you're able to solve the CoreData problem above, it's time to start looking at how to segment your UserDefaults data. this gross but easy solution that immediately comes to mind is prefixing your UserDefault keys with test when running from your test scheme. below is an example of how could structure a wrapper around UserDefaults to manage this
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
let keyPrefix: String
init(userDefaults: UserDefaults, keyPrefix: String) {
self.userDefaults = userDefaults
self.keyPrefix = keyPrefix
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: prefixedKey(forKey: key))
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: prefixedKey(forKey: key))
}
func prefixedKey(forKey key: String) -> String {
return "\(keyPrefix)\(key)}"
}
}
where you could make use of the wrapper like so
let userDefaultsPrefix = CommandLine.arguments.contains("-testGameCenter") ? "testGameCenter_" : ""
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: .standard, keyPrefix: userDefaultsPrefix)
to get something more elegant, you could look a little more into UserDefaults to see if you could apply a solution similar to the one for CoreData where there are two entirely separate stores. from a quick glance at this initializer, maybe you could do something as simple as this with your wrapper instead
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: key)
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: key)
}
}
where you construct it like so
let userDefaultsSuiteName: String? = CommandLine.arguments.contains("-testGameCenter") ? myTestingGameCenterSuiteName : nil
let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: userDefaults)
lastly, from a comment you made on another reply, it sounds like you are also concerned with fresh install scenarios. that said, the approaches i've outlined will not help (at least i don't think) with persisting data across deletes/installs. but, what i think you should think about is if it's necessary to test those delete/install concerns from your production bundle id. could you instead either manually test those concerns from your test bundle id and/or write unit tests around the components that involve those concerns? when you are approaching your testing strategy, it's important to make sure that you're testing the right things at the right layers; testing the wrong things at the wrong layers makes each testing layer much, much harder to execute
Targets is designed to do just that. You set pre-processor macros values to get the compiler to compile specific code based on target / macros values.
In your case, you change path to the customer game / test data file based on selected the target / macro combination.
You can also set a different bundleID for each target.
Once this is all setup you simply just switch between target and compile. The whole thing should just work seamlessly.
Make a backup of your project and then follow this tutorial which covers exactly how to do this:
https://www.appcoda.com/using-xcode-targets/
If the link above is broken in future, just search "Xcode target tutorials"
UPDATE
The problem is that facebook documentation for swift is outdated so to solve this you will have to log your custom event like this:
func logMyEvent(name : String, value : String) {
let params : [String: Any] = ["myParamName" : "myParamValue"]
let eventName: AppEvents.Name = AppEvents.Name(rawValue: "myEventName")
AppEvents.logEvent(eventName, parameters: params)
}
IMPORTANT!
Take into account that facebook will log your event in its console about 20 minutes after you called it. So do not stress if the data is not there, just wait (I'm talking from experience hahaha). If you have any doubts don't hesitate to contact me, maybe I can help :D
I'm integrating Swift FacebookCore SDK so I can use Facebook Analytics! The problem is that facebooks official documentation DOES NOT WORK! It seems that they haven`t updated the code so I can not get the real code to log my own customized Event!
This is the code that Facebook gives you!
* For more details, please take a look at:
* developers.facebook.com/docs/swift/appevents
*/
func logMyEventEvent(name : String, value : String) {
let params : AppEvent.ParametersDictionary = [
"name" : name,
"value" : value
]
let event = AppEvent(name: "myEvent", parameters: params)
AppEventsLogger.log(event)
}
Got it from here: https://developers.facebook.com/docs/app-events/getting-started-app-events-ios in the Manually Log Events section.
But AppEvent NO LONGER EXISTS.
Searching the web I found out that is because Facebook renamed it to AppEvents
IMAGE WERE I FOUND IT Here is the link to the GitHub poll. https://github.com/facebookarchive/facebook-swift-sdk/issues/433
But this still does not solve my issue, because I can not log a custom event.
Has anyone solved the same problem without going to the previous version?
Thank you very much!
It's hard to believe, but I couldn't find any documentation on this either.
However, I worked out the following code from looking though the FBSDKCoreKit source. I haven't tested it yet, but I am posting it here just in case I meet with a sudden unexpected death in the next few minutes.
import FBSDKCoreKit
let name = "myEvent"
let parameters: [String: Any] = [
"myParameter": "myParameterValue"
]
let event = AppEvents.Name(name)
AppEvents.logEvent(event, parameters: parameters)
For Custom Event you can use
import FBSDKCoreKit
AppEvents.shared.logEvent(AppEvents.Name(rawValue: "AppEventName"), parameters: [AppEvents.ParameterName.init("Key"):"Value"])
import FBSDKCoreKit
AppEvents.logEvent(AppEvents.Name.(*FBSDKAppEventName), parameters: ["*FBEvent": <Any>])
*FBSDKAppEventName based on facebook, so choose one out of these:
Facebook Standart Events
*FBEvent - Based on chosen FBSDKAppEventName there might be, and might not be parameter. Choose one from Parameters and set as String.
Example:
AppEvents.logEvent(AppEvents.Name.achievedLevel, parameters: [AppEvents.Parameters.achieved: "5"])
Example track without the parameters:
AppEvents.logEvent(AppEvents.Name.submitApplication)
Try this:
1- Declare globally an enum with all required events:
enum FACEBOOK_EVENTS: String {
case appOpened = "MyApp iOS is opened"
case appClosed = "MyApp iOS is terminated"
case checkoutClicked = "Checkout in iOS is clicked"
case placeOrderClicked = "Place order in iOS is clicked"
}
2- Second, register manually the event like this:
AppEvents.logEvent(AppEvents.Name.init(rawValue: GlobalClass.FACEBOOK_EVENTS.checkoutClicked.rawValue))
AppEvents.logEvent(AppEvents.Name.init(rawValue: GlobalClass.FACEBOOK_EVENTS.appOpened.rawValue))
...
If you want by default functions:
import FBSDKCoreKit
AppEvents.shared.logEvent(.subscribe)
I want to release my app in only 2 country and i want to do 2 different functionality for both country.
For example.
ViewController1 functionality is different in Jamaica.
ViewController1 functionality is different in Kenya.
Different functionality means content is different, or input forms are different.
Is it possible? if yes then please refer some document.
Thanks in advance
You should have a screen that allows user to select their country, after that, store selected country in our app (by UserDefault or Keychain, etc...).
Based on the selected country then you can switch logic/layout to adapt the requirement above
some notes about App Store:
1) language should / must be selected by user on Prefs, NOT in Apps.
Chances Apple will refuse apps not following above logic.
2) You could test current language / Zone using code (see below for language)
BUT I think Apple can refuse as you use a different behaviour
3) if really you need it, You can load a different controller using Storyboards (I suggest using different storyboards AND lod them at runtime using segues and "*.soryboard" as in:
func ViewControllerFromStoryboardWith( name: String ) -> UIViewController {
// we use an identifier equal to filename for now.
let storyboard = UIStoryboard(name: name, bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: name) as UIViewController
return vc
}
// test lang:
func currHWLanguage()->String{
let defs : UserDefaults = UserDefaults.standard
let languages : NSArray = defs.object(forKey: "AppleLanguages") as! NSArray
let current = languages[0] as! String
// since 9.0 we get en-US etc.. so cut to 2:
let result = (current as NSString).substring(to: 2)
#if DEBUG
// force to IT as a bug in simulator
// return "IT"
#endif
return result.uppercased()
//NSLog("%#", current)
}
This is a problem many applications are trying to solve. Basically, you have the following options:
Let the user choose. This is the safest option if one application contains two different configuration.
Try to detect location of the user. Language/Locale is unsafe because many people will have English (or different) locale set up. Very unsafe. You shouldn't ask for GPS location for this. The safest option is to create a server request and check the location using the IP address. A bit complicated and won't work if a VPN is used (e.g. antivirus apps create VPNs).
Create two different apps. In the end, this is the best option. Add a second application target to your project and release two separate apps with separate configuration.
I am fairly new in Google Analytics in general, so please be patient with me and my questions.
If I am using the Google Analytics for web, by putting in the tracking code in my web header, GA will automatically collect data (visitors, page view, sources, etc)
For mobile app, I need to put the plist (iOS) or json (Android) into the build and compile.
My understanding is that it's not enough just by putting the plist or json file into the app, right?
I would need to implement each and every single thing I want to track. For example, if i want a pageview (screens), then I would need to implement it inside my app code
https://developers.google.com/analytics/devguides/collection/ios/v3/screens
so it's not automatic like in web, where I put the script on header and it works right away.
Is that correct?
I haven't used GA for web, so I can't compare it to GA for mobile.
But I believe you are correct that you need to implement everything you want to track.
It's not too difficult. Using GA 3.11 for iOS, in Swift 3.0, the first thing I do is set up the shared GA instance in my app delegate's didFinishLaunchingWithOptions method:
GAI.sharedInstance().trackUncaughtExceptions = true
GAI.sharedInstance().dispatchInterval = 120
GAI.sharedInstance().logger.logLevel = GAILogLevel.info
GAI.sharedInstance().tracker(withTrackingId: "YOUR GA ID GOES HERE")
To track a screen view, I do this in viewWillAppear of each view controller:
if let tracker = GAI.sharedInstance().defaultTracker {
tracker.set(kGAIScreenName, value: "YOUR SCREEN NAME GOES HERE")
tracker.send(GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject])
}
To send an event:
let tracker = GAI.sharedInstance().defaultTracker
tracker?.send(GAIDictionaryBuilder.createEvent(withCategory: "YOUR CATEGORY", action: "YOUR ACTION", label: "YOUR LABEL", value: NSNumber(integerLiteral: YOURINTEGERVALUE)).build() as NSDictionary as [NSObject : AnyObject])
Setting custom dimensions and sending screen views with dimensions is similar. That's about as far as I've gone with GA.
Im trying to integrate Google Analytics in SWIFT. I used this user guide and tried to do in SWIFt. But I'm having hard time since this is the first time using Google Analytics. Is there any tutorial/resource for SWIFT ? Thanks in advance.
Edit1: Procedure and code I have used,
1. Added the google headers in bridging-header file
2. Added these in Appdelegate
GAI.sharedInstance().trackUncaughtExceptions = true
GAI.sharedInstance().dispatchInterval = 20
GAI.sharedInstance().trackerWithTrackingId("UA-XXXX-YY")
3. Gave the screen name in viewDidAppear as self.screenName = "Game Screen"
4. Created an event as
var tracker = GAI.sharedInstance().trackerWithTrackingId("UA-XXXX-YY")
tracker.send(GAIDictionaryBuilder.createEventWithCategory("SolveGame", action: "GameSolved", label: "Solve", value: nil).build())
I know I'm late, but I was in a similar situation today - not knowing much about Google Analytics and trying to implement it in Swift, for which there's little help online yet. I got it working with basically the same code you have shown here. One suggestion: If you also set
GAI.sharedInstance().logger.logLevel = GAILogLevel.Verbose
You may get some useful messaging in the console.
One other minor point is that I'm calling trackerWithTrackingId() first, before the other calls. Not sure if order matters.
Also, I'm assuming from your point #3 that you're implementing GAITrackedViewController, but figured I would mention that anyway as a tip.
And one final sanity check - you are using your actual tracking id, rather than "UA-XXXX-YY" in your code, right?
I just had to deal with this. I used both the demo provided AND integrated it into my app.
Nothing. 0s.
Then I came in this morning to take a look. It was working this morning. So evidently there is a good bit of lag before this aspect of Google Analytics kicks in.
As for your event tracking, that should work if you are tracking events, however that isn't how you would track a given page.
Assuming that want to track pages to you would want to use something like this.
var storyboardViewName = "Lender-Details-View"
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// [START GOOGLE ANALYTICS]
var tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIScreenName, value: storyboardViewName)
var builder = GAIDictionaryBuilder.createScreenView()
tracker.send(builder.build() as [NSObject : AnyObject])
// [END GOOGLE ANALYTICS]
//.... other code here .....
}