NSUserDefault Custom object error - "array cannot be bridged from Objective-C" - ios

On the line that contains return goal //**// my program crashes and gives me the error: "fatal: array cannot be bridged from Objective-C". Could anyone know what this is from?
func saveGoals (goals : [Goal]) {
var updatedGoals = NSKeyedArchiver.archivedDataWithRootObject(goals)
NSUserDefaults.standardUserDefaults().setObject(updatedGoals, forKey: "Goals")
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadCustomObjectWithKey() -> [Goal?] {
if let encodedObject : NSData = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData {
var encodedObject : NSData? = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData
var goal : [Goal] = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObject!) as [Goal]
return goal //**//
} else {
return [Goal]()
}
}
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: GoalsTableView!
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoals: [Goal] = loadCustomObjectWithKey() as? [Goal] {
goalsArray = storedGoals
}
//retrieve data.
if var storedGoalList: [Goal] = NSUserDefaults.standardUserDefaults().objectForKey("GoalList") as? [Goal]{
goalsArray = storedGoalList;
}
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoals(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
}

You're trying to return a [Goal] as [Goal?]. Since the array content types don't match (and can't match) you get a runtime exception. Changed the return type to [Goal], particularly since you always return something anyway.

Related

Need help using predicates to filter NSArray that presents data in UITableVIew

I currently have NSArray that gets its data from a mySQL database.
I need to filter this data based on a hard-coded string "Customer1"
The following is what I have so far:
import UIKit
class showCustomerDetails: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedDetailProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : DetailModel = DetailModel()
#IBOutlet weak var stockResultsFeed: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.stockResultsFeed.delegate = self
self.stockResultsFeed.dataSource = self
let detailModel = FeedDetail()
detailModel.delegate = self
detailModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.stockResultsFeed.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "customerDetails"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .center
// Get the stock to be shown
let item: DetailModel = feedItems[indexPath.row] as! DetailModel
// Configure our cell title made up of name and price
let customerDetails = [item.code, item.manufacturer, item.model].compactMap { $0 }.joined(separator: " — ")
print(customerDetails)
// Get references to labels of cell
myCell.textLabel!.text = customerDetails
return myCell
}
}
The following is what I was thinking of doing, but I am not sure how to properly apply it:
let searchString = "Customer1"
let predicate = NSPredicate(format: "SELF contains %#", searchString)
let searchDataSource = feedItems.filter { predicate.evaluateWithObject($0) }
And then:
let item: DetailModel = searchDataSource[indexPath.row] as! DetailModel
NSArray data is coming from:
import Foundation
protocol FeedDetailProtocol: class {
func itemsDownloaded(items: NSArray)
}
class FeedDetail: NSObject, URLSessionDataDelegate {
weak var delegate: FeedDetailProtocol!
let urlPath = "https://www.example.com/test1/test1.php"
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
}else {
print("details downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let stocks = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let stock = DetailModel()
//the following insures none of the JsonElement values are nil through optional binding
if let code = jsonElement[“code”] as? String,
let customer = jsonElement["customer"] as? String,
let manufacturer = jsonElement["manufacturer"] as? String,
let model = jsonElement["model"] as? String
{
print(code)
print(manufacturer)
print(model)
print(customer)
stock.code = code
stock.manufacturer = manufacturer
stock.model = model
stock.customer = customer
}
stocks.add(stock)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: stocks)
})
}
}
This is Swift. Use Array, not NSArray, and just call Array's filter method. NSArray belongs to Cocoa and Objective-C; you should use native Swift types and Swift methods as much as possible.
If you insist on filtering an NSArray using a Cocoa Objective-C method, and you insist on using NSPredicate, the simplest approach is to form your predicate with init(block:).
Here's a simple illustration:
let arr = ["Manny", "Moe", "Jack"] as NSArray
let p = NSPredicate { element, _ in
return (element as? String)?.contains("a") ?? false
}
let arr2 = arr.filtered(using: p)
print(arr2) // [Manny, Jack]
But (just to drive home the point) it's so much simpler in native Swift:
let arr = ["Manny", "Moe", "Jack"]
let arr2 = arr.filter {$0.contains("a")}

How to load tableview before view is called?

