Swift 4 Key Value Dictionary stored in User Defaults - ios

I am new to Swift and have a requirement to store a database of key value pairs. The key value pairs are a name with a corresponding 4 digit number in database that remains in memory after the app is excited. I am thinking to use a dictionary with the name as the key and the 4 digit numbers as the value. These are then stored in the iPad flash memory using the user defaults class.
Below is the code that I’ve currently developed. The code that adds to the database compiles ok but the code that checks the name and number for a match in the database won't compile due to the following message (Value of optional type '[Any]?' not unwrapped; did you mean to use '!' or '?'?) which is because of this line of code (if let databaseCheck = database[name]). Ive obviously tried unwrapping but can't seem to shake the error message.
Anyone got any ideas whats causing the error or any issues with the approach?
public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
var foundInDatabaseFlag: Bool = false
let database = UserDefaults.standard.array(forKey: "Database")
if let databaseCheck = database[name]
{
if (databaseCheck == number)
{
foundInDatabaseFlag = true
}
}
return foundInDatabaseFlag
}
public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry: [String: String] = [:]
newEntry[name] = number
UserDefaults.standard.set(newEntry, forKey: "Database")
}

There is a major mistake. You save a dictionary but retrieve an array.
Apart from that a dictionary retrieved from UserDefaults is [String:Any] by default, you have to conditional downcast the object.
The code checks if there is a dictionary in UserDefaults and if there is the requested key in one expression
public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
guard let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String],
let databaseCheck = database[name] else { return false }
return databaseCheck == number
}
Another mistake is that you are always overwriting the entire dictionary in UserDefaults. If you want to save multiple key-value pairs you have to read the dictionary first.
public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry : [String: String]
if let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String] {
newEntry = database
} else {
newEntry = [:]
}
newEntry[name] = number
UserDefaults.standard.set(newEntry, forKey: "Database")
}
Side note: The parameter labels are highly recommended in Swift for better readability.

Related

How to use multiple key for the same value in an Swift Dictionary?

How can I store user records into Swift Dictionary, but index them by two type of keys: userID and firstName? Is it possible?
If a value is removed both expression users[aUserID] and user[aFirstName] should return nil.
Dictionaries don't care about unique values, they only care about unique keys. So you can have as many identical values in a dictionary as you like, as long as their keys are all different.
The way I would approach your problem is by hiding all the necessary logic to keep the dictionary keys in sync in a wrapper class:
struct User {
let userId: Int
let firstName: String
// more user properties
}
class UserDict {
private var dict = [String: User]()
subscript(key: String) -> User? {
return dict[key]
}
subscript(key: Int) -> User? {
return dict["\(key)"]
}
func add(_ user: User) {
dict[user.firstName] = user
dict["\(user.userId)"] = user
}
func remove(forKey key: String) {
if let user = dict[key] {
dict.removeValue(forKey: user.firstName)
dict.removeValue(forKey: "\(user.userId)")
}
}
func remove(forKey key: Int) {
remove(forKey: "\(key)")
}
var count: Int {
return dict.count / 2
}
}
You can have multiple keys for the same value, consider this example:
class Value {
var name = "John"
}
var dictionary: [String: Value] = [:]
var value1 = Value()
dictionary["key1"] = value1
dictionary["key2"] = value1
value1.name = "Theresa"
dictionary["key1"]?.name // "Theresa"
dictionary["key2"]?.name // "Theresa"
For the second part of your question:
If value is removed both expression users[aUserID] and user[aFirstName] should return nil.
When you do this dict["key1"] = nil, the value for the key2 is still there and would be value1, so you could create a custom subscript that would handle that by finding identical values in the dictionary.

Conditional cast from string to string always succeds in swift 3

