Search code examples
iosswiftnskeyedarchivernskeyedunarchiver

Swift can't unarchive Data


I have a UIView subclass which contains the following:

class someClass: UIView {
  var someString = ""
  var imageView: UIImageView
  var position: CGPoint
  var dimensions: CGFloat

  init(position: CGPoint, dimensions: CGFloat, someString: String) {
    self.position = position
    self.dimensions = dimensions
    self.someString = someString
    self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: dimensions, height: dimensions))
    super.init(frame: CGRect(x: position.x, y: position.y, width: dimensions, height: dimensions))
    addSubview(imageView)
  }
}

Also I added required init?(coder aDecoder: NSCoder) and encodeWithCoder(coder: NSCoder):

required init?(coder aDecoder: NSCoder) {
  self.someString = aDecoder.decodeObject(forKey: "someString") as! String
  self.position = aDecoder.decodeObject(forKey: "position") as! CGPoint
  self.dimensions = aDecoder.decodeObject(forKey: "dimensions") as! CGFloat
  self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: dimensions, height: dimensions))
  super.init(frame: CGRect(x: position.x, y: position.y, width: dimensions, height: dimensions))
  self.addSubview(imageView)
}

func encodeWithCoder(coder: NSCoder) {
  coder.encode(self.position, forKey: "position")
  coder.encode(self.dimensions, forKey: "dimensions")
  coder.encode(self.someString, forKey: "someString")
}

So, I created an object of the class:

let someObject = someClass(position: CGPoint(x: 2.0, y: 3.0), dimensions: 10.0, someString: "Hello")

I'm trying to archive the object:

let archivedObject = NSKeyedArchiver.archivedData(withRootObject: someObject)

And then to unarchive:

let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: archivedObject) as! someClass

Here I'm getting the error:

my error

If print unarchivedObject it will be "fatal error: unexpectedly found nil while unwrapping an Optional value".

Can somebody help me with this? What is wrong?


Solution

  • It looks like the problem is that you're not properly overriding encodeWithEncoder. If you put a breakpoint inside that function, you'll see that it's never getting called, which is why the object is nil when you try to decode it - it never got encoded!

    You'll also run into an error trying to use decodeObject on a CGPoint. Make sure to change that line too and then you should be in business.

    By the way, it is common practice to capitalize class names so that they are distinct from instances of that class, so I changed someClass to SomeClass.

    class SomeClass: UIView {
    var someString = ""
    var imageView: UIImageView
    var position: CGPoint
    var dimensions: CGFloat
    
    init(position: CGPoint, dimensions: CGFloat, someString: String) {
        self.position = position
        self.dimensions = dimensions
        self.someString = someString
        self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: dimensions, height: dimensions))
        super.init(frame: CGRect(x: position.x, y: position.y, width: dimensions, height: dimensions))
        addSubview(imageView)
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.someString = aDecoder.decodeObject(forKey: "someString") as! String
        self.position = aDecoder.decodeCGPoint(forKey: "position")
        self.dimensions = aDecoder.decodeObject(forKey: "dimensions") as! CGFloat
        self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: dimensions, height: dimensions))
        super.init(frame: CGRect(x: position.x, y: position.y, width: dimensions, height: dimensions))
        self.addSubview(imageView)
    }
    
    override func encode(with aCoder: NSCoder) {
        aCoder.encode(self.position, forKey: "position")
        aCoder.encode(self.dimensions, forKey: "dimensions")
        aCoder.encode(self.someString, forKey: "someString")
    }
    
    }