NSUserDefaults arrayForKey value of anyObject has no member removeAtIndex (Swift) - ios

I am using the NSUserDefaults to store an array (of strings), and when loading it, it appears to be interpreted as an AnyObject instead of an Array. I don't understand how this is possible because I am using the arrayForKey method for my default, which I thought was supposed to return an array?
The exact error I am getting is:
Value of type '[AnyObject]?' has no member 'removeAtIndex'
which is occurring at shoppingListDefaults.arrayForKey("ShoppingList").removeAtIndex(indexPath.row)
let shoppingListDefaults = NSUserDefaults.standardUserDefaults()
let deleteAction = UITableViewRowAction(style: .Normal, title: "Delete") { (rowAction:UITableViewRowAction, indexPath:NSIndexPath) -> Void in
shoppingListDefaults.arrayForKey("ShoppingList").removeAtIndex(indexPath.row)
self.slItems.reloadData() // `slItems` is the IBOutlet for a UITableView
}
deleteAction.backgroundColor = UIColor.redColor()
return [deleteAction]

arrayForKey returns an optional, so you have to unwrap it before you can call anything else on it. You also can't directly edit the array you get from defaults since it is immutable. You have to edit the array and then update defaults with the update array.
Try:
if var list = shoppingListDefaults.arrayForKey("ShoppingList") {
list.removeAtIndex(indexPath.row)
shoppingListDefaults.setArray(list, forKey: "ShoppingList")
}

This array is immutable. You need to retrieve it from defaults, remove object, and set back modified array to defaults.

Related

Ambiguous use of 'mutableCopy()' Swift3

I tried to update Swift 3 and I got the following error :
Ambiguous use of 'mutableCopy()'
Before update to swift 3. It runs well.
Swift 2.3
NSUserDefaults.standardUserDefaults().objectForKey("listsavednews")?.mutableCopy() as! NSMutableArray
Swift 3.0
(UserDefaults.standard.object(forKey: "listsavednews")? as AnyObject).mutableCopy() as! NSMutableArray
I found that mutableCopy in Swift3 return Any that doesnt have method mutableCopy() so that it needs to cast to AnyObject.
Any helps thanks.
I dont know why I can't comment.
Thanks all, I'll be using :
UserDefaults.standard.mutableArrayValue(forKey: "listsavednews")
mutableCopy is an Objective-C method from NSObject. There's little reason to use it in Swift 3.
Since you are dealing with UserDefaults and mutableCopy, you must be dealing with either an array or dictionary. Or it could be a string.
The proper way to do this in Swift 3 is to use the proper UserDefaults method to get an array or dictionary. And assign the result to a var. That combination will give you a mutable array or mutable dictionary.
var someArray = UserDefaults.standard.array(forKey: "somekey")
or:
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey")
In the two above cases, you end up with an optional since there might not be any data for the given key. And you also get a non-specific array or dictionary which isn't ideal. It would be better to cast the result to the appropriate type.
Let's say you have an array of strings and you want an empty array if there is nothing currently in user defaults. You can then do:
var someArray = UserDefaults.standard.array(forKey: "somekey" as? [String]) ?? []
Adjust as necessary if the array contains something other than String.
If you actually have a dictionary, the code would be similar.
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey") as? [String:String] ?? [:]
If your original object is just a string, then you could do:
var someString = UserDefaults.standard.string(forKey: "somekey") ?? ""

NSUserDefaults optionals in Swift

