Search code examples
objective-cuiviewuiimageviewsprite-kitskview

How to lower a UIView element in a SpriteKit scene


I have a regular SpriteKit scene. In fact it is the default scene created from "Sprite Kit" which draws spaceships.

I would like to draw on the background.

I have successfully added a UIImageView as a subview of the main skView. I can draw on this with lines and such.

But I can't seem to succesfully put the UIImageView behind the main skview.

How can I do this? Here is my sample code.

(In the UIImageView I'm just drawing random lines every 0.1 seconds.)

@implementation GameViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
//    skView.showsFPS = YES;
//    skView.showsNodeCount = YES;
    /* Sprite Kit applies additional optimizations to improve rendering performance */
    //skView.ignoresSiblingOrder = YES;

    // Create and configure the scene.
    GameScene *scene = [GameScene unarchiveFromFile:@"GameScene"];
    scene.scaleMode = SKSceneScaleModeAspectFill;

    [skView presentScene:scene];
}

-(void)viewWillLayoutSubviews{

    self.imageView = [UIImageView new];
    self.imageView.frame = self.view.frame;
    [self.view addSubview:self.imageView];
    [self startRepeatingTimer];
}

-(void)viewDidLayoutSubviews{

    // this doesn't work. was just trying it out.
    int i = [[self.view subviews] count];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:i-1];

}

///// DRAW RANDOM LINES EVERY 0.1 SECONDS ON THE UIIMAGEVIEW SUBVIEW
- (IBAction)startRepeatingTimer {

    [self.repeatingTimer invalidate];
    self.repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(drawOnImage:) userInfo:nil repeats:YES];
}

- (void)drawOnImage:(NSTimer*)theTimer {

    //setup
    float w = self.view.frame.size.width;
    float h = self.view.frame.size.height;

    UIGraphicsBeginImageContextWithOptions(self.view.frame.size,NO,0.0);
    [self.imageView.image drawInRect:CGRectMake(0, 0, w, h)];
    CGContextRef c = UIGraphicsGetCurrentContext();

    //draw
    CGContextMoveToPoint(c, drand48()*w,drand48()*h);
    CGContextAddLineToPoint(c, drand48()*w,drand48()*h);
    CGContextSetLineCap(c, kCGLineCapRound);
    CGContextSetLineWidth(c, 1.0f );
    CGContextSetRGBStrokeColor(c, drand48(), drand48(), drand48(), 1.0);
    CGContextStrokePath(c);

    // set the image
    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

Solution

  • Here's a SpriteKit-friendly way to draw random lines on a background image. The basic steps are

    1. Create a blank image
    2. Convert the image to an SKTexture
    3. Create a sprite with the texture
    4. Draw a random line on the image
    5. Update the sprite's texture with the image
    6. Wait for 0.1 seconds
    7. Iterate over steps 4-6

    In GameScene.m, add the following

    -(void)didMoveToView:(SKView *)view {
    
        self.scaleMode = SKSceneScaleModeResizeFill;
    
        // 1. Create an empty UIImage
        self.image = [self blankImageWithSize:view.frame.size];
        // 2. Convert the image to a texture
        SKTexture *texture = [SKTexture textureWithImage:_image];
        // 3. Create a sprite from the texture
        SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:texture];
    
        // Center the sprite in the view
        sprite.position = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view..frame));
        // Set zPosition such that the sprite is underneath other nodes
        sprite.zPosition = -1000;
        [self addChild:sprite];
    
        self.sprite = sprite;
    
        // 4. and 5. Create an SKAction that draw lines on the image and then update the
        // sprite's texture
        SKAction *drawAction = [SKAction runBlock:^{
            [self drawOnImage];
            _sprite.texture = [SKTexture textureWithImage:_image];
        }];
        // 6. create a 0.1 second delay
        SKAction *wait = [SKAction waitForDuration:0.1];
        SKAction *action = [SKAction sequence:@[drawAction,wait]];
    
        // 7. Iterate
        [self runAction:[SKAction repeatActionForever:action]];
    
        // Create a label and add it to the scene to show the image is
        // in the background
        SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    
        myLabel.text = @"Hello, World!";
        myLabel.fontSize = 65;
        myLabel.position = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view..frame));
    
        [self addChild:myLabel];
    
    }
    

    This method creates a blank image

    - (UIImage*) blankImageWithSize:(CGSize)size
    {
        UIGraphicsBeginImageContext(size);
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image;
    }
    

    This method draws lines on the image

    - (void)drawOnImage {
    
        //setup
        float w = _image.size.width;
        float h = _image.size.height;
    
        UIGraphicsBeginImageContextWithOptions(_image.size,NO,0.0);
        [_image drawInRect:CGRectMake(0, 0, w, h)];
        CGContextRef c = UIGraphicsGetCurrentContext();
    
        //draw
        CGContextMoveToPoint(c, drand48()*w,drand48()*h);
        CGContextAddLineToPoint(c, drand48()*w,drand48()*h);
        CGContextSetLineCap(c, kCGLineCapRound);
        CGContextSetLineWidth(c, 1.0f );
        CGContextSetRGBStrokeColor(c, drand48(), drand48(), drand48(), 1.0);
        CGContextStrokePath(c);
    
        // set the image
        _image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }