3

I am using NSKeyedArchiver to archive an NSMutableDictionary that contains an NSMutableArray of subclassed SKSpriteNodes. Its a card game where i'm saving some game stats and then an array of sprites associated with the game.

Archival is working pretty well. I'm also implementing - (id)initWithCoder:(NSCoder *)aDecoder and - (void)encodeWithCoder:(NSCoder *)aCoder in my SKSpriteNode subclass to make sure that all of the properties, including the sprite's texture, are coded through to the archive.

The problem is that when I unarchive this game data. In iOS 8, my NSMutableArray of SKSpriteNodes unpack just fine, and are added back to the game with their textures and properties in tact.

In iOS 7.1 however, everything unpacks except for the node's texture. Instead, I get the error: SKTexture: Error loading image resource: "MissingResource.png" and my sprites have big red X's in place of their textures.

Here's my code:

Archival:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    SKView *view = (SKView *)self.window.rootViewController.view;
    if( view.scene ) {
        GameScene* game = (GameScene*)view.scene;

        NSMutableDictionary* currentGame = [[NSMutableDictionary alloc] init];

        // game stats
        [currentGame setObject:[[Global getSharedInstance] undoStack] forKey:@"undoStack"];
        [currentGame setObject:[NSNumber numberWithInt:game.hints] forKey:@"remainingHints"];
        [currentGame setObject:[NSNumber numberWithInt:game.undos] forKey:@"remainingUndos"];
        [currentGame setObject:[NSNumber numberWithInt:[[Global getSharedInstance] moves]] forKey:@"gameMoves"];
        [currentGame setObject:[NSNumber numberWithInt:[[Global getSharedInstance] score]] forKey:@"gameScore"];
        [currentGame setObject:[NSNumber numberWithInteger:[[Global getSharedInstance] elapsedTime]] forKey:@"elapsedTime"];
        [currentGame setObject:[NSNumber numberWithInteger:[[Global getSharedInstance] minutes]] forKey:@"minutes"];
        [currentGame setObject:[NSNumber numberWithInteger:[[Global getSharedInstance] startedTimeInterval]] forKey:@"startedTimeInterval"];
        [currentGame setObject:[NSNumber numberWithInteger:[[Global getSharedInstance] elapsedPauseTime]] forKey:@"elapsedPauseTime"];
        [currentGame setObject:[NSNumber numberWithInteger:[[Global getSharedInstance] lastPauseTime]] forKey:@"lastPauseTime"];

        // here - game cards SKSpriteNodes
        [currentGame setObject:game.cards forKey:@"cards"];

        // encode to disk
        NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject:currentGame];
        [encodedData writeToFile:[Global gameDataFilePath] atomically:YES];
    }
}

Sprite Coding

- (id)initWithCoder:(NSCoder *)aDecoder
{
    NSLog(@"%@",[aDecoder decodeObjectForKey:@"texture"]);

    self = [super initWithTexture:[aDecoder decodeObjectForKey:@"texture"] color:[UIColor clearColor] size:CGSizeMake(106,156)];
    if(self) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.theme = [aDecoder decodeObjectForKey:@"theme"];
        self.suit = [aDecoder decodeObjectForKey:@"suit"];
        self.value = [aDecoder decodeIntForKey:@"value"];
        self.ID = [aDecoder decodeIntForKey:@"ID"];
        self.locked = [aDecoder decodeBoolForKey:@"locked"];
        self.faceUp = [aDecoder decodeBoolForKey:@"faceUp"];
        self.black = [aDecoder decodeBoolForKey:@"black"];
        self.dealCard = [aDecoder decodeBoolForKey:@"dealCard"];
        self.highlighted = [aDecoder decodeBoolForKey:@"highlighted"];
        self.highlightAction = [aDecoder decodeObjectForKey:@"highlightAction"];
        self.inStackNode = [aDecoder decodeObjectForKey:@"inStackNode"];
        self.shadowNode = [aDecoder decodeObjectForKey:@"shadowNode"];
        self.highlightNode = [aDecoder decodeObjectForKey:@"highlightNode"];
        self.zPosition = [aDecoder decodeFloatForKey:@"zPosition"];
        [self addChild:self.shadowNode];
        [self addChild:self.inStackNode];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.theme forKey:@"theme"];
    [aCoder encodeObject:self.suit forKey:@"suit"];
    [aCoder encodeInt:self.value forKey:@"value"];
    [aCoder encodeInt:self.ID forKey:@"ID"];
    [aCoder encodeBool:self.black forKey:@"black"];
    [aCoder encodeBool:self.locked forKey:@"locked"];
    [aCoder encodeBool:self.faceUp forKey:@"faceUp"];
    [aCoder encodeBool:self.dealCard forKey:@"dealCard"];
    [aCoder encodeBool:self.highlighted forKey:@"highlighted"];
    [aCoder encodeObject:self.highlightAction forKey:@"highlightAction"];
    [aCoder encodeObject:self.inStackNode forKey:@"inStackNode"];
    [aCoder encodeObject:self.shadowNode forKey:@"shadowNode"];
    [aCoder encodeObject:self.highlightNode forKey:@"highlightNode"];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeFloat:self.xScale forKey:@"scale"];
    [aCoder encodeFloat:self.zPosition forKey:@"zPosition"];
    [aCoder encodeObject:self.texture forKey:@"texture"];
}