I'm trying to deal with compiler in case of optional values. Task is very simple, my func fetches user defaults to appear them in tableview. If user launches app for the first time, it setts default values. Setting default values works fine (checked with print log), but fetching causes:
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm already worked with optionals for a long time, but, perhaps I'm still confused about them, 'cos I see that everything seems to be correct and even compiler says, that everything is ok.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
if (userDefs.objectForKey("gender") != nil) {
defsArray.append((userDefs.objectForKey("gender")?.stringValue)!)
defsArray.append((userDefs.objectForKey("age")?.stringValue)!)
defsArray.append((userDefs.objectForKey("online")?.stringValue)!)
}
else {
userDefs.setObject("Male", forKey: "gender")
userDefs.setObject("21-30", forKey: "age")
userDefs.setObject("Online", forKey: "online")
}
return defsArray
}
You are force unwrapping your optionals, and you should get them as strings before appending them to your array.
A cleaner way to set the defaults would be to coalesce the unwrapping of your optionals, Try the following approach:
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
defsArray.append(userDefs.stringForKey("gender") ?? "Male")
defsArray.append(userDefs.stringForKey("age") ?? "21-30")
defsArray.append(userDefs.stringForKey("online") ?? "Online")
return defsArray
}
The code above uses the coalesce (??) operator. If your optional, say userDefs.stringfForKey("gender"), returns nil, the coalesce operator will use the default value "Male".
Then at a later time you can save your user defaults (or create them) if they haven't been set before.
Also, is worth noticing that you should be unwrapping your optionals using the if let notation. Instead of comparing if its != nil, as this will prevent you from force unwrapping them inside the code block.
I hope this helps!
Apple highly recommends to set default values via registerDefaults of NSUserDefaults.
As soon as possible (at least before the first use) set the default values for example in applicationDidFinishLaunching:
let userDefs = NSUserDefaults.standardUserDefaults()
let defaultValues = ["gender" : "Male", "age" : "21-30", "online" : "Online"]
userDefs.registerDefaults(defaultValues)
The default values are considered until a new value is set.
The benefit is you have never to deal with optionals nor with type casting.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
return [userDefs.stringForKey("gender")!,
userDefs.stringForKey("age")!,
userDefs.stringForKey("online")!]
}
The forced unwrapping is 100% safe because none of the keys can ever be nil.
Please read Registering Your App’s Default Preferences in Preferences and Settings Programming Guide
You can create a default key "firstBootCompleted". Then use:
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.boolForKey("firstBootCompleted") == false) {
defaults.setObject(true, forKey: "firstBootCompleted")
// Initialize everything for your first boot below
} else {
// Initialize everything for not your first boot below
}
The reason this works is that boolForKey returns false when the object for key is nil
You are storing all the value as String and then at the time of fetching
you are typecast the value again in String
userDefs.objectForKey("gender")?.stringValue
A String can not be convert as String again.
try to access as
userDefs.stringForKey("gender")

Retrieving bool array from NSUserdefaults in swift

I have defined an array like this in my iOS app
var array=[Bool]()
and assigning some bool values to this.After that I am storing this array in my user defaults in this way.
userDefaults.setObject(dm.array, forKey: "array")
Now I want to retrievw this array. So I did like this
dm.array=userDefaults.arrayForKey("array") as! Array
But here im getting an error
Down cast from '[AnyObject]?' to 'Array' only unwraps optional; did you mean to use '!'?
array = userDefaults.objectForKey("array") as? [Bool] ?? [Bool]()

Parse pointer to object EXC_BAD_ACCESS

