I'm new to iOS and Cocos development.
I currently have a basic app going on in my HelloWorldLayer class. It contains my sprites and touch interaction methods and all is well.
I'm trying to add another "panel" (UIView?) over top of what is currently seen. Eventually this panel will have buttons or other things that will interact with the main canvas.
How can I include another UIView onto the canvas screen? Through my appDelegate, or my HelloWorldLayer?
Thanks
Here is one way to do it. I've used UITextView here but you could use the approach for any descendant of UIView. Bear in mind that UIKit's y coordinate is zero at the top-left of the screen, whereas Cocos2D's is zero at the bottom left.
// Make your subview
UITextView* t = [[UITextView alloc] initWithFrame: CGRectMake(10, 10, 200, 200)];
t.backgroundColor = [UIColor blackColor];
t.textColor = [UIColor whiteColor];
t.text = #"Hello UIKit!";
t.editable = NO;
// Add it as a subview of the Cocos2D view
UIView* cocosView = [[CCDirector sharedDirector] openGLView];
[cocosView addSubview:t];
Alternatively you could try Blue Ether's CCUIViewWrapper, repository here.
Related
I've been trying to figure out an "easy" way to have a scrollable vertical list for my SpriteKit game.
I'm going to use it to implement the shop / upgrades part of my game so I would like the user to be able to scroll down the list to see the different upgrades that are possible.
I've seen quite a few posts on integrating a UIScrollView into SpriteKit but most of them seem to scroll the whole screen.
What I have so far is:
-(void) createSceneContents
{
[super createSceneContents];
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10,100, 250, 200)];
_scrollView.contentSize = CGSizeMake(2000, 120);
_scrollView.scrollEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.backgroundColor = [UIColor brownColor];
[self.view addSubview:_scrollView];
//Test sprites for scrolling and zooming
SKSpriteNode *greenTestSprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor]
size:CGSizeMake(25, 25)];
[greenTestSprite setName:#"greenTestSprite"];
greenTestSprite.position = CGPointMake(25, 125);
[self addChild:greenTestSprite];
//[self addChild: [self newMenuNode]];
}
I'm trying to think what the easiest way is for me to be able to add the green test sprite into that UIScrollView. can't get my head round the way that other people have done this as they seem to be connecting the scrolling of the UIScrollView with the SKView to make the screen seem like it's scrolling.
I'm sure there is a much easier way of doing this but been searching for a while now.
Any help would be appreciated.
Thanks allot in advance.
I would avoid using UIKit components if possible. When you have a UIView that is parented to UIKit components, it makes it difficult to interact with your SpriteKit components that live in the SKView. It's pretty easy to subclass SKNode and make a "ScrollingNode" that uses gesture recognizers on the SKView to do the scrolling. Here's an example:
https://github.com/JenDobson/SpriteKitScrollingNode
Another great tutorial to take a look at is http://www.raywenderlich.com/44270/sprite-kit-tutorial-how-to-drag-and-drop-sprites. Go to the part at the end on gesture recognizers.
You can use UIKit in a SpriteKit application.
If you use a UIScrollView you are going to end up having to get your hands dirty regarding re-use of cells, layout, user-interaction etc. You can actually make a cell look pretty good (and a cell is a UIView so you can sack a bunch of subviews in there) if you just use (and customise) a UITableView
Your hierarchy would be:
Scene:
TableView
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).
I have more of a generic question. I have a custom class that subclasses UIView. I override '-drawRect' to draw points in a line which I successfully did. Now I declare another UIView object inside my class that I want to animate. It should be a white circle outline which I'd like to move with an animation and this is why I want it to be of UIView type. But how can I draw inside it? What if I have 3 other similar cases for example? So my point is, I want to keep all of the functionality inside the component's class and have custom drawing for one or more UIView subviews. I think I am missing something on conceptional level.
In ActionScript 3 I would do it so:
var first:MovieClip = new MovieClip();
first.graphics...
......
var nTh:MovieClip = new MovieClip();
nTh.graphics...
This is what I want to achieve (as I said I've already made the drawing of the dots inside -drawRect, now I want to make the bigger circle which has to be animated later):
What I did is creating an UIView instance and setting its bounds and position in -initWithFrame:
//circleFrameSideSize will serve as the diameter of the circle
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, circleYPos, circleFrameSideSize, circleFrameSideSize)];
circle.layer.borderColor = [UIColor whiteColor].CGColor;
circle.layer.borderWidth = 1;
//here we do the trick for making the circle with using layer's corner radius
circle.layer.cornerRadius = circleFrameSideSize/2;
[self addSubview:circle];
This is absolutely confusing. On a 3.5" iPhone simulator, all of the UIButtons on my app work just fine. However, when I launch on the 4" iPhone simulator, all of the UIButtons on the left side of the app do not receive any click events.
Below are screenshots of the 3.5" size and the 4" size. On the 4" size, I've added a line. Left of that line, none of the buttons receive click events. To the right of that line, all buttons behave normally. The left side of buttons 2, 5, and 8 do not respond to clicks, but the right sides of those buttons do respond.
UPDATE----
Thanks to #iccir, I've discovered more info. Apparently, my UIWindow is only 320x480 instead of 568x320 as it should be. I'm not touching the UIWindow in my code except to make it key and visible. In my MainWindow.xib I connect its IBOutlet to my rootViewController.
<UIWindow: 0xc097d50; frame = (0 0; 320 480); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0xc098460>; layer = <UIWindowLayer: 0xc097e70>>
I'm flabberghasted. Any idea why the UIWindow is incorrectly sized?
This is a pretty common issue: Your UIButton is outside of the bounds of one of its superviews. If clipsToBounds/masksToBounds is set to NO (the default), your UIButton is still going to show up, but touch events aren't going to be sent to it.
Let's simplify this case. Suppose a view controller with the following code:
- (void) viewDidLoad
{
[super viewDidLoad];
UIColor *fadedRedColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.25];
UIColor *fadedBlueColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:0.25];
CGRect containerFrame = CGRectMake(25, 25, 100, 100);
CGRect buttonFrame = CGRectMake(100, 100, 64, 44);
UIView *container = [[UIView alloc] initWithFrame:containerFrame];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:#"Button" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[button setFrame:buttonFrame];
[button addTarget:self action:#selector(_handleButton:) forControlEvents:UIControlEventTouchUpInside];
[container setBackgroundColor:fadedRedColor];
[button setBackgroundColor:fadedBlueColor];
[container addSubview:button];
[[self view] addSubview:container];
}
- (void) _handleButton:(id)sender
{
NSLog(#"Moooooo!");
}
Which looks like this:
The button is contained in container, but it resides outside of the container's bounds (the container is 100 pixels wide and 100 pixels tall, the button's origin is at 100, 100).
When you touch the screen, UIKit is going to start at the top of the view hierarchy (UIWindow) and call -[UIView hitTest:withEvent:] recursively until it finds the view that should handle the touch. However, in this example, UIKit will never descend into the container (since you touched outside its boundary), and thus the button subview will not be hit.
If we instead change the buttonFrame to be 50, 50, it looks like this:
The part of the button that overlaps with the container will respond to touch event. The part that resides outside of the container will not:
To debug a view that isn't fully touchable, you can try a debugging function like the following:
static void sDebugViewThatIsntTouchable(UIView *view)
{
UIView *superview = [view superview];
while (superview) {
CGRect rectInSuperview = [view convertRect:[view bounds] toView:superview];
CGPoint topLeft = CGPointMake(CGRectGetMinX(rectInSuperview), CGRectGetMinY(rectInSuperview));
CGPoint topRight = CGPointMake(CGRectGetMaxX(rectInSuperview), CGRectGetMinY(rectInSuperview));
CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rectInSuperview), CGRectGetMaxY(rectInSuperview));
CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rectInSuperview), CGRectGetMaxY(rectInSuperview));
if (![superview pointInside:topLeft withEvent:nil]) {
NSLog(#"Top left point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:topRight withEvent:nil]) {
NSLog(#"Top right point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:bottomLeft withEvent:nil]) {
NSLog(#"Bottom left point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:bottomRight withEvent:nil]) {
NSLog(#"Bottom right point of view %# not inside superview %#", view, superview);
}
superview = [superview superview];
}
};
Edit:
As you mentioned in the comments, the culprit view was the main UIWindow, which was sized to 320x480 rather than 320x568. Turning on "Full Screen at Launch" in the xib fixed this.
Of course, the question is: "Why?" :)
If you pull up your xib file in a text editor, you will notice that a width of 320 and height of 480 are hardcoded to the window. When the xib is decoded at launch time, the window is initially constructed with this 320x480 frame.
UIKit then queries -[UIWindow resizesToFullScreen] (a private method). If this returns YES, the UIWindow does the equivalent of [self setFrame:[[self window] bounds]].
Toggling the "Full Screen at Launch" flag in Interface Builder directly toggles the private UIWindow.resizesToFullScreen flag.
Let me guess, this happens only in the landscape mode, right ?
I had the same issue in my app when I was developing specifically for the iPhone-4S. But when I began testing on the iPhone-5, touches on the bottom did not work. It were the frame. Make sure frames are set to bounds, both in the code, and the XIB file (if there is one). Different frames/bounds in the man and xib files, might also result in such behaviour.
I eventually removed the xib files, and did everything programmatically. One thing I learnt was, set your frames in viewWillAppear or viewDidAppear methods instead of viewDidLoad. Also check the frame/bounds in your RootViewController. One last thing, try not to use constant value for frames, use referential frames with respect to superview.
PS, one way to know if it really is the frames/bounds that are responsible for this behaviour is, setting the masksToBounds to YES, for the views. That way, your views will not be visible outside their rects.
What is the best way to go about making an app that does the following: the user taps at locations, and on the locations, a square is drawn. There is no way to erase the squares. You simply tap wherever you want and squares of a predefined size are drawn.
I was thinking to make a custom UIView and override the drawRect method by keeping a list of all the (x,y) locations of the squares and then calling [customView setNeedsDisplay] and drawing all the squares everytime a new square is drawn.
Is there a better way?
In Java, I would use an offscreen image, draw the square onto the offscreen image, and then draw the image onto the screen on every repaint() call. But, is this good to do for iPad? If so, what is code that will let me initialize a UIImage and draw a square onto it?
You could just add an empty UIView, which you only set the background color on, and then add it to the view you are tapping.
Example of adding a yellow square in the viewcontroller's viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 30, 30)];
square.backgroundColor = [UIColor yellowColor];
[self.view addSubview:square];
[square release];
}
Note: You can also give each square an id in its tag and later retrieve them with viewWithTag: from their superview (i.e. self.view from the controller)