Unwrapping Plist and finding object - arrays

Getting hugely frustrated trying to translate objective c to swift. I have the following code that works for objective c.
NSMutableArray *path = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Sequence List" ofType:#"plist"]];
//Shuffle the array of questions
numberSequenceList = [self shuffleArray:path];
currentQuestion = currentQuestion + 1;
if (Round==1) {
//Take first object in shuffled array as the first question
NSMutableArray *firstQuestion = [[NSMutableArray alloc] initWithArray:[numberSequenceList objectAtIndex:0]];
//Find question and populate text view
NSString *string = [firstQuestion objectAtIndex:0];
self.lblNumber.text = string;
//Find and store the answer
NSString *findAnswer = [firstQuestion objectAtIndex:1];
Answer = [findAnswer intValue];
}
But I can't seem to get this to work in swift. I can pull out the contents of the plist using
var path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist")
But I can't see that there is an equivalent to objectAtIndex in swift. If I try the following I get an error message advising "string does not have a member named subscript", which apparently means I need to unwrap path.
let firstQuestion = path[0]

The methods you are calling, like NSBundle.mainBundle().pathForResource, return optionals, because they might fail. In Objective-C, that failure is indicated by a nil, whereas Swift uses optionals.
So in your example:
var path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist")
path is of type Optional<String> (or String?) and not of type String. Optional<String> doesn’t have a subscript method (i.e. doesn’t support [ ]). To use the string within, you have to check if the optional contains a value (i.e. the call to pathForResource was successful):
// the if let syntax checks if the optional contains a valid
if let path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist”) {
// use path, which will now be of type String
}
else {
// deal with pathForResource failing
}
You can read more about optionals in introduction of the Swift book.

You haven't translated the entire first line from Objective-C. You are missing the call to NSMutableArray which creates the array from the contents of the file. The original code is confusing because it calls the contents of the file path when it is really questions. Try this:
if let path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist") {
let questions = NSMutableArray(contentsOfFile: path)
//Shuffle the array of questions
numberSequenceList = self.shuffleArray(questions)
currentQuestion = currentQuestion + 1
if Round == 1 {
//Take first object in shuffled array as the first question
let firstQuestion = numberSequenceList[0] as NSArray
//Find question and populate text view
let string = firstQuestion[0] as NSString
self.lblNumber.text = string
//Find and store the answer
let findAnswer = firstQuestion[1] as NSString
Answer = findAnswer.intValue
}
}

Related

Returning decoded data of multiple types not working

