I am trying to implement Urban Airship Analytics in my app. I want to track each and every event in my app, for that I have made a different class and passed tracking data as a dictionary.
Following https://docs.urbanairship.com/platform/ios/?swift#ios-screen-tracking link for the same.
I am passing parameters as:
UAirship.shared().analytics.trackScreen("MainScreen")
let event = UACustomEvent()
event.properties = createParamDictionary(paramDict1,paramDict2)
event.track()
As event properties is readonly, I can not assign/add data to it.
And the only option I can see is adding data one by one according to its defined type.
ie.
event.setStringProperty("abcd", forKey: "abcd")
event.setNumberProperty(123, forKey: "xyz")
Which is very tedious in my case.
So My questions are:
Am I doing it correctly?
If Yes, then is there any other variable or some way from which I can directly add parameters?
I also want to add User_id for tracking particular user. Any provision for this?
Thanks.
I think that you can create a custom method to UACustomEvent class which takes a dictionary and sets values using UA defined method, something like this,
extension UACustomEvent {
func setEventProperties<T: Any>(_ values: [String: T]) {
for keyValuePair in values {
if let value = keyValuePair.value as? String {
setStringProperty(value: value, forKey: keyValuePair.key)
} else if let value = keyValuePair.value as? Int {
setNumberProperty(value: value, forKey: keyValuePair.key)
}
}
}
}
That way, you dont have to use setNumberProperty or setStringProperty each time, you want to set events. You can simply do it like this,
event.setEventProperties(["abcd": "abcd", "xyz": 123])
Related
I'm wondering what's the best way to store user settings in Swift. With user settings I mean simple (small) data, not some big files. Until now, I used a class with all properties I wanted to be saved.
All of those properties conform to Codable. However, I didn't save the whole class in UserDefaults, instead I saved each property individually. But I don't like that approach. There are several issues with it: The code gets longer because for every variable I have to write didSet{...}. For example:
var percentage: Double = UserDefaults.standard.double(forKey: "percentage") {
didSet {
UserDefaults.standard.set(percentage, forKey: "percentage")
}
}
As you can see, the variable name is written 4 times here. So there is a high chance of misspelling / copy and paste errors.
So why don't I save the whole class then? Well, I noticed that if I add a variable to the class, the decoding of the class doesn't work anymore and all data is lost even if I give the new variable a default value.
There seems to be a way to fix this: decoding manually. For example:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
//etc...
}
However, decoding manually seems to require me to decode every variable separately. I don't like that as well because there is also a high chance to forget about one variable etc. (so it's the same problem as above).
What I would like to do as well is to give the user the option to export and import settings and to use iCloud for settings synchronization. For the former it would be better to store the whole Settings class (I could export and import the JSON file).
Is there a smart way to do this?
Thanks for helping me out!
You might also want some kind of class managing all of your user's stuff, something like this:
class SettingsManager {
private let defaults = UserDefaults.standard
var percentage: Double {
get { return defaults.value(forKey: "percentage") as? Double ?? 0.0 }
set { defaults.set(newValue, forKey: "percentage") }
}
}
This way you can reduce the required amount of code to this:
// Retrieve a value
let percentage = SettingsManager().percentage
// Set new value
SettingsManager().percentage = 0.55
Ideally you might use property wrappers like described here.
This eliminates the need of encoding/decoding the value until it's a custom type.
So in my app, I have a view controller that allows the user to input an email and password into UITextFields, and then I log the user in with Firebase and identify their uid associated with the email. I also have a struct called User which contains username and userID Strings and an array of Strings (I'm not providing the code for it because I don't think it is necessary, but if you need it, let me know).
I have a class level variable
var currentUser:User? = nil
and in the login button's #IBaction method I do the following to initialize a user with a FIRDataSnapshot using the aforementioned uid:
FIRAuth.auth()?.signIn(withEmail: usernameTextBox.text!, password: passwordTextBox.text!) { (user, error) in
if error == nil {
//Valid Email and Password
let userID = FIRAuth.auth()?.currentUser?.uid
self.childRef.observe(.value, with: { snapshot in
for item in snapshot.children.allObjects as! [FIRDataSnapshot] {
let dict = item.value as! Dictionary<String,Any>
if (dict["UserID"] as? String == userID) {
self.currentUser = User(snapshot: item) //<----This is where currentUser is assigned the new User
}
//currentUser = User(snapshot: )
self.performSegue(withIdentifier: "LoginSegue", sender: sender)
}
})
}
else {
//Error stuff
}
}
My problem is that in another classes I need to access the currentUser variable (to add friends, etc.) but I am not sure how to go about it. I also cannot pull the user data from Firebase in other classes because only the one described above has access to the UserID that I need to identify which user information to use and modify.
I cannot use prepareForSegue because there is an SWRevealViewController in between the one where you login and the other view controllers, and SWRevealViewController is written in Objective-C, which I am not familiar with. My research has revealed that some possibilities may be global variables and singletons, but using either is seen as unsafe practice by many programmers. Also using NSUserDefaults (I think they've been renamed to UserDefaults in Swift 3) is another potential solution? I am open to any suggestions.
Thanks in advance.
There are multiple ways to use same user data or fetched data across multiple classes. It depends on the size and usage of data.
Here are some simple ways to achieve this:
You can use UserDefaults to persist data across multiple classes even after restarting the app, data will persists after restarting until you clear it.
You can use a Singleton class just like the AppDelegate, add some relevant properties e.g. Dictionary or Array and access it from any of the class.
Pass data from one class to another when pushing the ViewController.
I think using a Singleton class would be enough solution for your problem if you don't need to persist it. And you can also write common methods in that Singleton class so you won't need to re-write the same method to use in multiple classes over and over again. Same goes for common Static Strings or Macros.
Hope it helps.
You can do this in any view. if let currentUser =
FIRAuth.auth()?.currentUser {
let email = currentUser.email //
get more user data
}
I know I can get the list of class properties using Mirror(reflecting:) but I can only print them. But what if I want to set properties to them and return the mirrored object.
Somwhat like this -
let mirroredObj = Mirror(reflecting: User())
for (index, property) in mirroredObj.children.enumerate() {
property.value = <SOME_VALUE>
}
return mirroredObj
Or maybe some totally different approach to do this?
You're trying to modify a class during runtime, which is impossible in Swift.
You are able to add a dictionary [String: Any] as a property though. It can be modified during runtime.
This has to be easy and I'm just missing something. I have a function in my application that is posting to a server, getting a response, and turning that response into an NSDictionary, and then turning one of the values sets in that NSDictionary into an NSArray. I need to access the values in this array outside of the scope of the function that created them. How can I make a json response available to my whole application if it is created within a specific function? Thanks!
There's a few ways you can do this, one of them is, as #GoodSp33d mentioned, NSUserDefaults another way is by using a completion handler and if you are open to using singletons, that's an option as well.
NSUserDefaults
// settings
var jo : [NSObject : AnyObject] = [
"a" : "1.0",
"b" : "2.0"
]
// store your value
userDefaults.setObject(jo, forKey: akey)
// optionally synchronize
var isOk = userDefaults.synchronize()
// safely retrieve your value
if let data0 = userDefaults.dictionaryForKey(akey) {
print(data0)
} else {
print("not set")
}
(source: Martin R)
note: this method actually persists the data to local storage making
it available across multiple sessions
#diatrevolo added:
It should be stated for consistency purposes that NSUserDefaults are
not always persistent across platforms. tvOS user defaults, for
example, get purged by the system
Singleton Method
There are a lot of people, myself included, that prefer not to use this approach but other consider this perfectly valid. This is the singleton approach. Setting your value to this property makes it globally available within your app.
struct sharedResult {
static var sharedDict: NSDictionary = nil
}
(more singleton approaches)
note: this method holds your data for the session only.
Completion Handler
However, I personally like to wrap my HTTP Requests in a closure / completion handler and when I have my object back from the server, I pass it into the completion handler.
// definition
func HTTPConnect(completion: (result: Bool)->()) {
// make http request {
// once you have your result pass it back
completion(result: myResult)
// }
}
// call from where you need the data
HTTPConnect() { (result) -> () in
// do stuff with the result
print(result)
}
Note: this method doesn't hold your data at all. It does, however, make it
easy for you to pass your value from one controller to another.
The code is from a book. In terms of overall app architecture (MVC), it's part of the Model. The model has two main components:
An array of tags called tags
A dictionary of tag - query called searches
The app saves these pieces of data in the NSUserDefaults (iOS defaults system) and on iCloud. The following method is called when a change in iCloud is signaled. The parameter is an instance of NSNotification.userInfo
// add, update, or delete searches based on iCloud changes
func performUpdates(userInfo: [NSObject: AnyObject?]) {
// get changed keys NSArray; convert to [String]
let changedKeysObject = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey]
let changedKeys = changedKeysObject as! [String]
// get NSUbiquitousKeyValueStore for updating
let keyValueStore = NSUbiquitousKeyValueStore.defaultStore()
// update searches based on iCloud changes
for key in changedKeys {
if let query = keyValueStore.stringForKey(key) {
saveQuery(query, forTag: key, saveToCloud: false)
} else {
searches.removeValueForKey(key)
tags = tags.filter{$0 != key}
updateUserDefaults(updateTags: true, updateSearches: true)
}
delegate.modelDataChanged() // update the view
}
}
My question is on the if - else inside the for loop. The for loop iterates over keys that where changed; either the user adds a new search, updates an existing search, or deletes a search. But, I don't understand the logic behind the if-else. Some clarifying thoughts would be appreciated. I've read it over and over but it doesn't tick with me.
if let query = keyValueStore.stringForKey(key)
means that if keyValueStore contains a string corresponding to key, then this string will be assigned to the constant query.
This is called "safe unwrapping":
inside the if let ... condition, the query is safely saved with saveQuery because using if let ... guarantees that the value of keyValueStore.stringForKey(key) won't be nil.
If the value is nil, then in the else branch, the filter method is used to update the tags array without the key we just processed: tags.filter{$0 != key} means "return all items in tags that are different from key" (the $0 represents the current item from the array processed by filter).