I have problem with parse. I have relation in my database between Event and Type. So, event has it's type, which consist of title and color(and other properties, but focus on relation)
Event:
#NSManaged var type: Type
Also, i have computer property eventColor:
var eventColor: UIColor{
return type?.typeColor ?? UIColor.clearColor()
}
And basic query:
override class func query() -> PFQuery? {
//1
let query = PFQuery(className: Event.parseClassName())
//2
query.includeKey("type")
//3
query.orderByDescending("startDate")
return query
}
Controller:
var events = [Event](){
didSet { eventsTableView.reloadData() }
}
Im making request:
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
self.activityIndicator.stopAnimating()
if let objects = objects as? [Event] where error == nil{
println("Count is: \(objects.count)")
for event in objects{
event.pinInBackground()
println("\(event.type)") //Here, type is available... and it prints out
}
self.events = objects // Property observer is called
}
else{
println("Handle error...")
}
})
And code that is called after table view reload:
tableView cellForRow....(){
let event = self.events[indexPath.row]
let color = event.eventColor // BAD ACCESS, because "type" dissappears.
}
So, after making this request, and assigning array of events to array in my view controller, i'm trying to read computer property event color and it fails(EXC_BAD_ACCESS). It seems like type object is available in block, which is executed after fetching events, but later it dissappears, and every time i want to read it, i have application runtime error.
I don't know, it feels like object is hold like weak in block(because i included "includeKey" and Type is downloaded), but later it's just immediatelly deallocated. What should i do, to prevent from deallocating type? I could make my own object of Type, not subclassing PFObject, but i believe that somewhere there should be the other, cleaner way.
Thank you
Edit:
Ok, so investigating a little bit more i discovered(cellForRow...)
I'm trying to get type and it's color property by subscripting PFObject:
let typeItem = eventItem["type"] as! PFObject
println("type is: \(typeItem)")
/*
type is: <Type: 0x7fe99bdf3330, objectId: dkEXw2r7zh, localId: (null)> {
color = "#F29C31";
name = swift;
}
*/
let colorFromType = typeItem["color"]
println("color: \(colorFromType)") //Works, returns valid value.
But writing it in other way:
let type = eventItem.type
let color = type?.color //Compiles, but runtime error:
'NSInvalidArgumentException', reason: '-[PFObject color]: unrecognized selector sent to instance 0x7fe99bdf3330'
println("type: \(type), color: \(color)")
So i'm not entirely sure, why this happens. It looks like this type property of Event could not be casted to Type, but i'm sure that's type of property.
EDIT, RESOLVED:
Ok, i'm so stupid, i didn't know about register subclass, and Type.registerSubclass resolved all problems.
It sounds like you need to typecast the object in the tableView cellForRow....() call.
Your call to .eventColor will fail if it doesn't have the cast, and the cast isn't saved to your self.events as far as I can see. Try this:
tableView cellForRow....(){
let event = self.events[indexPath.row] as! Event
let color = event.eventColor
}
Also, if you are using a subclass, you need to be sure to use registerSubclass ahead of time or your properties will not be available!

SWIFT: "removeAtIndex" don't work with (sender:UIButton)

#IBOutlet var items: [UIButton]
#IBAction func itemsHidden(sender: UIButton) {
sender.hidden = true
items.removeAtIndex(sender)
}
Hello.
For example, i have array of items.
The code has the error: "Cannot invoke 'removeAtIndex' with an argument list of type (UIButton)".
What i need to do, that "removeAtIndex" works?
Thanks...
A removeAtIndex method expects to get an index as a parameter.
If you want to remove an object use func removeObject(_ anObject: AnyObject)
EDIT
There's no removeObject in a swift's array (only in NSMutableArray).
In order to remove an element, you need to figure out it's index first:
if let index = find(items, sender) {
items.removeAtIndex(index)
}
You don't tell us the class of your items object.
I assume it's an Array. If not, please let us know.
As Artem points out in his answer, removeAtIndex takes an integer index and remove the object at that index. The index must be between zero and array.count-1
There is no removeObject(:) method for Swift Array objects because Arrays can contain the same entry at more than one index. You could use the NSArray method indexOfObject(:) to find the index of the first instance of your object, and then removeAtIndex.
If you're using Swift 2, you could use the indexOf(:) method, passing in a closure to detect the same object:
//First look for first occurrence of the button in the array.
//Use === to match the same object, since UIButton is not comparable
let indexOfButton = items.indexOf{$0 === sender}
//Use optional binding to unwrap the optional indexOfButton
if let indexOfButton = indexOfButton
{
items.removeAtIndex(indexOfButton)
}
else
{
print("The button was not in the array 'items'.");
}
(I'm still getting used to reading Swift function definitions that include optionals and reference protocols like Generator so the syntax of the above may not be perfect.)

Resources