I am downloading data from Firebase and storing into UserDefaults. I am encoding and decoding it properly, and I can retrieve the data once its decoded. The problem I am stuck on is how to "return" any of the save values when needed. The struct I used to store that data contains 3 different types of value types: Bool String and Int.
There are parts of my app where I am going to need to retrieve any one of the different value types, depending on the scenario. I wrote a function that I was hoping to use to pull data from the decoded object when needed. Since the data is already saved as the correct value type. I can't seem to find a way to make the "return" change or be agnostic depending on the value type. I did some reading on Generics in Swift, but I am still unsure if that is the right solution. I have outlined my code below to explain further. Any suggestions or solutions would be much appreciated.
This function below is what I am referring to where I am stuck. I put the return type as Any, to experiment and see what would happen. Its not working right since the data is already formatted with the correct type so when I retrieve it using Any it would need me to convert it back to a String Bool or Int. Additionally, the data: UserData is an enum I made to allow selection, so that I could use dot notation to select whichever item I would want.
static func getUserInfo(data: UserData) -> Any { **// <- Return type here, also data is an enum below**
if let savedPerson = defaults.object(forKey: "userInfo") as? Data {
let decoder = JSONDecoder()
if let specificUser = try? decoder.decode(User.self, from: savedPerson) {
switch data {
case .number:
return specificUser.mobile
case .name:
return specificUser.mobile
case .pictureUrl:
return specificUser.pictureUrl!
case .accountType:
return specificUser.accountType
case .isEmailVarified:
return specificUser.isEmailVarified
default:
break
}
}
}
return ""
}
Here is the UserData enum:
enum UserData {
case number
case name
case pictureUrl
case accountType
case isEmailVarified
}
Here is the command I was intended to use to "retrieve" whatever option I wanted
let number = SaveToDefaults.getUserInfo(data: .mobile) as! String
myLabel.text = number
EDIT:
Here is the original struct that that all of the data is retrieved from Firebase with and then encoded into UserDefaults
struct User: Encodable, Decodable {
var uid: String
var name: String
var email: String
var mobile: String
var pictureUrl: String?
var accountType: AccountType!
var token: String?
var stripeId: String?
var pending: PendingStatus?
var isEmailVarified: Bool?
init(uid: String, dictionary: [String: Any]) {
self.uid = uid
self.name = dictionary["name"] as? String ?? ""
self.email = dictionary["email"] as? String ?? ""
self.mobile = dictionary["mobile"] as? String ?? ""
self.pictureUrl = dictionary["pictureUrl"] as? String ?? ""
if let index = dictionary["accountType"] as? Int {
self.accountType = AccountType(rawValue: index)
}
self.token = dictionary["token"] as? String ?? ""
self.stripeId = dictionary["stripe_id"] as? String ?? ""
if let pendingUser = dictionary["pendingUser"] as? Int {
self.pending= PendingStatus(rawValue: accountonline)
}
if let driverstatus = dictionary["isDriverPending"] as? Int {
self.driverPendingStatus = DriverStatus(rawValue: driverstatus)
}
self.isEmailVerified = false
}
}
There are some languages, like PHP, which let you return any data type in a single function. Generally speaking, languages that allow this get a bad reputation for maintainability.
That disclaimer out of the way:
If you’re really just displaying the result as a String on the screen, there’s probably nothing inherently evil in using your case statement to cast each value as a string and return that.
E.g.
case .isEmailVarified:
return String(specificUser.isEmailVarified) // EDITED to include a non-String variable after you EDITED your question to include struct.
EDIT:
I agree with the other commenters who say passing the Struct around instead of individual values is a good thing. One trick you could try, assuming you can target iOS 14, is to take advantage of the new #AppStorage construct instead of userDefaults. This might just be personal preference on my part, but the below feels pretty legible.
#AppStorage("user") var user = ""
// to persist your object as JSON string
let u = try! JSONEncoder().encode(userObjectFromFirebase)
result = String(data: u, encoding: .utf8) ?? ""
self.user = result
// to fetch your object as User object
let u = try! JSONDecoder().decode(User.self, from: self.user.data(using: .utf8)!)
// reference your values
myLabel.text = u.number
myLabel2.text = String(u.isEmailVerified)

I need to show information without brackets

I have code that get weather api but information from "weather" prints "id" and "main" information in console and also in text label with brackets in console
(
250
)
Here is some relevant code:
if let weather = jsonObj.value(forKey: "weather") as? NSArray {
let idObject = weather.value(forKey: "id")
// DispatchQueue.main.sync {
// self.weatherLabel.text = idObject as? String }
print (idObject)
let mainObject = weather.value(forKey: "main")
print(mainObject as Any)
}
Below, in the second half of this answer, I’ll describe what the issue is in your code snippet. But you are using Swift, so you shouldn’t be using NSDictionary and NSArray at all. You should use JSONDecoder to parse your json, and deal solely with Swift objects.
So you might define structures like so:
struct WeatherReport: Decodable {
let id: Int
let main: String
}
struct ResponseObject : Decodable {
let weather: [WeatherReport]
}
And decode it like so:
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: data)
for weatherReport in responseObject.weather {
print(weatherReport.id)
}
} catch {
print(error)
}
Now, my definition of WeatherReport is undoubtedly incorrect, as it needs to match your JSON structure, which you haven’t shared with us. For example, I’m assuming your id values are numeric (no quotes around them; we can’t tell by looking at your output). Also, you should check the documentation and figure out what the JSON looks like if there was an error. So you’d have to check that. But hopefully this illustrates the idea.
Regarding why you’re seeing the ( and ) around your values, that’s because the objects you are printing are, themselves, NSArray objects. When you call value(forKey:) on a NSArray of NSDictionary objects, it will go through the array and grab the value associated with that key for each dictionary in your array, and will return an array of values. Hence the ( and ) in the output. Sure, your “array of dictionaries” appears to currently have only one dictionary in it, but the fact that it is an array suggests that it’s possible that weather can possibly contain multiple dictionaries.
Consider this over-simplified version of your JSON:
{
"weather": [
{
"id": 42,
"main": "Rain"
},
{
"id": 520
"main": "Cloudy"
}
]
}
Thus
if let weather = jsonObj.value(forKey: "weather") as? NSArray {
let identifiers = weather.value(forKey: "id")
print (identifiers)
}
Will output
(
42,
520
)
Now, your array of dictionaries obviously only has one dictionary at this point, but this illustrates what’s going on.
If you don’t want the brackets, get the firstObject from the array:
guard
let weather = jsonObj.value(forKey: "weather") as? NSArray,
let weatherReport = weather.firstObject as? NSDictionary
else {
return
}
if let idObject = weatherReport.value(forKey: "id") as? Int {
print(idObject)
}
Or, if you want to print all of them:
guard
let weather = jsonObj.value(forKey: "weather") as? NSArray
else {
return
}
for object in array {
if let weatherReport = weather as? NSDictionary, let identifier = weatherReport["id"] as? Int {
print(identifier)
}
}
This is how one navigates a NSArray of NSDictionary objects. But, as described at the beginning of my answer, you shouldn’t be using these NS types at all. Get your raw Data, and decode it to model object types using JSONDecoder, instead.

