Jeff E
Jeff E

Reputation: 63

NSCoding for a non-compliant object (MKMapItem)

Trying to save a MKMapItem as part of my custom class.

 import UIKit
 import MapKit

 class Place: NSObject, NSCoding {

 var mapItem : MKMapItem!
 var type : Category!

 init(mapItem: MKMapItem, type: Category) {
   self.mapItem = mapItem
   self.type = type

 }

// MARK: NSCoding
    required init?(coder decoder: NSCoder) {

       mapItem = decoder.decodeObject(forKey: "mapItem") as! MKMapItem?
       type = decoder.decodeObject(forKey: "type") as! Category?
    }

    func encode(with coder: NSCoder) {

      coder.encode(mapItem, forKey: "mapItem")
      coder.encode(type, forKey: "type")
    }

 }

But this won't work because MKMapItem is not NSCoding-compliant (compiler doesn't complain though). I do understand how to encode custom classes, but can't figure out how to do it for an object already defined by iOS.

I know there is one answer out there for Objective-C on this, but would really like a Swift solution. Thanks.

PS I have tried to subclass MKMapItem and provide "new" initializers, even though it would take considerable code changes elsewhere. But that leads to "Cannot assign to property: 'placemark' is a get-only property". Both 'placemark' and 'isCurrentLocation' are get-only.

import UIKit
import MapKit

class NewMapItem: MKMapItem {


    required init(placemark: MKPlacemark, isCurrentLocation:Bool, name: String,         phoneNumber: String, url: URL, timeZone: TimeZone) {
    self.placemark = placemark  //compiler complains "get-only"
    self.isCurrentLocation = isCurrentLocation  //compiler complains "get-only"
    self.name = name
    self.phoneNumber  = phoneNumber
    self.url = url
    self.timeZone = timeZone

}

// MARK: NSCoding

init?(coder decoder: NSCoder) {

    placemark = (decoder.decodeObject(forKey: "placemark") as! MKPlacemark?)! //compiler complains "get-only"
    isCurrentLocation = decoder.decodeBool(forKey: "isCurrentLocation")  //compiler complains "get-only"
    name = decoder.decodeObject(forKey: "name") as? String
    phoneNumber = decoder.decodeObject(forKey: "phoneNumber") as? String
    url = decoder.decodeObject(forKey: "url") as! URL?
    timeZone = decoder.decodeObject(forKey: "timeZone") as! TimeZone?
}

func encode(with coder: NSCoder) {

    coder.encode(placemark, forKey: "placemark")
    coder.encode(isCurrentLocation, forKey: "isCurrentLocation")
    coder.encode(name, forKey: "name")
    coder.encode(phoneNumber, forKey: "phoneNumber")
    coder.encode(url, forKey: "url")
    coder.encode(timeZone, forKey: "timeZone")
}
}

Upvotes: 0

Views: 471

Answers (3)

gadu
gadu

Reputation: 1826

You could always make it compliant by extending it

extension MKMapItem: NSCoding {
   required init?(coder decoder: NSCoder) {
       // codes
   }

   func encode(with coder: NSCoder) {
       // more codes
   }
}

Upvotes: 0

Bruce Fraser
Bruce Fraser

Reputation: 1

MKMapItem uses NSSecureCoding so you can make your class conform to NSSecureCoding as in the following example and you don't have to recreate MKMapItem:

class BGFPlaceItem: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool {
        return true
    }
    func encode(with aCoder: NSCoder) {
        aCoder.encode(loadedAs.rawValue, forKey: "loadedAs")
        aCoder.encode(favorited, forKey: "favorited")
        aCoder.encode(mapItem, forKey: "mapItem")
    }
    required convenience init?(coder aDecoder: NSCoder) {
        let la = aDecoder.decodeObject(forKey: "loadedAs") as! String
        let fav = aDecoder.decodeBool(forKey: "favorited")
        let mi = aDecoder.decodeObject(forKey: "mapItem") as! MKMapItem
        let la2 = LoadedType(rawValue: la)!
        self.init(loadedAs: la2, isFavorited: fav, with: mi)
    }
    enum LoadedType: String {
        case generalPlace
        case favorite
        case searchResult

        var color: UIColor {
            switch self {
            case .generalPlace: return UIColor(named: "generalPlaceColor")!
            case .favorite: return UIColor(named: "favoriteColor")!
            case .searchResult: return UIColor.yellow
            }
        }
    }
    var loadedAs: LoadedType
    var favorited: Bool
    var mapItem: MKMapItem

    init(loadedAs: LoadedType, isFavorited favorited: Bool, with mi: MKMapItem) {
        self.loadedAs = loadedAs
        self.favorited = favorited
        self.mapItem = mi

        super.init()
    }
}

Upvotes: 0

Jeff E
Jeff E

Reputation: 63

I have figured it out.

Break down the mapItem in "func encode(with coder: NSCoder)" and build it back up again in "init?(coder decoder: NSCoder)"

I did take advantage of the MKMapItem initializer with placemark.

   MKMapItem(placemark: placemark!)

Here is my working class:

import UIKit
import MapKit

class Place: NSObject, NSCoding {

  var mapItem : MKMapItem!
  var type : Category!

  init(mapItem: MKMapItem, type: Category) {
    self.mapItem = mapItem
    self.type = type
  }

// MARK: NSCoding
    required init?(coder decoder: NSCoder) {

    //liberate the properties of mapItem and rebuild it
    // let isCurrentLocation = decoder.decodeObject(forKey: "isCurrentLocation") as! Bool //don't need set already
      let name = decoder.decodeObject(forKey: "name") as! String?
      let phoneNumber = decoder.decodeObject(forKey: "phoneNumber") as! String?
      let url = decoder.decodeObject(forKey: "url") as! URL?
      let timeZone = decoder.decodeObject(forKey: "timeZone") as! TimeZone?
      let placemark = decoder.decodeObject(forKey: "placemark") as! MKPlacemark?

     self.mapItem = MKMapItem(placemark: placemark!)
     self.mapItem.name = name
     self.mapItem.url = url
     self.mapItem.phoneNumber = phoneNumber
     //self.mapItem.isCurrentLocation = isCurrentLocation //don't need this. Set already
     self.mapItem.timeZone = timeZone

     type = decoder.decodeObject(forKey: "type") as! Category?

   }

   func encode(with coder: NSCoder) {
     let placemark = mapItem.placemark
     let name = mapItem.name
     let phoneNumber  = mapItem.phoneNumber
     let url = mapItem.url
     let timeZone = mapItem.timeZone

     coder.encode(name, forKey: "name")
     coder.encode(phoneNumber,forKey: "phoneNumber")
     coder.encode(url, forKey: "url")
     coder.encode(timeZone, forKey: "timeZone")
     coder.encode(type, forKey: "type")
     coder.encode(placemark, forKey: "placemark")

   }

}

Upvotes: 1

Related Questions