The NSLog() in the - (id)initWithCoder:(NSCoder *)aDecoder method prints the correct SKTexture in iOS 8, and prints the Missing Image SKTexture in iOS 7.

Thoughts?

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
Matt Fiocca
  • 1,533
  • 13
  • 21
  • Is this on device ? Simulator ? Also, if devices, is by chance one of the devices retina and the other non retina ? – prototypical Dec 27 '14 at 23:23
  • device, and you're correct, in my testing the ios8 device is a retina ipad mini, the ios7 device is ipad 2. – Matt Fiocca Dec 28 '14 at 01:39
  • Try it on the ios7 and ios 8 simulators and with retina and non-retina devices so maybe you can pinpoint if that indeed is the core issue. Also, at what point are you unarchiving ? I see that you are archiving when entering background, but when are you unarchiving ? – prototypical Dec 28 '14 at 01:53
  • Also, is the texture you are archiving originally loaded from a SKTextureAtlas ? – prototypical Dec 28 '14 at 01:57
  • i'll test it out in the simulators. i'm unarchiving in the `viewWillLayoutSubviews` method of the view controller that controls all my `SKScenes`. this vc is the rootViewController of the application and it is in this method that I am loading the SKScenes to get around some orientation issues with sprite kit. My SKSpriteNodes are subclassed with a convenience factory, but the loading of the textures basically boil down as `[SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:[UIImage imageNamed:"card_image"]]]`, and they aren't being loaded via an atlas. – Matt Fiocca Dec 28 '14 at 02:24
  • Also, do any of your images have an `@2x` or `@3x` suffix ? – prototypical Dec 28 '14 at 02:32
  • sorry for all the questions , my theory at the moment is a bit much for the comment section. So my questions are meant to see if I can consolidate my theory some first. – prototypical Dec 28 '14 at 02:38
  • no problem on the questions, thanks for the help! i do not have @(2/3)x in the image names. There are quite a bit of card images because of the ability to change card themes in the game, and to keep the bundle size low, I instead have just 2c.png (two of clubs), and the image itself is at @2x resolution. I then just rely on the frame to size the image for all devices. The thing that has me confused is that the textures work just fine on all devices, until they are unarchived. – Matt Fiocca Dec 28 '14 at 14:43
  • Well, part of my theory is that it has to do with how you are achieving the texture "originally" in combination with 7.1 possibly. Would be interesting to see if you encountered the same issue if you used an `SKTexureAtlas` as opposed to a UIImage as your source for the texture - for the sake of pinpointing if that is the issue. – prototypical Dec 28 '14 at 21:53

1 Answers1

1

Following up on my own question, what I opted to do instead was to store game state in [NSUserDefaults standardUserDefaults] and [NSUbiquitousKeyValueStore defaultStore] for iCloud. I now save NSMutableArrays of NSMutableDictionarys representing my card attributes in user defaults, and then i rebuild my sprites from these values when it comes time to revive a saved game.

Everything is working like it should now.

Matt Fiocca
  • 1,533
  • 13
  • 21