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
Related
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 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.
Using Xcode 6.4 and programming in swift.
I am typing a program that should read in JSON from a URL. A sample of the JSON can be found at this URL (https://itunes.apple.com/us/rss/topmovies/limit=2/json) and Ive been using this site to parse the JSON in order to read it better (http://json.parser.online.fr/).
Now I need to work through the levels of the JSON in order to get to
the actual movie names and image URL's but I am lost at what kind of variable entryDictionary should be. I was thinking it should be an array of dictionaries, and this compiles, but the output of entryDictionary in the console is sloppy looking, starting with Optionl( and not entry{ as it should. And when I go to loop through entryDictionary, I get an error saying entry does not have a subscript of type AnyObject.
So I am asking how I retrieve the im:name fields and im:image from the JSON.
func downloadDataFromURLString(urlString: String) {
//Downloaded data and is now stored in data. Took code out because
//irrelevant to my problem at this point. Data variable has correct
//JSON, now I am trying to parse it.
} else { //download suceeded, time to parse
var error: NSError? = nil
var names = [String]()
if let rootDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? [String: AnyObject] {
let feedDictionary = rootDictionary["feed"] as! [String: AnyObject]
let entryDictionary: AnyObject? = feedDictionary["entry"]
println(entryDictionary) //For debugging
//for entry in entryDictionary as! NSArray {
// let name = entryDictionary["name"]
// let image = entryDictionary["image"]
// let movie = Movie(name: name!, image: image!)
// weakSelf!.movies.append(movie)
//}
here is a blueprint of the JSON
"feed":{
"author":{},
"entry":[
{
"im:name":{
"label":"Deadpool"
},
"im:image":[],
"summary":{},
"im:price":{},
"im:contentType":{},
"rights":{},
"title":{},
"link":[],
"id":{},
"im:artist":{},
"category":{},
"im:releaseDate":{}
AnyObject is indeed not subscriptable (you're trying to subscript a variable whose type is AnyObject? with ["feed"]). You should also avoid casting to Cocoa container types like NSArray and NSDictionary whenever you can. Here's an example of how you might get the labels out of the entries array's names array:
import Foundation
func labels(feedDictionary:[String:AnyObject]) -> [String] {
guard let entries = feedDictionary["entry"] as? [String:AnyObject] else {
return []
}
return entries.flatMap { (key:String, value:AnyObject) -> String? in
guard key == "im:name" else {
return nil
}
guard let name = value as? [String:String] else {
return nil
}
return name["label"]
}
}
I'd however advise against using NSJSONSerialization on its own in Swift for anything but the simplest case, as you end up casting and wrapping optionals until the cows come home.
There are good 3rd party libraries such as Freddy and SwiftyJSON which apply Swift language features to accomplish a very convenient JSON (de)serialization experience.
For instance with Freddy you could express your problem in the following style:
let json = try JSON(data: data)
json.decode("feed", type:Feed.self)
struct Feed: JSONDecodable {
let entries:[Entry]
init(json: JSON) throws {
self.entries = try json.arrayOf("entry", type:Entry.self)
}
}
struct Entry:JSONDecodable {
let name:IMName
init(json: JSON) throws {
self.name = try json.decode("im:name", type:IMName.self)
}
}
struct IMName:JSONDecodable {
let label:String
init(json: JSON) throws {
self.label = try json.string("label")
}
}
Considering the following model:
class Person: Object {
dynamic var name = ""
let hobbies = Dictionary<String, String>()
}
I'm trying to stock in Realm an object of type [String:String] that I got from an Alamofire request but can't since hobbies has to to be defined through let according to RealmSwift Documentation since it is a List<T>/Dictionary<T,U> kind of type.
let hobbiesToStore: [String:String]
// populate hobbiestoStore
let person = Person()
person.hobbies = hobbiesToStore
I also tried to redefine init() but always ended up with a fatal error or else.
How can I simply copy or initialize a Dictionary in RealSwift?
Am I missing something trivial here?
Dictionary is not supported as property type in Realm.
You'd need to introduce a new class, whose objects describe each a key-value-pair and to-many relationship to that as seen below:
class Person: Object {
dynamic var name = ""
let hobbies = List<Hobby>()
}
class Hobby: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
For deserialization, you'd need to map your dictionary structure in your JSON to Hobby objects and assign the key and value to the appropriate property.
I am currently emulating this by exposing an ignored Dictionary property on my model, backed by a private, persisted NSData which encapsulates a JSON representation of the dictionary:
class Model: Object {
private dynamic var dictionaryData: NSData?
var dictionary: [String: String] {
get {
guard let dictionaryData = dictionaryData else {
return [String: String]()
}
do {
let dict = try NSJSONSerialization.JSONObjectWithData(dictionaryData, options: []) as? [String: String]
return dict!
} catch {
return [String: String]()
}
}
set {
do {
let data = try NSJSONSerialization.dataWithJSONObject(newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
override static func ignoredProperties() -> [String] {
return ["dictionary"]
}
}
It might not be the most efficient way but it allows me to keep using Unbox to quickly and easily map the incoming JSON data to my local Realm model.
I would save the dictionary as JSON string in Realm. Then retrive the JSON and convert to dictionary. Use below extensions.
extension String{
func dictionaryValue() -> [String: AnyObject]
{
if let data = self.data(using: String.Encoding.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
return json!
} catch {
print("Error converting to JSON")
}
}
return NSDictionary() as! [String : AnyObject]
} }
and
extension NSDictionary{
func JsonString() -> String
{
do{
let jsonData: Data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
return String.init(data: jsonData, encoding: .utf8)!
}
catch
{
return "error converting"
}
}
}
UPDATE 2021
Since Realm 10.8.0, it is possible to store a dictionary in a Realm object using the Map type.
Example from the official documentation:
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var currentCity = ""
// Map of city name -> favorite park in that city
let favoriteParksByCity = Map<String, String>()
}
Perhaps a little inefficient, but works for me (example dictionary from Int->String, analogous for your example):
class DictObj: Object {
var dict : [Int:String] {
get {
if _keys.isEmpty {return [:]} // Empty dict = default; change to other if desired
else {
var ret : [Int:String] = [:];
Array(0..<(_keys.count)).map{ ret[_keys[$0].val] = _values[$0].val };
return ret;
}
}
set {
_keys.removeAll()
_values.removeAll()
_keys.appendContentsOf(newValue.keys.map({ IntObj(value: [$0]) }))
_values.appendContentsOf(newValue.values.map({ StringObj(value: [$0]) }))
}
}
var _keys = List<IntObj>();
var _values = List<StringObj>();
override static func ignoredProperties() -> [String] {
return ["dict"];
}
}
Realm can't store a List of Strings/Ints because these aren't objects, so make "fake objects":
class IntObj: Object {
dynamic var val : Int = 0;
}
class StringObj: Object {
dynamic var val : String = "";
}
Inspired by another answer here on stack overflow for storing arrays similarly (post is eluding me currently)...
I created a custom class to save a array of Products in NSUserDefaults, but when I will test in my ViewController, I always get this using the autocomplete from Xcode 7.1
The function saveToFile or loadFromFile always shows self: DataFile as unique parameter.
This is my DataFile class
import Foundation
class DataFile {
let userDefaults = NSUserDefaults.standardUserDefaults()
func saveToFile<T>(object: [T], key: String) -> String {
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(object as! AnyObject)
self.userDefaults.setObject(encodedData, forKey: key)
self.userDefaults.synchronize()
return "oi"
}
func loadFromFile<T>(key: String) -> [T]? {
let decoded = self.userDefaults.objectForKey(key) as! NSData
if let decodedProducts = NSKeyedUnarchiver.unarchiveObjectWithData(decoded) as? [T] {
return decodedProducts
}
return nil
}
}
What am I doing wrong?
Thank you
Your funcs are not static function, so, you must create object to use these functions.
let xxx = DataFile()
xxx.loadFromFile(<#T##key: String##String#>)