func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
if let key = key as? String {
userDefaults.removeObject(forKey: key)
} else {
#if DEBUG
NSLog("\(key)")
#endif
}
}
}
I'm getting this warning. can anyone suggest me how to avoid this warnoing
All keys in UserDefaults must be of type String. So key is declared as a String. So attempting to cast it to a String is pointless. Hence the warning.
All you need is:
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
userDefaults.removeObject(forKey: key)
}
}
There is no need to cast something to the type that it is already known (to the compiler) to have.
Just remove the whole condition and use your key directly.
Since the keys in the UserDefault should of type String, casting the key to string is of no use, and hence you are getting this warning.
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key, _) in dict {
userDefaults.removeObject(forKey: key)
}
}
It will always show waring because dictionaryRepresentation() return [String : Any].
So when you cast from string to string it will definitely show warning.
for more see this -> https://developer.apple.com/documentation/foundation/userdefaults/1415919-dictionaryrepresentation
I had the same issue with a private function in Swift 5 and I found a solution working for me.
The solution was to change the value to optional.
I added a question mark after the type I was looking for. (as String"?")
You can see an example here :
private func doSomeThing(completion: #escaping (String) -> ()) {
let Something = somethingElse;
if let anoterThing = something as String?{
completion(anoterThing)
}else{
completion("Error at private func doSomeThing")
}
}
You can find more pieces of information here:
https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html
Swift: difference as String? vs. as? String
Downcasting in Swift with as and as?
Best Regards

Override old object in array swift ios

I have an array of custom objects. For simplicity, lets say each object has 3 properties : id, timestamp and text. I am now obtaining a JSON response from my server which contains updated text for my objects in the array. I need to do the following :
Obtain the object with the given id from my array.
Set the text of the object to the updated text WITHOUT changing any of its other properties.
Override the old object with the new and updated one.
I have found the following extension which allows me to find the object in my array.
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
I am then using the following logic to obtain the item from the array.
let my_object = questionImageObjects.find({$0.id== myId})
Now I set the text using my_object.text = currText.
The last step is to override the old object in the array with the updated one. This is where I am stuck.
The extension find will return a copy of the original struct. Whatever you do to it, the original won't be affected. You can modify it through the index:
struct DataModel: CustomStringConvertible {
var id: Int
var timestamp: NSDate
var text: String
init(id: Int, timestamp: NSDate, text: String) {
self.id = id
self.timestamp = timestamp
self.text = text
}
// For debug purposes
var description: String {
get { return "( id = \(id), timestamp = \(timestamp), text = \(text) )" }
}
}
// Initial data
var arr = [
DataModel(id: 1, timestamp: NSDate(), text: "Anakin Skywalker"),
DataModel(id: 2, timestamp: NSDate(), text: "Luke Skywalker")
]
// Now the server returns a JSON that says Anakin has turned to the Dark Side
let jsonString = "{ \"id\": 1, \"text\": \"Darth Vader\" }"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// Update the text
if let dict = json as? [String: AnyObject],
let id = dict["id"] as? Int,
let newText = dict["text"] as? String,
let index = (arr.indexOf { $0.id == id }) {
arr[index].text = newText
}
print(arr)
If those if lets confuse you, here's a step-by-step guide:
if let dict checks that the JSON can be converted into a Dictionary
let id checks that it has an id key of type Int
let newText checks that it has a text key of type String
let index checks that the array contains an element with that id

Array from dictionary keys in swift

