use NSUserDefaults with array of struct type

2019-08-02 09:26发布

I have problems to figure out how to save my string of type "RiskEntry" with NSUserDefaults. I already went through some other posts, but somehow I still did not manage to solve this particular issue.

Let me explain what the code from below does right now: I get some data from my class CustomCell in the following code snippet. Here I first check with an "identifier" which array to update with the new array value "consequences".

It all works fine and the updated array is stored in riskEntry. However, I cannot work out how to store this with NSUserDefaults now. When I try it with e.g. riskItemDefaults.set(riskEntry, forKey: "riskItem") I get an exception error.

Any idea what I am doing wrong here?

SWIFT3 (I removed all code not relevant for this question)

class: RiskPlan: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellUpdaterDelegate {

 var riskEntry = [RiskEntry]()
 var riskItemDefaults = UserDefaults.standard

// ------ start of delegate function (receiving from CustomCell) ---------
      func transferData(consequencesTranferred: String, identifier: String) {
        if let index = riskEntry.index(where: {$0.title as String == identifier}) {
            riskEntry[index].consequences = consequencesTranferred
    } else {
        print ("nothing")
    }

// save with NSUserDefaults
        riskItemDefaults.set(riskEntry, forKey: "riskItem")
  }
}

This is my struct:

public struct RiskEntry {
    let title: String
    var consequences: String
}

my Custom Cell

// ---------------- delegate to transfer entered data to VC -----------------
protocol CustomCellUpdaterDelegate {
    func transferData(consequencesTranferred: String, identifier: String)
}

// ---------------- start of class CellCustomized -----------------
class CustomCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate, UITextViewDelegate {

    var delegate: CustomCellUpdaterDelegate?

    // text fields, text views and picker views
    @IBOutlet weak var riskTitle: UITextView!
    @IBOutlet weak var consequences: UITextView!

    // ---------------- listener for text view to save input in string when editing is finished -----------------
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.tag == 1 {
            textConsequences = consequences.text
            nameIdentifier = riskTitle.text
            delegate?.transferData(consequencesTranferred: self.textConsequences, identifier: nameIdentifier)
        } else {
            print ("nothing")
        }
    }
}

4条回答
对你真心纯属浪费
2楼-- · 2019-08-02 10:05

The closest type to a Swift struct that UserDefaults supports might be an NSDictionary. You could copy the struct elements into an Objective C NSDictionary object before saving the data.

查看更多
beautiful°
3楼-- · 2019-08-02 10:11

The problem is you can't save your custom array in NSUserDefaults. To do that you should change them to NSData then save it in NSUserDefaults Here is the code I used in my project it's in swift 2 syntax and I don't think it's going be hard to convert it to swift 3

let data = NSKeyedArchiver.archivedDataWithRootObject(yourObject);
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "yourKey")
NSUserDefaults.standardUserDefaults().synchronize()

and to the get part use this combination

if let data = NSUserDefaults.standardUserDefaults().objectForKey("yourKey") as? NSData {
    let myItem = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? yourType
}

hope this will help

查看更多
放荡不羁爱自由
4楼-- · 2019-08-02 10:12

Saving objects in UserDefaults have very specific restrictions:

set(_:forKey:) reference:

The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.

You need to serialize your model, either using NSCoding or as an alternative using JSON, to map to a supported value by UserDefaults.

查看更多
来,给爷笑一个
5楼-- · 2019-08-02 10:16

I was able to program a solution based on @ahruss (How to save an array of custom struct to NSUserDefault with swift?). However, I modified it for swift 3 and it also shows how to implement this solution in a UITableView. I hope it can help someone in the future:

  1. Add the extension from below to your structure (adjust it to your own variables)

  2. Save the required array item like this:

    let encoded = riskEntry.map { $0.encode() } riskItemDefaults.set(encoded, forKey: "consequences") riskItemDefaults.synchronize()

  3. Load your item like this

    let dataArray = riskItemDefaults.object(forKey: "consequences") as! [NSData] let savedFoo = dataArray.map { RiskEntry(data: $0)! }

  4. If you'd like to show the saved array item in your cells, proceed this way:

    cell.consequences.text = savedFoo[indexPath.row].consequences as String

Here is the complete code, modified for Swift3

structure

// ---------------- structure for table row content -----------------
struct RiskEntry {
    let title: String
    var consequences: String
}

extension

  extension RiskEntry {
        init?(data: NSData) {
            if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
                title = coding.title as String
                consequences = (coding.consequences as String?)!
            } else {
                return nil
            }
        }

        func encode() -> NSData {
            return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
        }

        private class Encoding: NSObject, NSCoding {
            let title : NSString
            let consequences : NSString?


            init(_ RiskEntry: RiskEntry) {
                title = RiskEntry.title as NSString
                consequences = RiskEntry.consequences as NSString?
            }

            public required init?(coder aDecoder: NSCoder) {
                if let title = aDecoder.decodeObject(forKey: "title") as? NSString {
                    self.title = title
                } else {
                    return nil
                }
                consequences = aDecoder.decodeObject(forKey: "consequences") as? NSString
            }

            public func encode(with aCoder: NSCoder) {
                aCoder.encode(title, forKey: "title")
                aCoder.encode(consequences, forKey: "consequences")
            }

        }
    }
查看更多
登录 后发表回答