Error : replaceObjectAtIndex:withObject:]: mutating method sent to immutable object - ios

My applicaiton crashes with the exception mentioned in the title. Please find below my explanation :
tried to store array values using NSUserDefaults in my TableViewController as under :
func didDismissContentViewController(controller:ContentViewController,pageIndex:Int) { //You're passed in the contentViewController here, use relevant data to update your model. NSLog("Inside delegate method ..\n") var currentIdx = pageIndex
NSLog("Value of index edited \(pageIndex)")
NSLog("Current edited value is : \(controller.myTextView.text)")
**pageContent.replaceObjectAtIndex(pageIndex, withObject: controller.myTextView.text)**
**//pageCotent is defined as an NSMutableArray only...**
NSUserDefaults.standardUserDefaults().setObject(pageContent as NSMutableArray, forKey: "PageValues")
NSUserDefaults.standardUserDefaults().synchronize()
}
Retrieval is done as under : if var storedPageContent = NSUserDefaults.standardUserDefaults().objectForKey("PageValues") as? NSMutableArray{ pageContent = storedPageContent
While I am able to store, retrieve and display the updated page values in their respective pages, subsequent transition back and forth between the tableviewcontroller and content view controller throws the below error:
015-12-21 00:51:46.592 PageApp[3943:203948] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent to immutable object' * First throw call stack:
I tried commenting out the store to NSUserDefault, and the applicaiton does not crash, whereas using the NSUserDefault to store values, causes this to happen, So the error appears when I store it in NSUserDefaults and then retrieve it and assign it to array.
any help on this will be appreciated.Exchange data between uitah

The arrays returned from NSUserDefaults are immutable, so you will need to make a mutable copy of the array and then re-set the array in NSUserDefaults, once you've modified it.
I don't know Swift well enough to give you a code example, but this is how it would be done in Objective-C:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefauts];
NSMutableArray *pageContent = [[userDefaults objectForKey:#"PageValues"] mutableCopy];
[pageContent replaceObjectAtIndex:pageIndex withObject:controller.myTextView.text];
[userDefaults setObject:pageContent forKey:#"PageValues"];

I ran into exactly the same problem yesterday. Apparently if you get an array object using NSUserDefaults it acts like an NSArray even if you specify it to be NSMutableArray.
So the way I solved this problem was using this way (Objective-C). Once I had passed it to another NSMutableArray I was able to add / remove objects from it.
NSArray *favArrayTemp = [[NSUserDefaults standardUserDefaults] objectForKey:#"MyFavServers"];
NSMutableArray favArray = [[NSMutableArray alloc] initWithArray:favArrayTemp];

I think the issue is even though you are assigning values retrieved from NSUserDefaults to a NSMutableArray, it is not actually creating a NSMutableArray as you expected. Can you try initialising a new NSMutableArray with the objects fetched from NSUserDefaults?
In Objective-C, it will be like
NSMutableArray *pageContent = [[NSMutableArray alloc] initWithObjects:object1,nil];
where object1 is the value fetched from NSUserDefaults. Sorry I did not know Swift

Use Swift native types.
Cast the type retrieved from NSUserDefaults to a native Swift collection type as var then it's mutable.
var pageContent = [String]()
...
let userDefaults = NSUserDefaults.standardUserDefauts()
pageContent = userDefaults["PageValues"] as! [String]
Replacing is pretty easy, too.
pageContent[pageIndex] = controller.myTextView.text
Side note: The forced unwrapping while reading the value from NSUserDefaults assumes that the key/value pair has been registered properly. Then the unwrapping is safe.

Thank you for the response. I made changes as suggested by you and it worked. Let me share it below:
// As mentioned by you, since NSUserDefault always expects an NS Array, I did the storing as under :
NSLog("I am inside the bactktomonths ..\n")
//Defined an NS Array to copy the Mutable array to Immutable array
var storedArray = NSArray()
storedArray = pageArray!
NSUserDefaults.standardUserDefaults().setObject(storedArray , forKey: "PageValues")
NSUserDefaults.standardUserDefaults().synchronize()
}
// Retrieval.
if (NSUserDefaults.standardUserDefaults().objectForKey("PageValues") != nil){
pageContent = NSUserDefaults.standardUserDefaults().objectForKey("PageValues")?.mutableCopy() as NSMutableArray
// I am not sure If I need to execute the below code, but I have left it as suggested and it works currently !
NSUserDefaults.standardUserDefaults().setObject(pageContent, forKey: "PageValues")
Thanks

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") ?? ""

How to create an NSDictionary and Write and Read the information from a NSUserDefaults object in swift

I want to store 3 variables all of type Int for a game (gameLevel,time,clicks). I want to save the values of these variables in one UIViewController at the end of the game, and pass it to another UIViewController that will present the gameScore (using UILabel).
Should I use an NSDictionary object for this task? and how do I create an NSUserDefaults object and pass it the NSDictionary?
Save the values in NSUserDefaults when game ends
NSUserDefaults.standardUserDefaults().setInteger(gameLevel, forKey: "gameLevelKey")
NSUserDefaults.standardUserDefaults().setInteger(time, forKey: "timeKey")
NSUserDefaults.standardUserDefaults().setInteger(clicks, forKey: "clicksKey")
NSUserDefaults.standardUserDefaults().synchronize()
Get the saved values from NSUserDefaults in other view controller
let gameLevel = NSUserDefaults.standardUserDefaults().integerForKey("gameLevelKey")
let time = NSUserDefaults.standardUserDefaults().integerForKey("timeKey")
let clicks = NSUserDefaults.standardUserDefaults().integerForKey("clicksKey")
NSUserDefaults is like database where you can save values using setXXX method and retrieve values using the xxxForKey method

iOS Swift Error: Execution was interrupted, reason : signal SIGABRT

I am trying to execute the following code in a playground. Fairly, I am treating both variables bm and ul equally but error shows only at ul
import UIKit
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject([], forKey: "bookmarks")
defaults.setObject([], forKey: "urls")
var bm : NSMutableArray = defaults.valueForKey("bookmarks") as NSMutableArray
var ul : NSMutableArray = defaults.valueForKey("urls") as NSMutableArray
bm.addObject("Google") //--->Works
ul.addObject("http://google.com") //--->Oops, No
I can't tell you why the first works and the second doesn't - it is probably just a quirk of playgrounds, timing and a delay in persisting NSUserDefaults to disk.
Your problem, however, is that valueForKey (and you should use objectForKey) returns immutable objects - so bm and ul will actually be NSArray instances and you can't simply cast them to NSMutableArray. You get a crash when you try to do so and mutate the object.
You need to create a mutable copy of your array.
var bm=defaults.objectForKey("bookmarks") as NSArray?
if bm != nil {
var bmCopy=bm!.mutableCopy() as NSMutableArray
bmCopy.addObject("Google")
defaults.setObject(bmCopy, forKey:"bookmarks")
}

Objective C: Replace array with "valueForKey"

I have currenty something (in my opinion) complex.
I have a project from someone else and I have to do some little changes.
Now to my problem:
I have an array that looks like this:
(
"class:BPPlaceRate,0x1bcc0330, GUID:319F45BDF11AAE313159998513BF0B2C \nproperty: \n{\n identifier = \"barrierefreier-opnv\";\n rating = 5;\n}"
)
This is the result of NSLogging the array I want to change.
I nerver saw an array like this one.
So what I want to change is the value for "rating".
I tried this with:
[[self.contentArray valueForKey:#"rating"] replaceObjectAtIndex:i withObject:ratingArray[0][i]];
In "ratingArray" are the new values.
With this code I get an Error:
-[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance
I know that it won't work. I tried to put the [self.contentArray valueForKey:#"rating"] in a tempArray. In this array I could change the values, but how can I transfer the values from the tempArray to the rating of self.contentArray?
Hope you can help me.
This array
(
"class:BPPlaceRate,0x1bcc0330, GUID:319F45BDF11AAE313159998513BF0B2C \nproperty: \n{\n identifier = \"barrierefreier-opnv\";\n rating = 5;\n}"
)
contains only one object. You see the output of this object's description method. I'm not sure whether the rating property is writeable by KVC - you can check it if you have source for BPPlaceRate class.
You can try to change the property this way:
id object = [self.contentArray firstObject];
[object setValue:ratingArray[0][i] forKey:#"rating"];

NSMutableArray cast to swift Array of custom type

I'd like to figure out how to specify or cast an NSMutableArray to a swift Array of a custom type. I currently have 2 files:
First file requires an NSMutableArray for its functionality (passed by reference, ability to remove particular objects with indices I don't know)
Second file uses a Swift array (better memory / throwaway array), with a custom type, which I declare using
let newArray: [CustomType]!
I need to pass the NSMutableArray in as a parameter to a function in the second file, which requires a [CustomType]. When simply calling it:
let newVC = UIViewController(array: mutableArray)
it keeps telling me 'CustomType' is not identical to 'AnyObject'. I've tried calling the function using mutableArray as [CustomType], which does not work either. How can I make the swift Array function accept my NSMutableArray?
This works for me:
var swiftArray = NSArray(array:mutableArray) as Array<CustomType>
swiftArray is then a Swift array of objects of CustomType. You can pass the array and iterate over it as you would expect.
What I needed was this:
let newVC = UIViewController(array: mutableArray as AnyObject as [CustomType])

Resources