I want to put the table names of my friends first. But why my view should be loaded first, then starts executing the code here
self.art = [responseObject]
let jsonArrays :[String:AnyObject] = self.art.firstObject as! Dictionary
let name = jsonArrays["response"]
for var value in name as! NSArray
{
value = (name?.valueForKey("first_name"))!
self.friends = [value]
}
Here is the full code
class TableViewController: UITableViewController
{
let manager = AFHTTPRequestOperationManager()
let url = "https://api.vk.com/method/friends.get"
var dict = NSDictionary()
var art = []
var friends = NSArray()
var arrayUser = NSArray()
var ar = []
override func viewDidLoad() {
super.viewDidLoad()
self.getFriends()
self.friends = NSArray()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getFriends()
{
dict = ["user_id":"107487867",
"name":"order",
"count":5,
"fields":"photo",]
self.manager.GET(url, parameters: dict,
success:{ (operation:AFHTTPRequestOperation,
responseObject: AnyObject!) in
self.art = [responseObject]
let jsonArrays :[String:AnyObject] = self.art.firstObject as! Dictionary
let name = jsonArrays["response"]
for var value in name as! NSArray
{
value = (name?.valueForKey("first_name"))!
self.friends = [value]
}
}){ (operation:AFHTTPRequestOperation!, NSError) -> Void in
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}

iOS reloading a UITableView from a Swift class/object

I am trying to build a object oriented iOS app and I am having problems calling a table reload (ui interaction) from one of my swift class files.
If I am working without objects and write all that stuff in my InterfaceController's viewDidLoad method everything works... but not if I put that into classes.
I am using an asynchronous request in order to load json data from a webservice. After receiving the data I am using this data as data source for my table view.
It seems the tableview is initialized after startup with no data, so it is neccessary to call reload() on the tableview after finishing the async request.
So here are the details:
Main TableController
import UIKit;
class DeviceOverview: UITableViewController {
#IBOutlet var myTableView: UITableView!
var myDevices = Array<String>();
var myDevicesSection = NSMutableDictionary()
var deviceObjects = Array<NSDictionary>();
var mappingSectionIndex = Array<String>();
var sharedUserDefaults = NSUserDefaults(suiteName: "group.barz.fhem.SharingDefaults")
// THIS IS AN ATTEMPT TO give the class itself as PARAMETER
// ALSO TRIED TO USE myTableView (IBOutlet)
var fhem :FHEM = FHEM(tableview : self)
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fhem.sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var devicesInSection : Array<NSDictionary> = self.fhem.sections[self.fhem.mappingSectionIndex[section]] as! Array<NSDictionary>
return devicesInSection.count;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("device", forIndexPath: indexPath) as! TableViewCell
let sectionName : String = fhem.mappingSectionIndex[indexPath.section]
if let devices : Array<NSDictionary> = self.fhem.sections.objectForKey(sectionName) as? Array<NSDictionary> {
let device = devices[indexPath.row]
var alias : NSString?;
if let attr : NSDictionary = device.objectForKey("ATTR") as? NSDictionary {
alias = attr.objectForKey("alias") as? String
}
if (alias != nil) {
cell.deviceLabel.text = alias as? String
}else{
if let deviceName : String = device.objectForKey("NAME") as? String {
cell.deviceLabel.text = deviceName
}
}
}
return cell
}
FHEM Class
import UIKit
class FHEM: NSObject {
var devices : [NSDictionary]
var sections : NSMutableDictionary
var deviceNames : [String]
var mappingSectionIndex : [String]
var myTableViewController : DeviceOverview
init(tableview : DeviceOverview){
self.devices = Array<NSDictionary>()
self.sections = NSMutableDictionary()
self.deviceNames = Array<String>()
self.mappingSectionIndex = Array<String>()
self.myTableViewController = tableview
super.init()
let url = NSURL(string: "xyz");
var request = NSURLRequest(URL: url!);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) in
if let jsonData: NSData? = data {
// do some great stuff
//
// this is an attempt to call the table view to reload
self.myTableViewController.myTableView.reloadData()
}
}
}
}
It throws compile errors if I try to put the self variable into the constructor of my constructor
var fhem :FHEM = FHEM(tableview : self)
It also does not work if I try to put the UITableView directly into the constructor
var fhem :FHEM = FHEM(tableview : myTableView)
Am I walking along complete wrong path using objects and interacting with the ui?
You can just post a Notification when your async task finishes:
NSNotificationCenter.defaultCenter().postNotificationName("refreshMyTableView", object: nil)
Add an observer to that notification to your DeviceOverview class method viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshList:", name:"refreshMyTableView", object: nil)
and add the method that will be fired at your DeviceOverview class
func refreshList(notification: NSNotification){
myTableView.reloadData()
}
Hi You can use Notification as suggested in Above answers otherwise you can use delegation to resolve this problem
Delegate is doing something like that you done but in easy manner.
Here you pass table view controller in you custom class.this thing you can do with delegate. create custom delegate method in your custom class. set delegate with table view controller object.
here you don't required to take IBOutlet of your table view because your controller inherited from table view controller so it's view is table view
import UIKit
class DeviceOverview: UITableViewController,FHEMDelegate {
#IBOutlet var myTableView: UITableView!
var myDevices = Array<String>();
var myDevicesSection = NSMutableDictionary()
var deviceObjects = Array<NSDictionary>();
var mappingSectionIndex = Array<String>();
var sharedUserDefaults = NSUserDefaults(suiteName: "group.barz.fhem.SharingDefaults")
var fhem :FHEM?
// THIS IS AN ATTEMPT TO give the class itself as PARAMETER
// ALSO TRIED TO USE myTableView (IBOutlet)
override func viewDidLoad() {
super.viewDidLoad()
fhem = FHEM ()
fhem?.delegate = self
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// var devicesInSection : Array<NSDictionary> = self.fhem!.sections[self.fhem!.mappingSectionIndex[section]] as Array<NSDictionary>
return 5;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("device", forIndexPath: indexPath) as UITableViewCell
let sectionName : String = fhem!.mappingSectionIndex[indexPath.section]
if let devices : Array<NSDictionary> = self.fhem!.sections.objectForKey(sectionName) as? Array<NSDictionary> {
let device = devices[indexPath.row]
var alias : NSString?;
if let attr : NSDictionary = device.objectForKey("ATTR") as? NSDictionary {
alias = attr.objectForKey("alias") as? String
}
if (alias != nil) {
// cell.deviceLabel.text = alias as? String
}else{
if let deviceName : String = device.objectForKey("NAME") as? String {
// cell.deviceLabel.text = deviceName
}
}
}
return cell
}
func reloadDataOfTable()
{
self.tableView.reloadData()
}
}
FHEM
import UIKit
protocol FHEMDelegate{
func reloadDataOfTable()
}
class FHEM : NSObject {
var devices : [NSDictionary]
var sections : NSMutableDictionary
var deviceNames : [String]
var mappingSectionIndex : [String]
//var myTableViewController : DeviceOverview
var delegate :FHEMDelegate?
override init(){
self.devices = Array<NSDictionary>()
self.sections = NSMutableDictionary()
self.deviceNames = Array<String>()
self.mappingSectionIndex = Array<String>()
self.delegate = nil
super.init()
let url = NSURL(string: "http://google.com");
var request = NSURLRequest(URL: url!);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) in
if let jsonData: NSData? = data {
// do some great stuff
//
// this is an attempt to call the table view to reload
self.delegate?.reloadDataOfTable()
}
}
}
}
Here in FHEM class create one custom delegate method reloadDataOfTable that implement in your viewcontroller class so when got response of code it call that method and this method (reloadDataOfTable) content code for reload table data.
I don't know much Swift, but I've been writing Objective-C for iOS for a while now. (In Objective-C at least) you must call init on super before accessing self, otherwise you're object isn't initialized correctly. You're also not assigning self to the result of super.init(), which is necessary in Objective-C, looks like it isn't in Swift though.
tl;dr -- Move the call to super.init() to the very first line in your FHEM class' init method.

