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.
Related
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.
I found an error when I test some codes from Github.
class Profile {
let text: String
let date: String
let id: String?
init?(data: NSDictionary?) {
if let text = data?.valueForKeyPath(Test.Text) as? String {
self.text = text
if let date = data?.valueForKeyPath(Test.Created) as? String {
self.date = date
id = data?.valueForKeyPath(Test.ID) as? String
}
}else{
return nil
}
}
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
}
The line of init? shows the error "constants self.data used before being initialized."
And I create a similar class of it, like
class Context {
let words: String
init?(text:String?) {
if let words = text {
self.words = words
}else{
return nil
}
}
}
This time it shows " all stored properties of class instance must be initialized before returing nil from an initializer."
For the first one , there is a workaround that I can delete the else block and give each properties an empty value would fix the error. However it would have me change the properties mutable.
( I don't want it to be mutable)
And for the second example, I just insert self.word = ""before the line of return nil could also fix the error.
But I really wonder why these similar cases show the different errors and realize the logic of Swift, and how can I fix it correctly?
Thank you for helping me.
Try this version of the code.
Code 1:
class Profile {
var text: String = ""
var date: String = ""
var id: String? = ""
init?(data: NSDictionary?) {
if let text = data?.valueForKeyPath(Test.Text) as? String {
self.text = text
if let date = data?.valueForKeyPath(Test.Created) as? String {
self.date = date
id = data?.valueForKeyPath(Test.ID) as? String
}
}else{
return nil
}
}
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
}
Code 2:
class Context {
var words: String = ""
init?(text:String?) {
if let words = text {
self.words = words
} else {
return nil
}
}
}
York initializers are incomplete and that's why you get the message. Swift is type save, which means that all non optional properties must be initialized before the class is returned. Even when the class returns a nil. secondly, you can't call self (which is the class itself) if you haven't initialized the class. However, that should not be a problem in your case, since you've defined a root class. For your first class, please, implement the code like this and it should work.
class Profile {
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
let text: String
let date: String
let id: String?
init?(data: NSDictionary?) {
guard let tempText = data?.valueForKeyPath(Test.Text) as? String else {
text = ""
date = ""
id = nil
return nil
}
text = tempText
if let tempDate = data?.valueForKeyPath(Test.Created) as? String {
date = tempDate
id = data?.valueForKeyPath(Test.ID) as? String
} else {
date = ""
id = nil
}
}
}
For the second class you need to do a similar thing, which means in the else statement give words a value and it should be okay.
I've recently started programming with swift and am currently creating an application, which receives json data from a server. I was wondering about the best way to create the data model. First I tried creating a class and if leting all of the properties, but I found it to be too cumbersome (I have objects with 10-12 proeprties):
class Favourite {
var mArtikelText: String = ""
var mRid: String = ""
init(object: [String: AnyObject]) throws {
if let text = object["text"] as? String {
self.mArtikelText = text
}
if let id = object["id"] as? String {
self.mRid = id
}
}
}
Then I read about the new guard keyword and decided to try with it:
enum JSONResponseError: ErrorType {
case NilResponseValue
}
class Favourite {
var mArtikelText: String
var mRid: String
init(object: [String: AnyObject]) throws {
guard let text = object["text"] as? String else {
self.mArtikelText = ""
throw JSONResponseError.NilResponseValue
}
guard let rid = object["id"] as? String else {
self.mRid = ""
throw JSONResponseError
}
self.mArtikelText = text
self.mRid = rid
}
}
But I get All stored properties of a class instance must be initialized before throwing from an initializer. I guess I could silence the error if I initialize the properties, when I declare them, as I did in the first example, but (correct me if I'm wrong) I thought the guard is used to avoid exactly this approach.
Then I tried guarding all the properties at once:
class Favourite {
var mArtikelText: String
var mRid: String
init(object: [String: AnyObject]) throws {
guard case let (text as String, rid as String) = (object["text"], object["id"]) else {
self.mArtikelText = ""
self.mRid = ""
throw JSONResponseError.NilResponseValue
}
self.mArtikelText = text
self.mRid = rid
}
}
But I don't like this approach, because I want to leave only the faulty values and work with the rest (sometimes I receive nil values, that I won't be using anyway, so there is no point in throwing the whole answer away).
So, my question is, what is the best way to create a reliable model class from a json dictionary?
Is it possible to use an "OR" condition using Swift's if let?
Something like (for a Dictionary<String, AnyObject> dictionary):
if let value = dictionary["test"] as? String or as? Int {
println("WIN!")
}
This would make no sense, how would you be able to tell whether value is an Int or a String when you only have one if statement? You can however do something like this:
let dictionary : [String : Any] = ["test" : "hi", "hello" : 4]
if let value = dictionary["test"] where value is Int || value is String {
print(value)
}
(Tested in Swift 2.0)
You can also do this if you need to do different things depending on the types:
if let value = dictionary["test"] {
if let value = value as? Int {
print("Integer!")
} else if let value = value as? String {
print("String!")
} else {
print("Something else")
}
}
Unfortunately to my knowledge you cannot. You will have to use two separate if statements.
if let value = dictionary["test"] as? String {
doMethod()
} else if let value = dictionary["test"] as? Int {
doMethod()
}
There are multiple ways of getting around this problem. This is just one of them.
Please refer to the Apple documentation on Optional Chaining for more information on this special type of if statement. This is with using Swift 1.2
I have a function that loops through an array that contains either Strings or Dictionaries and sets values for a new Dictionary of type [String:AnyObject], using values from the original array as keys. sections is an array of custom Section objects and its method getValue always returns a String.
func getSectionVariables() -> [String:AnyObject] {
var variables = [String:AnyObject]()
for section in sections {
if let name = section.name as? String {
variables[name] = section.getValue()
} else if let name = section.name as? [String:String] {
for (k, v) in name {
if variables[k] == nil {
variables[k] = [String:String]()
}
if var dict = variables[k] as? [String:String] {
dict[v] = section.getValue() //This works, but of course copies variables by value and doesn't set variables[k]
}
(variables[k] as? [String:String])![v] = section.getValue() //Can't get this line to compile
}
}
}
return variables
}
If I try to cast variables[k] as a [String:String], the compiler insists that String is not convertible to DictionaryIndex<String, AnyObject>. I'm not sure why downcasting isn't working and why the compiler thinks I'm trying to use the alternate dictionary subscript syntax.
Note that this project is in Swift 1.1.
The general problem is that a "cast expression" does not yield an "l-value" and therefore cannot be assigned to. For example, consider the following simplified version of the code:
var x = "initial"
var y : AnyObject? = x
(y as? String) = "change"
You will get an error on the last line, because you cannot assign to a "cast expression".