2

I have a class that I'd like to be able to open, and export to a file. It doesn't need to be edited - this is simply a way to share part objects CoreData database with other users of my app, so I don't think I need the complexity of subclassing NSDocument.

The class is pretty basic, with two properties String and a filename for a NSImage that references an image in the app sandbox.

Initially, I thought of using a Document Bundle, basically a folder with the images (if required) and a plist with the strings and image file names. Now, I'm leaning toward creating a class-just-for-export-and-import that uses NSCoding to save the images and the strings into a file.

Here's what I'm not sure about:

  • is this a good idea? Am I missing something here?
  • can I save an array of objects with NSImage and String properties into a single file? I know I can save an image, fairly easily, but can I bundle them up into a single document? Is NSCoding the way to do this?
  • Do I need to convert the NSImage to NSData first?
  • How does my decoder know which part of the file is a String and which part is a NSImage?

I have done my research! There's lots of info about saving an image, or saving a NSDocument, but I just want a simple way for users to export and import arbitrary data into, and out of, my application's core data store.

glenstorey
  • 5,134
  • 5
  • 39
  • 71
  • Thanks for the awesome answers. For people from the future with similar questions this is a good resource too: http://nshipster.com/nscoding/ – glenstorey Jun 27 '16 at 17:43

3 Answers3

2

Using NSCoding you can save every class that has built-in support for this protocol or you add it. Both NSImage and – of course – NSString conforms to NSCoding. You simply have to add NSCoding support to your custom class, which is pretty simple:

- (void)encodeWithCoder:(NSCoder*)coder
{
  [coder encodeObject:self.stringProperty forKey:@"String Property"];
  [coder encodeObject:self.imageProperty forKey:@"Image Property"];
}

- (instancetype)initWithCoder:(NSCoder*)coder
{
  self = [super init]; // or `-initWithCoder:, if it is supported
  if (self)
  {
    self.stringProperty = [coder decodeObjectForKey:@"String Property"];
    self.imageProperty = [coder decodeObjectForKey:@"Image Property"];
  }
  return self;
}

If you want to save your data as property list file, you solely can use property list classes. NSString is a property list, but NSImage is not – and of course your custom class is not. So you have to convert instances of NSImage and your custom class to instances of NSData, because NSData is a property list. You can do that with – drum-roll – NSCoding. Simply use a keyed archiver. The result of +archivedDataWithRootObject: (NSKeyedArchiver) is an instance of NSData. The way back you use NSKeyedUnarchiver.

But here is little reason to chose the file format of property lists, if you do not want to edit it with generic property list tools.

So, to answer directly to your Qs:

is this a good idea? Am I missing something here?

It's ok

can I save an array of objects with NSImage and String properties into a single file?

Since NSArray comforms to NSCoding and is a property list, both approaches will work out of the box.

I know I can save an image, fairly easily, but can I bundle them up into a single document?

You are the owner of the file and you define the file format. Just go ahead.

Is NSCoding the way to do this?

Yes.

Do I need to convert the NSImage to NSData first?

Only if you want to save a property list file.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
1

Either option should work fine. Be sure to use a keyed archiver if you go the NSCoding route.

Either option could also deal with an array of objects, you just need to design your file structure / handling code to check and deal with it.

For the file option the image will just be an image on disk. For the coding option everything will be binary data on disk, but that's done during the coding process (where you will convert the image to data).

Based on the structure of the file, which would be the extensions of the files in the package probably. For the coding option it's the key names.

Both of your options are effectively identical for users and are extensible in the future, whichever you choose is down to personal preference. I expect the coding option is faster and easier to write the code for.

Wain
  • 118,658
  • 15
  • 128
  • 151
1

Implementing NSCoding protocol you can share easily the object you describe.

public required init!(coder:NSCoder)
{
    self.busStopName = coder.decodeObjectForKey("busStopName") as? String
    self.busStopID = coder.decodeIntegerForKey("busStopID") as Int
    let lines: [String]! = coder.decodeObjectForKey("busLines") as? [String]
    let photo: NSData = coder.decorDataForKey("photo") as? NSData
}

public override func encodeWithCoder(encoder: NSCoder) -> Void
{
    encoder.encodeObject(self.busStopName, forKey:"busStopName")
    encoder.encodeInteger(self.busStopID, forKey: "busStopID")
    encoder.encodeObject(self.busLines, forKey: "busLines")
    encoder.encodeDataObject(self.busStopImage, forKey: "photo")
}

In order to create an UIImage from NSData use this initializer

let image: UIImage = UIImage(data: <the nsdata decoded>)

And to obtain the NSData from an UIImage use one of this UIKit Functions

UIImageJPEGRepresentation(::) UIImagePNGRepresentation(_:)

Adolfo
  • 1,862
  • 13
  • 19