i set up Google Analytics for my iOS-App and it is basically working fine (Screen Tracking, Purchases,...), except the install Campaign tracking is not ok.
I saw a similar question here which is not solved:
Google analytics iOS campaign tracking testing on development
This is the guide i used to implement (is in Objective-C):
https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#general-campaigns
I see in my Google Analytics Dashboard the referrer installs, but no campaign installs.
This is a test Url
https://click.google-analytics.com/redirect?tid=UA-51157298-2&url=https%3A%2F%2Fitunes.apple.com%2Fat%2Fapp%2Fbikersos-motorrad-unfall-sos%2Fid980886530&aid=com.BikerApps.BikerSOS&idfa=%{idfa}&cs=test_source&cm=test_medium&cn=test_campaign&cc=test_campaign_content&ck=test_term
which i generated here:
https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#url-builder
My Code which should track the install is the following:
AppDelegate:
//Method where app parses any URL parameters used in the launch
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
//Track google Analytics URL
GAService.shared.trackURL(url)
//some other handlers here ....
return true/false
}
GAService Singleton:
#objc public class GAService : NSObject {
private let trackID : String = "UA-xxxxxx-x"
var trackGoogleAnalytics : Bool = Constants.Analytics.trackGoogleAnalytics
private let UTM_SOURCE_KEY : String = "xxx"
private let UTM_MEDIUM_KEY : String = "xxx"
private let UTM_CAMPAIGN_KEY : String = "xxx"
var trackUncaughtExceptions : Bool = true
var tracker : GAITracker? = nil
static let shared = GAService()
var UtmSource : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_SOURCE_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_SOURCE_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_SOURCE_KEY)
UserDefaults.standard.synchronize()
}
}
}
var UtmCampaign : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_CAMPAIGN_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_CAMPAIGN_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_CAMPAIGN_KEY)
UserDefaults.standard.synchronize()
}
}
}
var UtmMedium : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_MEDIUM_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_MEDIUM_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_MEDIUM_KEY)
UserDefaults.standard.synchronize()
}
}
}
private override init() {
super.init()
if(trackGoogleAnalytics) {
tracker = GAI.sharedInstance().tracker(withTrackingId: trackID)
tracker!.allowIDFACollection = true
GAI.sharedInstance().trackUncaughtExceptions = trackUncaughtExceptions
GAI.sharedInstance().logger.logLevel = GAILogLevel.error
GAI.sharedInstance().dispatchInterval = 1
}
}
public func trackURL(_ url : URL){
let urlString = url.absoluteString
// setCampaignParametersFromUrl: parses Google Analytics campaign ("UTM")
// parameters from a string url into a Map that can be set on a Tracker.
let hitParams : GAIDictionaryBuilder = GAIDictionaryBuilder()
// Set campaign data on the map, not the tracker directly because it only
// needs to be sent once.
hitParams.setCampaignParametersFromUrl(urlString)
// Campaign source is the only required campaign field. If previous call
// did not set a campaign source, use the hostname as a referrer instead.
if((hitParams.get(kGAICampaignSource) != nil) && (url.host ?? "").length() != 0) {
hitParams.set("referrer", forKey: kGAICampaignMedium)
hitParams.set(url.host, forKey: kGAICampaignSource)
}
let hitParamsDict : [AnyHashable : Any] = hitParams.build() as Dictionary as [AnyHashable : Any]
if(hitParamsDict.count > 0) {
// A screen name is required for a screen view.
let source : String? = hitParams.get(kGAICampaignSource)
let medium : String? = hitParams.get(kGAICampaignMedium)
let campaign : String? = hitParams.get(kGAICampaignName)
if(source != nil || medium != nil || campaign != nil) {
tracker?.set(kGAIScreenName, value: "openUrl")
GAService.shared.UtmSource = source ?? ""
GAService.shared.UtmMedium = medium ?? ""
GAService.shared.UtmCampaign = campaign ?? ""
}
// SDK Version 3.08 and up.
//[tracker send:[[[GAIDictionaryBuilder createScreenView] setAll:hitParamsDict] build]];
let sendDict : [AnyHashable : Any] = GAIDictionaryBuilder.createScreenView().setAll(hitParamsDict).build() as Dictionary as [AnyHashable : Any]
tracker?.send(sendDict)
tracker?.set(kGAIScreenName, value: nil)
}
}
As i have to set a screenName for such a tracking i defined openUrl as a constant and set it to nil afterwards i sent the dictionary.
I hope anybody can see what i'm doing wrong
thank you in advance
I used it with deep link where I decide the navigation:
let tracker1 = GAI.sharedInstance().tracker(withTrackingId: "...")
let hitParams = GAIDictionaryBuilder()
hitParams.setCampaignParametersFromUrl(path)
let medium = self.getQueryStringParameter(url: path, param: "utm_medium")
hitParams.set(medium, forKey: kGAICampaignMedium)
hitParams.set(path, forKey: kGAICampaignSource)
let hitParamsDict = hitParams.build()
tracker1?.allowIDFACollection = true
tracker1?.set(kGAIScreenName, value: "...")
tracker1?.send(GAIDictionaryBuilder.createScreenView().setAll(hitParamsDict as? [AnyHashable : Any]).build() as? [AnyHashable : Any])
// this is for url utm_medium parameter
/*
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}*/
then I saw the source on google analytics.
I think that Google Analytics docs are very poor on this subject. If you look carefully in what is written in the main guide for "Campaign Measurement" it says "After an app has been installed, it may be launched by referrals from ad campaigns, websites, or other apps" and Google gives an example with custom url scheme which is handled.
Hopefully, there is another guide https://developers.google.com/analytics/solutions/mobile-campaign-deep-link which describes how to use deep linking for campaign measurement.
So, if you want to track campaign measurement you must use either Universal Links mechanism or custom url scheme for your application.
Universal Links mechanism is the preferred way if you have backend and hence can decide where to redirect user: in iTunes or in application.
Assume, you have a site link https://example.com/openapp which redirects user to iTunes Connect. You should insert you Universal Link address (https://example.com/openapp) in the
generator https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#url-builder.
Related
I want to compare data at one node with data located at an entirely different node.
Please know, in advance I cannot change any front end codes, by instruction of my project lead.
Context:
I'm building for a client which will be an upscale chatting app for people who want to find new roommates on college campuses. There are two user types, leasees (leasee) and leasors (leasor).
leasor users add listings and have profiles, while leasee users also have profiles and initiate communication by reaching out to leasor users first with a roommateRequest - it's a chat message but its basically like a Skype, Facebook or LinkedIn request if you will.
I want both user types to be able to write to or remove UIDs from the roommates node (their roommates list on the app), similar to a Facebook friends list. However, when a leasee has no roommates they cannot write to the roommates node because they send a roommateRequest to leasor users and leasor users cannot send a roommateRequest to a leasee user profile.
On the frontend iOS client, the leasor user accepts a roommateRequest - which simultaneously writes to both the leasor user's roommates node & the requesting leasee user's roommates node.
Here is the front end iOS client code related to the database data I will show you next:
import Foundation
import FirebaseAuth
import FirebaseDatabase
class ChatFunctionService {
func sendRoommateRequestToId(text: String, _ id: String, completionHandler: #escaping() -> Void) {
let timestamp = Date().timeIntervalSince1970
guard let uid = Auth.auth().currentUser?.uid,
let expiration = Calendar.current.date(byAdding: .day, value: 7, to: Date())?.timeIntervalSince1970 else {
return
}
var values: [String: Any] = ["expiration": expiration, "status": "pending"]
values["receiverProfileType"] = otherUserTypeTextString
Database.database().reference().child("roommateRequests").childByAutoId().setValue(values) { _, ref in
let message = UserChatModel(
dictionary: [
"text": text,
"senderId": uid,
"receiverId": id,
"timestamp": timestamp,
"roommateRequestId": ref.key!,
"receiverProfileType": self.otherUserTypeTextString
]
)
self.sendChat(message, toUser: id) {
completionHandler()
}
}
}
///Send message:
private func sendChat(_ message: UserChatModel, toUser receiverId: String, completion: #escaping() -> Void) {
guard let uid = Auth.auth().currentUser?.uid,
let partnerId = message.getPartnerId() else { return }
Database.database().reference().child("messages").childByAutoId().updateChildValues(message.data) { _, ref in
let messageUID = ref.key!
let senderRef = Database.database().reference().child("conversations").child(uid).child(self.userTypeTextString).child(receiverId)
let receiverRef = Database.database().reference().child("conversations").child(receiverId).child(self.otherUserTypeTextString).child(uid)
ref.updateChildValues(["uid": messageUID])
senderRef.updateChildValues([messageUID: 1])
receiverRef.updateChildValues([messageUID: 1])
self.updateChatsData(chat: message, partnerUID: partnerId, messageUID: messageUID)
completion()
}
}
func acceptRoommateRequest(roommateRequestId: String, userTypeTextString: String, receiverUID: String, otherUserTypeTextString: String, completion: #escaping () -> Void) {
guard let uid = Auth.auth().currentUser?.uid else {
completion()
return
}
Database.database().reference().child("roommateRequests").child(roommateRequestId).child("status").setValue("accepted")
let values = [
"/\(uid)/\(userTypeTextString)/\(receiverUID)": 1,
"/\(receiverUID)/\(otherUserTypeTextString)/\(uid)": 1
]
Database.database().reference().child("roommates").updateChildValues(values)
updateChatsMetaDataForNewRoommate(userTypeTextString: userTypeTextString, receiverUID: receiverUID, otherUserTypeTextString: otherUserTypeTextString) {
completion()
}
}
func updateChatsMetaDataForNewRoommate(userTypeTextString: String, receiverUID: String, otherUserTypeTextString: String, completion: #escaping () -> Void ) {
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("chatsData").child(uid).child(userTypeTextString).child(receiverUID).child("roommates").setValue(true)
Database.database().reference().child("chatsData").child(receiverUID).child(otherUserTypeTextString).child(uid).child("roommates").setValue(true)
sendUserChat(text: "roommate request granted.", receiverUID: receiverUID) { (messageId) in
completion()
}
}
func sendUserChat(text: String, receiverUID: String, completion: #escaping(String?) -> Void) {
let timestamp = Date().timeIntervalSince1970
guard let uid = Auth.auth().currentUser?.uid else { return }
var chatDictionary: [String: Any] = [
"text": text, "timestamp": timestamp,
"senderId": uid, "receiverId": receiverUID
]
chatDictionary["receiverProfileType"] = "leasee"
let chat = UserChatModel(dictionary: chatDictionary)
guard let partnerUID = chat.getPartnerId() else {
return
}
Database.database().reference().child("threads").childByAutoId().updateChildValues(chat.data) { _, ref in
let messageUID = ref.key!
ref.updateChildValues(["uid": messageUID])
self.updateChatsData(
chat: chat,
partnerUID: partnerUID,
messageUID: messageUID
)
completion(messageUID)
}
}
func updateChatsData(chat: UserChatModel, partnerUID: String, messageUID: String) {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
var chatData = [String: Any]()
chatData["lastMessageSenderId"] = uid
chatData["lastMessageId"] = messageUID
chatData["lastMessage"] = chat.text ?? ""
chatData["chatMemberIds"] = [uid, partnerUID]
Database.database().reference().child("chatsMetaData").child(uid).child(userTypeTextString).child(partnerUID).setValue(chatData)
Database.database().reference().child("chatsMetaData").child(partnerUID).child(otherUserTypeTextString).child(uid).setValue(chatData)
}
}
class UserChatModel {
var text: String?
var senderId: String
var receiverId: String
var timeStamp: NSNumber
var data: [String: Any]
var roommateRequestId: String?
init(dictionary: [String: Any]) {
data = dictionary
text = dictionary["text"] as? String
senderId = dictionary["senderId"] as! String
timeStamp = dictionary["timestamp"] as! NSNumber
receiverId = dictionary["receiverId"] as? String ?? ""
roommateRequestId = dictionary["roommateRequestId"] as? String
super.init(dictionary: dictionary)
}
func getPartnerId() -> String? {
guard let uid = Auth.auth().currentUser?.uid else {
return nil
}
return senderId == uid ? receiverId : senderId
}
}
Once the leasee has a single roommate, they should be able to remove that roommate from their roommates list (if they so wish). Continually, only leasor users can accept a roommateRequest which writes to both the roomateRequest sender (leasee) user's UID in the roommates node and the receiver (leasor) user's UID in the roommates node. Moreover, leasor users can also remove leasee users from their roommates node, at anytime.
The issue is I don't want just any user to be able to write or update the roommates node of any user unless it is removing themselves from it.
Here is the full realtime-database structure for the chat portion of the app, in two screenshot images:
I've narrowed my works down (between the iOS front end, database structure and security rules) to matching the lastMessageId value from the chatsMetaData or chats node to then check the threads node to get the roommateRequestId and then compare the roommateRequestId to the 'roommateRequests' node to verify that the leasor can write to the leasee's node. And then I will provide an OR case and match the value at the roommates node to the value of auth.uid for after they are already on each other's roommate's list for when a user wants to write to their roommates node (removing a roommate) - but I have no idea on how to do any of this. I know what to do but I am not sure how to achieve it...
I think I can reference the chatsMetaData node all the way down to the 'lastMessageId' value :
With this value I think I should then search the threads node to get the roommateRequestId and then search the roommateRequests node to match it and allow a write along with some auth.uid checks to match uid values in the node's data. But how can I do all this if they are by autoId way?
I have been working in iOS autofill credential extension since long days. I have checked so many iOS articles and videos. But I am not able to show credential on quick type bar, reset things are successfully integrated. Can any give me quick help?
Using this video and url which was shared from apple:
https://developer.apple.com/videos/play/wwdc2018/721
https://developer.apple.com/documentation/authenticationservices
I am using below code to save credential to Keychain for particular domain.
let keychain = Keychain(server: "instagram.com", protocolType: .https, authenticationType: .htmlForm)
keychain["emailAddress"] = "Password"
And use this code for save domain:
func savedomain(domain: String, account: String, password: String, completion: ((Bool, SharedWebCredentialsManagerError?) -> Void)? = nil) {
SecAddSharedWebCredential(domain as CFString, account as CFString, password as CFString?) { error in
guard let error = error else {
completion?(true, nil)
return
}
let errorDescription = CFErrorCopyDescription(error) as String
let saveFailedError = SharedWebCredentialsManagerError.saveFailed(errorDescription)
completion?(false, saveFailedError)
}
}
I have created autofill extension and getting saved credentials, but not able to display credential on quick type bar in safari for instagram.com
I have implemented autofill extension for all social sites, Sharing my source code to save emailID-Password with domain.
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Above "Save" function i have written in my custom KeyChainManager class, Also i added below code in KeyChainManager class which is as below.
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
I am saving my data from VC by calling our KeyChainManager class like below:
let email = (txtEmail?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let password = (txtPassword?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let domain = (txtDomain?.text ?? "").lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
let user = User(email: email, password: password, key: self.key, domain: domain, identifier: self.keyIdentifier)
let data = (try? JSONEncoder().encode(user)) ?? Data()
let _ = KeyChain.save(key: "\(self.keyIdentifier)", data: data)
This all stuff is for saving our credentials, Now major point is how to list all saved credentials in our extension's CredentialProviderViewController.swift.
For that i added below method in KeyChainManager class :
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
And calling this function from extension's CredentialProviderViewController.swift like this:
users.removeAll()
for i in 0..<1000000 {
if let encodedData = KeyChain.load(key: "\(i)") {
let user = try! JSONDecoder().decode(User.self, from: encodedData)
if user.key == key && ((serviceIdentifier.contains(user.domain ?? "")) ) {
users.append(user)
}
}else{
break
}
}
I hope this content helps you as i spent many days to create just one demo :) :)
Comment below, If its helps you :) :)
Thanks,
Anjali.
You need to populate ASCredentialIdentityStore in order for the quicktype bar to work. See the description of ASCredentialProviderViewController:
Optionally add ASPasswordCredentialIdentity instances to the shared ASCredentialIdentityStore to make identities available directly in the QuickType bar.
This is also described in the WWDC presentation you reference.
How I implemented population of ASCredentialIdentityStore:
var firstItem = ASPasswordCredentialIdentity(serviceIdentifier: ASCredentialServiceIdentifier(identifier: "https://online.somebank.com/auth/login", type: .URL), user: "login#bank.com", recordIdentifier: nil)
ASCredentialIdentityStore.shared.replaceCredentialIdentities(with: [firstItem]) { result, error in
if result {
print("saved")
}
}
In my case everything works perfect.
So I fetch all passwords from remote and then populate ASCredentialIdentityStore with available passwords.
I am trying to create an AppSettings class to store user setttings in an user dictionary. My AppSettings class:
class AppSettings : NSObject
{
static var user: [String: Any]?
static let sharedSingleton : AppSettings = {
let instance = AppSettings()
return instance
}()
var userID: String?
var baseUrl: String?
override private init() {
super.init()
let userDefaults = UserDefaults.standard
AppSettings.user = userDefaults.object(forKey: "user") as! [String : Any]?
print(AppSettings.user ?? "User Defaults has no values")
self.saveSettings()
}
func saveSettings()
{
let userDefaults = UserDefaults.standard
if((AppSettings.user) != nil)
{
userDefaults.set(AppSettings.user, forKey: "user")
}
userDefaults.synchronize()
}
}
I am storing values in user this way:
AppSettings.user?["User_ID"] = String(describing: dictionary["User_ID"])
AppSettings.sharedSingleton.saveSettings()
let defaults = UserDefaults.standard.object(forKey: "user")
print(defaults ?? "User is null")
It shows that User is null. What's wrong?
Assuming your example is complete, the problem lies in this line
AppSettings.user?["User_ID"] = String(describing: dictionary["User_ID"])
The user variable is an optional, so unless you created an empty dictionary first this is what happens - if user is not nil, assign a value under "User_ID" key.
All you need to do is write AppSetting.user = [String:Any]() before trying to add values to it. Now, it may seem that you do it in init(), but it's not called unless you call sharedInstace.
On a side note - if you are making this a singleton, why do you expose the user variable as a static?
I am new to Firebase and I want to store the provider photo URL however it come out the error 'Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.' I have tried different type of the method but it seems not working for example let profilePicUrl = profile.photoURL as String or let profilePicUrl = NSString(NSURL: profile.photoURL)
It is my method
func createFirebaseUser(){
let key = ref.child("user").childByAutoId().key
if let user = FIRAuth.auth()?.currentUser{
for profile in user.providerData{
let uid = profile.uid
let providerID = profile.providerID
let displayName = profile.displayName
let email = ""
let profilePicUrl = profile.photoURL
//let userDict :[String : AnyObject] = ["name": username!"profilePicUrl": profilePicUrl!]
let profile = Profile(uid: uid, displayName: displayName!,email: email, imageUrl: profilePicUrl)
let childUpdates = ["/user/\(key)": profile]
ref.updateChildValues(childUpdates, withCompletionBlock: { (error, ref) -> Void in
// now users exist in the database
print("user stored in firebase")
})
}
}
and it is the data model
import Foundation
class Profile {
private var _uid: String
private var _displayName: String
private var _email: String?
private var _gender: String!
private var _city: String!
private var _imageUrl: String!
var uid: String {
return _uid
}
var displayName: String {
get {
return _displayName
}
set {
_displayName = newValue
}
}
var email: String {
get {
return _email!
}
set {
_email = newValue
}
}
var gender: String {
get {
return _gender
}
set {
_gender = newValue
}
}
var city: String {
get {
return _city
}
set {
_city = newValue
}
}
var imageUrl: String {
get {
return _imageUrl
}
set {
_imageUrl = newValue
}
}
init(uid: String, displayName: String, email: String, imageUrl: String) {
_uid = uid
_displayName = displayName
_email = email
_imageUrl = imageUrl
}
You can only store the 4 types of NSObjects you mentioned in Firebase. But for the most part, data is just a string and storing strings is easy.
Assuming that your photoURL is an actual NSURL, you can save it as a string
let ref = myRootRef.childByAppendingPath("photo_urls")
let thisFileRef = ref.childByAutoId()
thisFileRef.setValue(photoURL.absoluteString)
and to go from a string to an NSURL
let url = NSURL(string: urlString)!
Also, it appears you a creating a new user in Firebase. You can greatly simplify your code like this
let usersRef = self.myRootRef.childByAppendingPath("users")
let thisUserRef = usersRef.childByAutoId()
var dict = [String: String]()
dict["displayName"] = "Bill"
dict["email"] = "bill#thing.com"
dict["gender"] = "male"
dict["photo_url"] = photoURL.absoluteString
thisUserRef.setValue(dict)
I would suggest making that code part of your User class so you can
let aUser = User()
aUser.initWithStuff(displayName, email, gender etc etc
aUser.createUser()
That really reduces the amount of code and keeps it clean.
If you are storing users, you should use the auth.uid as the key to each users node. This is more challenging in v3.x than it was in 2.x but hopefully Firebase will soon have a fix.
What you should really do is to store a relative path to your data into Firebase database and the prefix of the absolute URL separately (maybe in Firebase in some other node or somewhere else). This will help you to be flexible and being able to switch to a different storage without a lot of worries. Moreover, it should solve your current problem, because you will be storing raw strings in the Firebase and then in the app, you will merge prefix and the relative path together in order to produce the complete URL.
For example, let's assume that your URL to a photo looks like that:
http://storage.example.com/photos/photo1.jpg
Then, you can decompose this URL into:
prefix = http://storage.example.com/
relativeUrl = photos/photo1.jpg
And store the prefix for example in some settings node in the Firebase database and the relativeUrl in your photos' data.
Then in order to construct the complete URL you want to concatenate them together.
I am writing an iOS app in Swift and the app collects user input from multiple app screens and in the last screen its supposed to POST the information collected to the server via API.
Now my question is, what is the best way to manage the collected data on the app? Should I use plist to save my form data ? It also has one image which I want to upload to my server from the final screen. How should I go about this?
PS: I also read about http://developer.apple.com/CoreData, but I'm not sure if this is the right way to go forward.
Any suggestion is greatly appreciated.
UPDATE: to save your time - this is Swift 1.2 solution. I didn't test it on Swift 2 (likely secureValue flow have to be updated)
Looks like you are talking about user's details/profile (correct me if I wrong), for this amount of data - using NSUserDefault is totally ok.
For user preferences (if that the case!!) I would use something like Preference Manager:
import Foundation
import Security
class PreferencesManager {
class func saveValue(value: AnyObject?, key: String) {
NSUserDefaults.standardUserDefaults().setObject(value, forKey: key)
NSUserDefaults.standardUserDefaults().synchronize()
}
class func loadValueForKey(key: String) -> AnyObject? {
let r : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey(key)
return r
}
class func saveSecureValue(value: String?, key: String) {
var dict = dictForKey(key)
if let v = value {
var data: NSData = v.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
(SecItemDelete(dict as NSDictionary as CFDictionary))
dict[kSecValueData as NSString] = v.dataUsingEncoding(NSUTF8StringEncoding,
allowLossyConversion:false);
var status = SecItemAdd(dict as NSDictionary as CFDictionary, nil)
} else {
var status = SecItemDelete(dict as NSDictionary as CFDictionary)
}
}
class func loadSecureValueForKey(key: String) -> String? {
var dict = dictForKey(key)
dict[kSecReturnData as NSString] = kCFBooleanTrue
var dataRef: Unmanaged<AnyObject>?
var value: NSString? = nil
var status = SecItemCopyMatching(dict as NSDictionary as CFDictionary, &dataRef)
if 0 == status {
let opaque = dataRef?.toOpaque()
if let op = opaque {
value = NSString(data: Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue(),
encoding: NSUTF8StringEncoding)
}
}
let val :String? = value as? String
return val
}
class private func dictForKey(key: String) -> NSMutableDictionary {
var dict = NSMutableDictionary()
dict[kSecClass as NSString] = kSecClassGenericPassword as NSString
dict[kSecAttrService as NSString] = key
return dict
}
}
You can use
PreferencesManager.saveSecureValue for secure data (like password etc) and
PreferencesManager.saveValue for the rest of values