UIKit + SpriteKit - Cannot properly layer buttons, nodes, and image views - ios

I'm quite new to iOS development, and I'm working on an app that uses UIKit for buttons and Sprite Kit for a virtual joystick. My goal is to have the buttons and joystick visible on top of a UIImageView (more specifically, a MotionJpegImageView). I used a storyboard to create the buttons, and programatically created a UIImageView and added it as a subview to my main view in my view controller. The joystick was created in a separate file that subclasses SKScene. However, there's a problem. My UIImageView shows up below the buttons (as desired) but covers the joystick. I need the joystick to be visible on top of the image. I've already tried the sendSubviewToBack method but that isn't doing the trick. I've also tried using zPositions, but that is not working either. Does anyone know how I can achieve my goal? Relevant code is below. Thanks for looking!
ViewController.m
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
NSURL *url = [NSURL URLWithString:#"http://shibuya.ipcam.jp:60001/nphMotionJpeg?Resolution=320x240&Quality=Standard"];
_imageView = [[MotionJpegImageView alloc] initWithFrame:CGRectMake(self.view.frame.origin.y, self.view.frame.origin.x,
[[UIScreen mainScreen] bounds].size.height,[[UIScreen mainScreen] bounds].size.width)];
_imageView.url = url;
[self.view addSubview:_imageView];
[self.view sendSubviewToBack:_imageView];
[_imageView play];
SKView *spriteView = (SKView *) self.view;
JoystickScene* joystick = [[JoystickScene alloc] initWithSize:CGSizeMake(768,1024)];
[spriteView presentScene: joystick];
[self.view bringSubviewToFront:spriteView];
}
JoystickScene.m
- (void)didMoveToView: (SKView *) view
{
SKSpriteNode *jsThumb = [SKSpriteNode spriteNodeWithImageNamed:#"joystick.png"];
SKSpriteNode *jsBackdrop = [SKSpriteNode spriteNodeWithImageNamed:#"dpad.png"];
joystick = [Joystick joystickWithThumb:jsThumb andBackdrop:jsBackdrop];
joystick.position = CGPointMake(jsBackdrop.size.width, jsBackdrop.size.width);
velocityTick = [CADisplayLink displayLinkWithTarget:self selector:#selector(joystickMovement)];
[velocityTick addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
joystick.zPosition = 1;
[self addChild:joystick];
}

The spritekit SKView is the view of your viewcontroller. Every subview will be rendered on top of it. Is there a special reason you want to use Spritekit for the joystick? Otherwise I would suggest to make it with standard UIKit elements and just add it after the imageView.
Using an SKView on top of other views will not give you the desired results, since it cannot have a transparent background.
Another issue with your code: you shouldn't create and add views in viewWillLayoutSubviews, since this method could be called multiple times (every device Rotation for instance). Use viewDidLoad instead.

Related

How to make my sprite kit scene appear above a UIView?

I've done some searching but haven't found anything directly addressing my issue: I have an SKScene with several SKNodes each with SKSpriteNode objects for my game, and I am using a background UIImageView (there are some things I need to do with background that cannot be reasonable done with sprite kit - to my knowledge at least); the problem I'm having is that the UIImageView appears above everything and I can't see my game objects.
In my view controller (.m file) I have the following code to call my scene
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SKView * skView = (SKView *)self.view;
SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
In the GameScene above, I have several SKNodes where all my game objects are children of (for example nodeHDU, nodePlay, nodePauseMenu...), and I am using a UIImageView as my background (I am switching between background scenes over time and am using some nice transitions such as UIViewAnimationOptionTransitionCrossDissolve; couldn't accomplish this with a SKSpriteNode as background without using multiple SKSpriteNodes and an intricate array of SKActions so I"m using UIImageView)
UIView *backgrounds = [[UIView alloc] init];
[self.view insertSubview:backgrounds belowSubview:self.view];
UIImageView *imageBackground = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height)];
[backgrounds addSubview:imageBackground];
[backgrounds sendSubviewToBack:imageBackground];
[imageBackground setImage:[UIImage imageNamed:#"1-A.png"]];
imageBackground.alpha=1.0;
However, the ImageBackground above is the only thing I see when running the app in Xcode. The other objects from my game (children of other nodes) exist, and various NSLog statements are getting called, but they seem to appear behind the ImageBackground I have. The code above "belowSubview" and "sendSubviewToBack" don't help.
So How can I send the imageBackground to actually be the background?
Your problem is here that you are directly setting SKView as a controller view. To overcome from this issue don't make self.view a type of SKView. Do the following step in your storyboard or Xib :
Add an UIImageView on the controller's view first. Set the image as well.
Then add a SKView over the UIImageViewand set UIClearColor to the background colour.
So the complete hierarchy is like UIViewController->UIView->UIImageView->SKView

Can you add a sprite kit particle emitter to the background of a UIViewController?

I'm new to sprite kit game development and I was wondering if it's possible to add a particle emitter to a UIViewController. I want to have buttons on the screen along with the particle emitter for an app idea I have which is the problem. There is no built in button object for SKScene, so I decided maybe it would be better to add a particle emitter to a UIViewController class instead. Is it possible to make a particle emitter in the background and to place buttons on top of it that way?
Is there a specific reason you want to use SpriteKit for this? If you just want to integrate particle effects in a UIViewController, look into using a view and adding a CAEmitterLayer and CAEmitterCell.
They work great without using any of SpriteKit, and performance is good.
One of the good features about SpriteKit is the easiness of the implementation with UIKit:
Create a SKScene subclass:
//ParticleScene.h
#import <SpriteKit/SpriteKit.h>
#interface ParticleScene : SKScene
#property SKEmitterNode *emitter;
#end
//ParticleScene.m
#import "ParticleScene.h"
#implementation ParticleScene
-(instancetype)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
self.backgroundColor = [UIColor clearColor];
[self addEmitterNode];
}
return self;
}
-(void) addEmitterNode{
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"SpriteKitParticleEmitter" ofType:#"sks"]];
emitter.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height);
[self addChild:emitter];
}
#end
Create SpriteKitParticleEmitter.sks
Top menu -> File / New / File... / (Resource) SpriteKit Particle File
Here you can modify the emitter as you want.
Add emitter to your UIViewController
You can add a UIView to your Storyboard and set it's subclass to SKView, or you can add the SKView programatically:
Add in -(void) viewDidLoad method:
SKView *spriteKitView = self.particleView; // Defined on storyboard
//Use SKView *spriteKitView = [[UIView alloc] initWithFrame:frame] to create it programatically and then [self.view addSubView:view];
spriteKitView.showsFPS = NO;
spriteKitView.showsNodeCount = NO;
spriteKitView.allowsTransparency = YES; //Prevents this view to show as a black square
spriteKitView.backgroundColor = [UIColor clearColor];
SKScene *scene = [JPParticleScene sceneWithSize:view.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[view presentScene:scene];
If you use a Storyboard, just place self.spriteKitView behind your UIButtons.

Adding UITextView to a scene in SpriteKit

I trying to create a game, and following Apple's advice I am using multiple scenes.
You could consider the game to be a text heavy point and click adventure.
Therein lies the problem. Lots of text, having done a bit of a search, it seems the recommend way, or rather the current only way to do this is with a UITextView thus:
[self.view addSubview: textView];
This is all well and good if you only have one SKScene, as that is the scene being displayed by the current view/SKView.
My problem is, that when I add the text to my scene's *view, which isn't the first scene the app loaded (its the third, or higher), the app jumps back to the first scene it loaded (which is my menu scene) and happily displays the required text.
Anybody got any idea why? I have menu scene transitioning to scene one, to scene two (in which I wish to display the test).
Please don't say I need a view per scene if I want to display more than a handful of words per scene, that just doesn't really make sense, but perhaps neither does my usage of SpriteKit.
I am still some what stunned there is no SK equivalent of the UITextView.
Anyway, any help, pointers would be great, thank you.
Ok, here are the relevant parts of the code.... I think.
Controller:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Configure the view.
SKView *skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
skView.showsDrawCount = YES;
// Create and configure the scene.
SKScene *scene = [[GTMainMenu alloc] initWithSize:skView.bounds.size];
// Present the scene.
[skView presentScene:scene];
}
where GTMainMenu is a subclass of SKScene, and has a button (orange box) to an "act" (A subclass of GTScene, itself a subclass of SKScene), this will cause a transition to the act, which has another button to the first scene.
Only you never make it to the scene, as the viewDidLoad returns you to the main menu.
This is the viewDidLoad of the scene, which will cause it to "jump" back to the main menu:
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
if (!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(self.size.width/2, self.size.height/2+20, 200, 40)];
textView.center = self.view.center;
textView.textColor = [UIColor blackColor];
textView.font = [UIFont systemFontOfSize:17.0];
textView.backgroundColor = [UIColor whiteColor];
textView.text = #"Where am I?";
[self.view addSubview:textView];
}
There is a git repo available here.
This is a striped down version of my project, removing everything that is unrelated to the issue at hand.
If you will excuse the code, my day job is Java, and I am struggling with certain concepts in Objective C at the moment.
Oh and sorry I managed to include the usersettings :S
Your view controller's viewWillLayoutSubviews method is not safeguarded against repeated execution. This method will not just run at app launch but every time the view rotates and resizes.
Add a check before creating/presenting a scene in that method:
if(self.view.scene == nil) { /* present scene */ }
Have you looked into SKLabelNode? I used it extensively in my SpriteKit game. If you need your SKLabelNode to do anything fancy (physics, etc.), you can just add it to a parent SKSpriteNode.

Why would CALayer animation work after loading view, but not just after initialization?

I have a custom view (a small indicator derived from UIView with a rotation animation) which has basically a heart icon (UIImageView) on middle and a few balls (another UIImageView) rotating around it using layer animation. Here is my code:
-(void)performInitialization{
self.backgroundColor = [UIColor clearColor];
CGRect frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
[imageView setImage:[UIImage imageNamed:#"RedHeart"]];
balls = [[UIImageView alloc] initWithFrame:frame];
[balls setImage:[UIImage imageNamed:#"AngularBalls"]];
[self addSubview:imageView];
[self addSubview:balls];
[balls.layer beginRotating];
}
...where my category on CALayer has:
-(void)beginRotatingWithAngularVelocity:(float)velocity{
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.removedOnCompletion = YES;
rotationAnimation.repeatCount = 999999;
rotationAnimation.duration = velocity;
rotationAnimation.cumulative = YES;
rotationAnimation.fromValue = [NSNumber numberWithFloat:0];
rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2];
[self addAnimation:rotationAnimation forKey:ROTATION_KEY];
}
-(void)beginRotating{
[self beginRotatingWithAngularVelocity:0.7];
}
performInitialization is called in the init[WithFrame|WithCoder] method of my view. I have one of these views in my storyboard's main view and the view animates perfectly. If I put several instances of my custom view into that main view, they all animate perfectly too. There is no problem if I put my view into any view on that storyboard. However, when I put that same view into a view from a nib, it won't animate. The same code, the same settings in IB (copy pasted to make sure everything is the same), but the view is still, stuck on the initial view as if there was no animation attached to the layer). Why would that happen? How can I make that animation work on nibs?
UPDATE: The problem appears to be related to having the animation on view's initializer. In some occasions, I am animating right inside initialization, but sometimes, after it is loaded (e.g. user clicked something and something is downloading). The problem appears to be consistent with the former case. My previous fallacy about being about storyboard vs. nibs apparently is just coincidence. I've updated the title accordingly.
Initialization is too early. You need to wait until the view is in your interface (signaled to the view by didMoveToWindow. Until then, the view is not part of the rendering tree (which is what does the drawing/animation of its layers).

Confused with a simple example of paging and panning. Obj-C

Studying iOs Development by - The Big Nerd Ranch Guide" (Conway and Hillegass)
Chapter "Subclassing UIView and UIScrollView"; Panning and paging.
The following chunk of code being typed in the
- (BOOL)application: didFinishLaunchingWithOptions: method.
(HypnosisView - is a custom made class that actually performs the drawing on the screen.)
Can't understand following code:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//-------Adding a scrool option-----------
CGRect screenRect=[[self window] bounds];
// create the UIScrollView to have the size of the window, matching its size
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[scrollView setPagingEnabled:YES];
[[self window] addSubview:scrollView];
// create the HypnosisView with a frame that is twice the size of the screen (with a big
// width)
CGRect bigRect = screenRect;
bigRect.size.width *=2.0;
HypnosisView *view=[[HypnosisView alloc] initWithFrame:screenRect];
// add the HypnosisView as a subview of the scrollView istead of the window
[scrollView addSubview:view];
// move the ractangle for the other HypnosisView to the right, just off the screen
screenRect.origin.x = screenRect.size.width;
HypnosisView *anotherView = [[HypnosisView alloc] initWithFrame:screenRect];
[scrollView addSubview:anotherView];
// tell the scrollView how big its virtual world is
[scrollView setContentSize:bigRect.size];
So our goal is to create a view instance with an width bigger than the iphone screen.
First we are declaring new variable "screenRect" that has bounds of a "window".
Then we are creating an instance of "UIScrollView" that has frame dimensions same as the
"screenRect" same as the window.
Making paging enabled.
Adding our newly created "scrollView" to the hierarchy of views.
So we have parent "window" and child "scrollView" (that has same dimensions as our main window)
Declaring a new variable "bigRect", and making it equal to our previously declared "screenRect".
Setting bigRect's "width" property to be twice as much.
Creating a new "view" object that is an instance of our custom made Hypnosis class that actually performs the drawing. We set our view's frame to be the same as our "screenRect" frame.
Adding our newly created "view" to the hierarchy of views. Now we have 3 level hierarchy: UIWindow--> UIScrollView-->HypnosysView
9.Now here, I don't understand what this line of code does and why do we need it (screenRect.origin.x = screenRect.size.width;)
10). Why are we creating another instance of HypnosisView in the next line?
11). at the end we notify scrollView about how big its size.
9.Now here, I don't understand what this line of code does and why do we need it (screenRect.origin.x = screenRect.size.width;)
10). Why are we creating another instance of HypnosisView in the next line?
The example will display 2 HypnosisViews which are side by side in a scroll view. The second one is off screen. So you have to drag/page the scroll view to see it.
screenRect.origin.x = screenRect.size.width
This just positions the 2nd hypnosis view to the right of the fist one.

Resources