NSUserDefaults Custom object - Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')

I must be incorrectly using Custom Objects for NSUserDefaults. The error " Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')". Below is my code, the Goal class is of particular interest, since this is where I am adopting the NSCoding protocol.
This code is global.
func saveGoals (goals : [Goal]) {
var updatedGoals = NSKeyedArchiver.archivedDataWithRootObject(goals)
NSUserDefaults.standardUserDefaults().setObject(updatedGoals, forKey: "Goals")
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadCustomObjectWithKey() -> [Goal] {
if let encodedObject : NSData = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData {
var encodedObject : NSData? = NSUserDefaults.standardUserDefaults().objectForKey("Goals") as? NSData
var goal : [Goal] = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObject!) as [Goal]
return goal
} else {
return [Goal]()
}
}
This code is in GoalsViewController.
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: GoalsTableView!
var cell = GoalTableViewCell()
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoals: [Goal] = loadCustomObjectWithKey() as [Goal]? {
goalsArray = storedGoals
}
//retrieve data.
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoals(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
var notification = NSNotificationCenter.defaultCenter()
notification.addObserver(self, selector: "finishCreatingGoal:", name: "FinishCreatingGoal", object: nil)
}
func finishCreatingGoal(notification : NSNotification) {
if (notification.name == "FinishCreatingGoal") {
var userInfo = notification.userInfo!
var text = userInfo["text"]! as String
var index = userInfo["index"]! as Int
var cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as GoalTableViewCell
goalsArray[index].title = cell.goalTextField.text
saveGoalList(goalsArray)
self.tableView.reloadData()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0), atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
}
}
This code is in the Goal class.
import UIKit
class Goal : NSObject, NSCoding {
var title : String? = ""
var checkmarked : Bool? = false
var isLastCell : Bool? = false
var enabled : Bool? = true
var priority = Priority.defaultPriority
override init() {
}
init(title : String) {
self.title = title
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title!, forKey: "title")
aCoder.encodeBool(checkmarked!, forKey: "checkmarked")
aCoder.encodeBool(isLastCell!, forKey: "isLastCell")
aCoder.encodeBool(enabled!, forKey: "enabled")
}
required init(coder aDecoder: NSCoder) {
title = aDecoder.decodeObjectForKey("title") as String!
checkmarked = aDecoder.decodeBoolForKey("checkmarked") as Bool
isLastCell = aDecoder.decodeBoolForKey("isLastCell") as Bool
enabled = aDecoder.decodeBoolForKey("enabled") as Bool
}
}
I am going to just copy code from a working project I have:
here is the Game object class with data from a math flash card game:
import Foundation
class GameData: NSObject {
var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrong: Bool = true
var usedTime: Int = 0
var correctCount: Int = 0
var correctTopNumber: [Int] = [Int]()
var correctBottomNumber: [Int] = [Int]()
var wrongTopNumber: [Int] = [Int]()
var wrongBottomNumber: [Int] = [Int]()
var date: NSDate = NSDate()
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrong, forKey: "skipWrong")
aCoder.encodeInteger(usedTime, forKey: "usedTime")
aCoder.encodeInteger(correctCount, forKey: "correctCount")
aCoder.encodeObject(correctTopNumber, forKey: "correctTopNumber")
aCoder.encodeObject(correctBottomNumber, forKey: "correctBottomNumber")
aCoder.encodeObject(wrongTopNumber, forKey: "wrongTopNumber")
aCoder.encodeObject(wrongBottomNumber, forKey: "wrongBottomNumber")
aCoder.encodeObject(date, forKey: "date")
}
init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrong = aDecoder.decodeBoolForKey("skipWrong")
usedTime = aDecoder.decodeIntegerForKey("usedTime")
correctCount = aDecoder.decodeIntegerForKey("correctCount")
correctTopNumber = aDecoder.decodeObjectForKey("correctTopNumber") as Array
correctBottomNumber = aDecoder.decodeObjectForKey("correctBottomNumber") as Array
wrongTopNumber = aDecoder.decodeObjectForKey("wrongTopNumber") as Array
wrongBottomNumber = aDecoder.decodeObjectForKey("wrongBottomNumber") as Array
date = aDecoder.decodeObjectForKey("date") as NSDate
}
override init() {
}
}
This part looks about the same as yours, but with more variable types. The archiver and retriever classes differ from you:
import Foundation
class ArchiveGameData:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveResults(#dataSet: [GameData]) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if NSKeyedArchiver.archiveRootObject(dataSet, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveGameData() -> NSObject {
var dataToRetrieve = [GameData]()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("results3.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [GameData] {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Finally, the code for storing and retrieving from within a ViewController:
//retrieveing
var gameDataArray = ArchiveGameData().RetrieveGameData() as [GameData]
//Archiving
gameData = GameData() //create local object then append all the new data, then store it
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time
//etc. for all properties
ArchiveGameData().ArchiveResults(dataSet: gameDataArray)

How can I store an array of custom objects (Goals)

How can I store an array of objects of type Goal which I have created in NSUserDefaults? (in swift)
Here is the code:
func saveGoalList ( newGoalList : [Goal] ){
let updatedGoalList = newGoalList;
NSUserDefaults.standardUserDefaults().setObject(updatedGoalList, forKey: "GoalList")
NSUserDefaults.standardUserDefaults().synchronize()
}
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: GoalsTableView!
var cell = GoalTableViewCell()
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoalList: [Goal] = NSUserDefaults.standardUserDefaults().objectForKey("GoalList") as? [Goal]{
goalsArray = storedGoalList;
}
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoalList(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.xpnotificationView.alpha = 0.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goalsArray.count //to ensure there is always an extra cell to fill in.
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //recreate the cell and try using it.
cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as GoalTableViewCell
cell.goalTextField.text = goalsArray[indexPath.row].title as String!
cell.checkmarkImageView.visible = goalsArray[indexPath.row].checkmarked as Bool!
if (cell.checkmarkImageView.visible == true) {
cell.blackLineView.alpha = 1.0
} else {
cell.blackLineView.alpha = 0.0
}
return cell
}
}
I understand that there are only certain data types that work with NSUserDefaults. Could anyone help me understand how I could do that?
Edit: Right now Goal inherits from NSObject.
I am posting code from a learning project I did to store objects using NSCoding. Fully functional and ready to use. A math game that was storing game variables, etc.
//********This class creates the object and properties to store********
import Foundation
class ButtonStates: NSObject {
var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrongAnswers = true
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrongAnswers, forKey: "skipWrongAnswers")
}
init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrongAnswers = aDecoder.decodeBoolForKey("skipWrongAnswers")
}
override init() {
}
}
//********Here is the data archiving and retrieving class********
class ArchiveButtonStates:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveButtons(#buttonStates: ButtonStates) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")
if NSKeyedArchiver.archiveRootObject(buttonStates, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveButtons() -> NSObject {
var dataToRetrieve = ButtonStates()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? ButtonStates {
dataToRetrieve = dataToRetrieve2 as ButtonStates
}
return(dataToRetrieve)
}
}
the following is in my ViewController where the game is played. Only showing the relevant code for retrieving and storing objects
class mathGame: UIViewController {
var buttonStates = ButtonStates()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//set inital view
//retrieving a stored object & placing property into local class variables
buttonStates = ArchiveButtonStates().RetrieveButtons() as ButtonStates
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
//storing the object
ArchiveButtonStates().ArchiveButtons(buttonStates: buttonStates)
}
}
You need your class to adopt the NSCoding protocol and encode and decode itself, like this:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch23p798basicFileOperations/ch36p1053basicFileOperations/Person.swift
Now you can transform an instance of your class into an NSData by calling NSKeyedArchiver.archivedDataWithRootObject: - and an NSData can go into NSUserDefaults.
This also means that an NSArray of instances of your class can be transformed into an NSData by the same means.
For Swift 2.1, your Goal class should look like :
import Foundation
class Goal : NSObject, NSCoding {
var title: String
// designated initializer
init(title: String) {
self.title = title
super.init() // call NSObject's init method
}
// MARK: - comply wiht NSCoding protocol
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title, forKey: "GoalTitle")
}
required convenience init?(coder aDecoder: NSCoder) {
// decoding could fail, for example when no Blog was saved before calling decode
guard let unarchivedGoalTitle = aDecoder.decodeObjectForKey("GoalTitle") as? String
else {
// option 1 : return an default Blog
self.init(title: "unknown")
return
// option 2 : return nil, and handle the error at higher level
}
// convenience init must call the designated init
self.init(title: unarchivedGoalTitle)
}
}
and you should use it in your view controller like I did in this test code :
// create an array with test data
let goal1 = Goal(title: "first goal")
let goal2 = Goal(title: "second goal")
let goalArray = [goal1, goal2]
// first convert the array of custom Goal objects to a NSData blob, as NSUserDefaults cannot handle arrays of custom objects directly
let dataBlob = NSKeyedArchiver.archivedDataWithRootObject(goalArray)
// this NSData object can now be stored in the user defaults
NSUserDefaults.standardUserDefaults().setObject(dataBlob, forKey: "myGoals")
// sync to make sure they are saved before we retreive anytying
NSUserDefaults.standardUserDefaults().synchronize()
// now read back
if let decodedNSDataBlob = NSUserDefaults.standardUserDefaults().objectForKey("myGoals") as? NSData {
if let loadedGoalsArray = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSDataBlob) as? [Goal] {
for goal in loadedGoalsArray {
print("goal : \(goal.title)")
}
}
}
As a final remark : it would be easier to use NSKeyedArchiver instead of NSUserDefaults, and store your array of custom objects directly to a file. You can read more about the difference between both methods in another answer I posted here.

Resources