Trying to fill an array with strings from the keys in a dictionary in swift.
var componentArray: [String]
let dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Components", ofType: "plist")!)
componentArray = dict.allKeys
This returns an error of: 'AnyObject' not identical to string
Also tried
componentArray = dict.allKeys as String
but get: 'String' is not convertible to [String]
Swift 3 & Swift 4
componentArray = Array(dict.keys) // for Dictionary
componentArray = dict.allKeys // for NSDictionary
With Swift 3, Dictionary has a keys property. keys has the following declaration:
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
A collection containing just the keys of the dictionary.
Note that LazyMapCollection that can easily be mapped to an Array with Array's init(_:) initializer.
From NSDictionary to [String]
The following iOS AppDelegate class snippet shows how to get an array of strings ([String]) using keys property from a NSDictionary:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let string = Bundle.main.path(forResource: "Components", ofType: "plist")!
if let dict = NSDictionary(contentsOfFile: string) as? [String : Int] {
let lazyMapCollection = dict.keys
let componentArray = Array(lazyMapCollection)
print(componentArray)
// prints: ["Car", "Boat"]
}
return true
}
From [String: Int] to [String]
In a more general way, the following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with string keys and integer values ([String: Int]):
let dictionary = ["Gabrielle": 49, "Bree": 32, "Susan": 12, "Lynette": 7]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection)
print(stringArray)
// prints: ["Bree", "Susan", "Lynette", "Gabrielle"]
From [Int: String] to [String]
The following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with integer keys and string values ([Int: String]):
let dictionary = [49: "Gabrielle", 32: "Bree", 12: "Susan", 7: "Lynette"]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection.map { String($0) })
// let stringArray = Array(lazyMapCollection).map { String($0) } // also works
print(stringArray)
// prints: ["32", "12", "7", "49"]
Array from dictionary keys in Swift
componentArray = [String] (dict.keys)
You can use dictionary.map like this:
let myKeys: [String] = myDictionary.map{String($0.key) }
The explanation:
Map iterates through the myDictionary and accepts each key and value pair as $0. From here you can get $0.key or $0.value. Inside the trailing closure {}, you can transform each element and return that element. Since you want $0 and you want it as a string then you convert using String($0.key). You collect the transformed elements to an array of strings.
dict.allKeys is not a String. It is a [String], exactly as the error message tells you (assuming, of course, that the keys are all strings; this is exactly what you are asserting when you say that).
So, either start by typing componentArray as [AnyObject], because that is how it is typed in the Cocoa API, or else, if you cast dict.allKeys, cast it to [String], because that is how you have typed componentArray.
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
dict.keys.sorted()
that gives [String]
https://developer.apple.com/documentation/swift/array/2945003-sorted
From the official Array Apple documentation:
init(_:) - Creates an array containing the elements of a sequence.
Declaration
Array.init<S>(_ s: S) where Element == S.Element, S : Sequence
Parameters
s - The sequence of elements to turn into an array.
Discussion
You can use this initializer to create an array from any other type that conforms to the Sequence protocol...You can also use this initializer to convert a complex sequence or collection type back to an array. For example, the keys property of a dictionary isn’t an array with its own storage, it’s a collection that maps its elements from the dictionary only when they’re accessed, saving the time and space needed to allocate an array. If you need to pass those keys to a method that takes an array, however, use this initializer to convert that list from its type of LazyMapCollection<Dictionary<String, Int>, Int> to a simple [String].
func cacheImagesWithNames(names: [String]) {
// custom image loading and caching
}
let namedHues: [String: Int] = ["Vermillion": 18, "Magenta": 302,
"Gold": 50, "Cerise": 320]
let colorNames = Array(namedHues.keys)
cacheImagesWithNames(colorNames)
print(colorNames)
// Prints "["Gold", "Cerise", "Magenta", "Vermillion"]"
Swift 5
var dict = ["key1":"Value1", "key2":"Value2"]
let k = dict.keys
var a: [String]()
a.append(contentsOf: k)
This works for me.
NSDictionary is Class(pass by reference)
Dictionary is Structure(pass by value)
====== Array from NSDictionary ======
NSDictionary has allKeys and allValues get properties with
type [Any].
let objesctNSDictionary =
NSDictionary.init(dictionary: ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"])
let objectArrayOfAllKeys:Array = objesctNSDictionary.allKeys
let objectArrayOfAllValues:Array = objesctNSDictionary.allValues
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
====== Array From Dictionary ======
Apple reference for Dictionary's keys and values properties.
let objectDictionary:Dictionary =
["BR": "Brazil", "GH": "Ghana", "JP": "Japan"]
let objectArrayOfAllKeys:Array = Array(objectDictionary.keys)
let objectArrayOfAllValues:Array = Array(objectDictionary.values)
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
This answer will be for swift dictionary w/ String keys. Like this one below.
let dict: [String: Int] = ["hey": 1, "yo": 2, "sup": 3, "hello": 4, "whassup": 5]
Here's the extension I'll use.
extension Dictionary {
func allKeys() -> [String] {
guard self.keys.first is String else {
debugPrint("This function will not return other hashable types. (Only strings)")
return []
}
return self.flatMap { (anEntry) -> String? in
guard let temp = anEntry.key as? String else { return nil }
return temp }
}
}
And I'll get all the keys later using this.
let componentsArray = dict.allKeys()
// Old version (for history)
let keys = dictionary.keys.map { $0 }
let keys = dictionary?.keys.map { $0 } ?? [T]()
// New more explained version for our ducks
extension Dictionary {
var allKeys: [Dictionary.Key] {
return self.keys.map { $0 }
}
}

Swift - Checking unmanaged address book single value property for nil

I'm relative new to iOS-Development and swift. But up to this point I was always able to help myself by some research on stackoverflow and several documentations and tutorials.
However, there is a problem I couldn't find any solution yet.
I want to get some data from the users addressbook (for example the single value property kABPersonFirstNameProperty). Because the .takeRetainedValue() function throws an error if this contact doesn't have a firstName value in the addressbook, I need to make sure the ABRecordCopyValue() function does return a value. I tried to check this in a closure:
let contactFirstName: String = {
if (ABRecordCopyValue(self.contactReference, kABPersonFirstNameProperty) != nil) {
return ABRecordCopyValue(self.contactReference, kABPersonFirstNameProperty).takeRetainedValue() as String
} else {
return ""
}
}()
contactReference is a variable of type ABRecordRef!
When an addressbook contact provides a firstName value, everything works fine. But if there is no firstName, the application crashes by the .takeRetainedValue() function. It seems, that the if statement doesn't help because the unmanaged return value of the ABRecordCopyValue() function is not nil although there is no firstName.
I hope I was able to make my problem clear. It would be great if anyone could help me out with some brainwave.
If I want the values associated with various properties, I use the following syntax:
let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String
let last = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String
Or you can use optional binding:
if let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
// use `first` here
}
if let last = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
// use `last` here
}
If you really want to return a non-optional, where missing value is a zero length string, you can use the ?? operator:
let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String ?? ""
let last = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String ?? ""
I got it through this function here:
func rawValueFromABRecordRef<T>(recordRef: ABRecordRef, forProperty property: ABPropertyID) -> T? {
var returnObject: T? = nil
if let rawValue: Unmanaged<AnyObject>? = ABRecordCopyValue(recordRef, property) {
if let unwrappedValue: AnyObject = rawValue?.takeRetainedValue() {
println("Passed: \(property)")
returnObject = unwrappedValue as? T
}
else {
println("Failed: \(property)")
}
}
return returnObject
}
You can use it in your property like this:
let contactFirstName: String = {
if let firstName: String = rawValueFromABRecordRef(recordRef, forProperty: kABPersonFirstNameProperty) {
return firstName
}
else {
return ""
}
}()
Maybe it's more than just answering to your question, but this is how I deal with the address book.
I've defined a custom operator:
infix operator >>> { associativity left }
func >>> <T, V> (lhs: T, rhs: T -> V) -> V {
return rhs(lhs)
}
allowing to chain multiple calls to functions in a more readable way, for instance:
funcA(funcB(param))
becomes
param >>> funcB >>> funcA
Then I use this function to convert an Unmanaged<T> to a swift type:
func extractUnmanaged<T, V>(value: Unmanaged<T>?) -> V? {
if let value = value {
var innerValue: T? = value.takeRetainedValue()
if let innerValue: T = innerValue {
return innerValue as? V
}
}
return .None
}
and a counterpart working with CFArray:
func extractUnmanaged(value: Unmanaged<CFArray>?) -> [AnyObject]? {
if let value = value {
var innerValue: CFArray? = value.takeRetainedValue()
if let innerValue: CFArray = innerValue {
return innerValue.__conversion()
}
}
return .None
}
and this is the code to open the address book, retrieve all contacts, and for each one read first name and organization (in the simulator firstName always has a value, whereas department doesn't, so it's good for testing):
let addressBook: ABRecordRef? = ABAddressBookCreateWithOptions(nil, nil) >>> extractUnmanaged
let results = ABAddressBookCopyArrayOfAllPeople(addressBook) >>> extractUnmanaged
if let results = results {
for result in results {
let firstName: String? = (result, kABPersonFirstNameProperty) >>> ABRecordCopyValue >>> extractUnmanaged
let organization: String? = (result, kABPersonOrganizationProperty) >>> ABRecordCopyValue >>> extractUnmanaged
println("\(firstName) - \(organization)")
}
}
Note that the println statement prints the optional, so you'll see in the console Optional("David") instead of just David. Of course this is for demonstration only.
The function that answers to your question is extractUnmanaged, which takes an optional unmanaged, unwrap it, retrieves the retained value as optional, unwrap it, and in the end attempts a cast to the target type, which is String for the first name property. Type inferral takes care of figuring out what T and V are: T is the type wrapped in the Unmanaged, V is the return type, which is known because specified when declaring the target variable let firstName: String? = ....
I presume you've already taken care of checking and asking the user for permission to access to the address book.

Resources