How do I convert objective-c `sortedArrayUsingSelector:NSSelectorFromString` to swift?

I trying to convert objective-ccode toswiftand got stuck on this line ofobjective-c` code:
NSArray *array = [[[InternalContactsHandler sharedBuffer] contacts] sortedArrayUsingSelector:NSSelectorFromString(#"string:")];
I try to do something similar using swift:
var array = InternalContactsHandler.sharedBuffer()?.contacts()?.sorted(by: NSSelectorFromString("string"))
but I get the following error:
Cannot convert value of type 'Selector' to expected argument type '(Any, Any) throws -> Bool'
Not sure what it wants me to throw or how to write it properly in code. Any ideas?
Edit
Trying to use the #selector code produce the same result:
var tempData = InternalContactsHandler.sharedBuffer()?.contacts()?.sorted( by: #selector(Contact.nameOfContactCompare:))
Cannot convert value of type 'Selector' to expected argument type '(Any, Any) throws -> Bool'
Contacts.nameOfContactCompare is in objective-C and looks like this:
-(NSComparisonResult)nameOfContactCompare:(Contact*)c{
NSString *f1 = [firstName stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
NSString *l1 = [lastName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *f2 = [c.firstName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *l2 = [c.lastName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (ABPersonGetSortOrdering() == kABPersonSortByFirstName) {
return [self sortPrio1contact1:f1 prio2c1:l1 prio3c1:company prio1c2:f2 prio2c2:l2 prio3c2:c.company];
} else {
return [self sortPrio1contact1:l1 prio2c1:f1 prio3c1:company prio1c2:l2 prio2c2:f2 prio3c2:c.company];
}
}
I have added the function in the .h file like this so that it can be used in swift:
-(NSComparisonResult)nameOfContactCompare:(Contact*)c;
The old objective-c code works:
NSArray *array = [[[InternalContactsHandler sharedBuffer] contacts] sortedArrayUsingSelector:NSSelectorFromString(#"nameOfContactCompare")];
Why not for swift?
You can sort your array by property like this:
yourArray = yourArray.sort({ $0.yourString.lowercased() < $1.yourString.lowercased() })
The .lowercased() is to make sure that case sensitive won't be considered.
replace NSSelectorFromString("string") with #selector(string) or #selector(SomeClass.string)
How do we use of NSSelectorFromString in swift?

swift array.removeAtIndex error

How come I'm getting the error "NSArray does not have a member named 'removeAtIndex'. How can I fix this? The error is on the fourth last line. Sorry if my question is stupid, I'm fairly new to programming. I appreciate all the help I get.
import Foundation
let userDefaults = NSUserDefaults.standardUserDefaults()
func isAppAlreadyLaunchedOnce()->Bool{
let defaults = NSUserDefaults.standardUserDefaults()
if let isAppAlreadyLaunchedOnce = defaults.stringForKey("isAppAlreadyLaunchedOnce"){
println("App already launched")
return true
}
else{
defaults.setBool(true, forKey: "isAppAlreadyLaunchedOnce")
println("App launched first time")
return false
}
}
struct newFactBook {
let factsArray = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the earth when the Great Pyramid was being built."]
}
var checkLaunch = isAppAlreadyLaunchedOnce()
var oldFunFactsArray = []
if(checkLaunch == false){
oldFunFactsArray = newFactBook().factsArray
}
else if (checkLaunch == true){
oldFunFactsArray = userDefaults.objectForKey("key") as! NSArray
}
func randomFacts1() -> (String, Int){
var unsignedArrayCount = UInt32(oldFunFactsArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return (oldFunFactsArray[randomNumber] as! String, randomNumber)
}
oldFunFactsArray.removeAtIndex[randomFacts1().1] //error here
userDefaults.setObject(oldFunFactsArray, forKey:"key")
userDefaults.synchronize()
println(oldFunFactsArray)
We have some problems here:
1 How to invoke a method
removeAtIndex is a method that accepts an Int as parameters. It cannot be invoked this way
removeAtIndex[randomFacts1().1]
instead you should write
removeAtIndex(randomFacts1().1)
2. The type of oldFunFactsArray is NSArray and it's wrong.
Intact when you write this:
var oldFunFactsArray = []
Swift does infer this:
var oldFunFactsArray : NSArray = []
But at this point you have an immutable NSArray so it does not have the removeAtIndex method.
Since you are using Swift I suggest you to declare the var oldFunFactsArray as follow:
var oldFunFactsArray : [String]
if checkLaunch == false {
oldFunFactsArray = newFactBook().factsArray
} else {
oldFunFactsArray = userDefaults.objectForKey("key") as! [String]
}
Please note that here I am declaring a Swift array of String(s). Since I use the var keyword this array will be mutable and we will be able to invoke removeAtIndex later on.
Also note that in the else branch I am force-casting the result of objectForKey to [String]. It will be fine since I see, below, you are writing the oldFunFactsArray in that slot.
Hope this helps.
You need to use NSMutableArray to use this method.
NSArray is not Mutable (can not change its content after it is intialized).

Sorting NSMutableArray with NSDictionaries that contains Numbers as String?

Have a little Problem and can´t find a working solution :(
I have a NSMutableArray like:
{
Entfernung = 129521;
"Event_DATE" = "2014-03-23";
"Event_ID" = 1;
"Event_KAT" = 1;
"Event_NAME" = achtzehn;
},
{
Entfernung = 112143;
"Event_DATE" = "2014-03-24";
"Event_ID" = 2;
"Event_KAT" = 2;
"Event_NAME" = neunzehn;
}
How can i sort this Array with the object "Entfernung"?
Thx 4 help!
Gerhard
Try something like this;
NSArray *stuff = .... //your array here;
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:#"Entfernung" ascending:YES comparator:^NSComparisonResult(id obj1, id obj2) {
//depending on the number stored in the string, you might need the floatValue or doubleValue instead
NSNumber *num1 = #([(NSString*)obj1 integerValue]);
NSNumber *num2 = #([(NSString*)obj2 integerValue]);
return [num1 compare:num2];
}];
NSArray *sortedStuff = [[stuff sortedArrayUsingDescriptors:#[sorter]];
The easiest I'd say would be to define a compare method on Entfernung class and then use - (void)sortUsingSelector:(SEL)comparator If you already have a function which accepts two objects (say your NSDictionary object) then I'd do the sort like this - (void)sortUsingFunction:(NSInteger (*)(id, id, void *))compare context:(void *)context
Give a man a fish, and he can eat today. Tell him how to fish, and he has to do the work himself for the rest of his life...
In Xcode, look at the help menu. In the help menu, you find an item "Documentation and API reference". There you type in "NSMutableArray", then you search for "sort". Which gives you five methods:
– sortUsingDescriptors:
– sortUsingComparator:
– sortWithOptions:usingComparator:
– sortUsingFunction:context:
– sortUsingSelector:
You can click on each one and read the description. The most straightforward to use is sortUsingComparator: which comes with a nice bit of sample code that you adapt for your purposes.

Resources