Quartz 2D MVC Drawing - iphone

all. I'm trying to follow a tutorial on making a ball bounce around the screen of an iPhone. The tutorial constructs the application in a MVC scheme. I'm having trouble wrapping my head around this concept when it comes to the drawRect method in the View implementation.
This is my Model header file:
#import <Foundation/Foundation.h>
#import "TestView.h"
#define BALL_SIZE 20.0
#define VIEW_WIDTH 320.0
#define VIEW_HEIGHT 460.0
#interface TestModel : NSObject
{
TestView* ball;
CGPoint ballVelocity;
CGFloat lastTime;
CGFloat timeDelta;
}
- (void) updateModelWithTime:(CFTimeInterval) timestamp;
- (void) checkCollisionWithScreenEdges;
#property (readonly) TestView* ball;
#end
The tutorial instructs me the user to override the init method of NSObject. I've also included the methods for controlling the "animation" logic:
- (id) init {
self = [super init];
if (self) {
ball = [[TestView alloc] initWithFrame: CGRectMake(0.0, 0.0, BALL_SIZE, BALL_SIZE)];
// Set the initial velocity for the ball
ballVelocity = CGPointMake(200.0, -200.0);
// Initialize the last time
lastTime = 0.0;
}
return self;
}
- (void) checkCollisionWithScreenEdges {
// Left Edge
if (ball.frame.origin.x <= 0) {
ballVelocity.x = abs(ballVelocity.x);
}
// Right Edge
if (ball.frame.origin.x >= VIEW_WIDTH - BALL_SIZE) {
ballVelocity.x = -1 * abs(ballVelocity.x);
}
// Top Edge
if (ball.frame.origin.y <= 0) {
ballVelocity.y = abs(ballVelocity.y);
}
// Bottom Edge
if (ball.frame.origin.y >= VIEW_HEIGHT - BALL_SIZE) {
ballVelocity.y = -1 * abs(ballVelocity.y);
}
}
- (void) updateModelWithTime:(CFTimeInterval) timestamp {
if (lastTime == 0.0) {
// initialize lastTime if first time through
lastTime = timestamp;
} else {
// Calculate time elapsed since last call
timeDelta = timestamp - lastTime;
// Update the lastTime
lastTime = timestamp;
[self checkCollisionWithScreenEdges];
// Calculate the new position of the ball
CGFloat x = ball.frame.origin.x + ballVelocity.x * timeDelta;
CGFloat y = ball.frame.origin.y + ballVelocity.y * timeDelta;
ball.frame = CGRectMake(x, y, BALL_SIZE, BALL_SIZE);
}
}
The View implementation file is the following:
#import "TestView.h"
#implementation TestView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect) rect {
}
#end
Finally, my View Controller:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
gameModel = [[TestModel alloc] init];
[self.view addSubview:gameModel.ball];
// Set up the CADisplayLink for the animation
gameTimer = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateDisplay:)];
// Add the display link to the current run loop
[gameTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void) updateDisplay:(CADisplayLink *) sender {
[gameModel updateModelWithTime:sender.timestamp];
}
OK, so now that I've provided a look at the structure of the code (hopefully I've given enough) I can get to my question. So when I add anything to drawRect a new object is drawn and does not get "animated" by the model logic methods.
Right now I have a bouncing square. When I try to fill the square with an ellipse in drawRect, I get a new object, drawn how I want, that just sits at 0,0 while the bouncing square is still active.
I'm sure I'm missing something really big here, but I've been banging my head against the wall for hours and can't figure it out. Any help would be greatly appreciated!

A couple of things here:
1- Have you overriden the view class in SB to TestView?
2- Looking at your code, I do not see how your model is connected to your View through your controller. Recall from the MVC model, that the Controller is on top and talks down (pyramid style) to the model and the view and so, somewhere in your view controller:
a) you need to instantiate your model
b) get values from your model and pass them to your view variables - which would be called in drawrect
Last but not least, lookup setNeedsDisplay which is the way to call and refresh the view.
Hope this helps without spoiling the assignment.

Related

How to add multiple UIImageViews with same property

I want to have multiple images (in this case, basketballs) move around on the screen.
How can I create multiple balls with the same name? I have one ball which is already moving around. So all the other balls should use the same calculation with the same property name for bumping and stuff like that.
Can someone help me? I know Java well, but not Obj C.
EDIT:
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ball movement.y);
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
This is my simple calculation. This code is in my loop method. this loop method is called by this code in my viewDidLoad method:
[NSTimer scheduledTimerWithTimeInterval:0.050 target:self selector:#selector(loop) userInfo:nil repeats:YES];
In the MainView .h file i wrote
BasketballView *ball;
new Balls are created by this code in my button action method:
for (int x = 0; x < numberOfBalls; x++)
{
ball = [[BasketballView alloc]initWithFrame:CGRectMake(arc4random()%100,arc4random()%100, 40, 40) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:ball];
}
When i run my application, only one ball is moving!
do you need more code?
thanks for your help
EDIT 2:
every ball is moving now, but they are not moving correctly .. here is what i did. i created a NSMuteableArray. i added this code in my Button action method:
[array addObject:ball];
after that, i added in the loop method some code. here is the new loop code that i have right now:
for (int i=0; i<array.count;i++){
ball = [array objectAtIndex:i];
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ballmovement.y);
}
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
right now, every ball is changing direction when one ball hits the boarder. so all the other balls move like the this one ball. they ignore the boarders. only if this one ball hits the boarder, they change their direction.
if i put the code for the ball movement inside the for-loop, the balls change their direction everytime one of the balls hits the boarder. they are moving like crazy, because every .5 seconds a ball hits one of the boarders
I think what you looking for is a way to add a bunch of objects from the same class. To do this, I would suggest that you overwrite an UIImageView class. If you do that, you can add custom properties to the balls. See the example below:
In your header file, you would have the following:
#import <UIKit/UIKit.h>
#interface BasketBallView : UIImageView
#property (nonatomic, strong) NSString *customProperty;
#property (assign) NSInteger anotherProperty;
//custom init method if you want
- (id) initWithFrame:(CGRect)frame andCustomProperty:(NSString *)CustomProperty andAnotherProperty:(NSInteger)AnotherProperty;
-(void)startMovement;
#end
The implementation would be something like this:
#import "BasketBallView.h"
#implementation BasketBallView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setImage:[UIImage imageNamed:#"your_bb_image.png"]];
}
return self;
}
-(id) initWithFrame:(CGRect)frame andCustomProperty:(NSString *)CustomProperty andAnotherProperty:(NSInteger)AnotherProperty
{
self = [self initWithFrame:frame];
if (self) {
self.customProperty = CustomProperty;
self.anotherProperty = AnotherProperty;
}
return self;
}
-(void)startMovement{
[NSTimer scheduledTimerWithTimeInterval:0.050 target:self selector:#selector(loop) userInfo:nil repeats:YES];
}
-(void)moveBall{
self.center = CGPointMake(self.center.x + ballmovement.x, self.center.y + ballmovement.y);
if (self.center.x > self.superview.frame.size.width || self.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (self.center.y > self.superview.frame.size.height || self.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
To then use and access your object from your viewcontroller, do it as follows:
BasketBallView *bb = [[BasketBallView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:bb];
You could, of course, throw the above lines of code into a loop, and add it multiple times to the view.
Edit:
If you want to add the BasketBallView to the view for the Interface Builder, you'll need to customize the initWithCoder method in the implementation file. In that method, you would similarly set your image, etc.
I would suggest that you don't add the BasketBallViews via the Interface Builder, however. Since you need a random number of balls, you need to add them from the code and use the method I outlined above. You would add them like this:
for(int i = 0; i < yourRandomNumber; i++){
BasketBallView *bb = [[BasketBallView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:bb];
}
You can't add copies of views to another view, in other words, you can't create the ball in the IB, then make copies of it in the code and add it to your view.
The properties I added to the class are just examples of some customization that you could add to the class if you needed to.
You can certainly still use the center property since the BasketBallView is an subclass of the UIImageView, and therefore inherits all the UIImageView properties.
If you have access to iOS7, or can wait another month, Apple's new SpriteKit framework will be able to do a lot of the physics and GPU rendering for you. Then it's just a case of making multiple instances of your "Basketball Class".
You need to add the uiimageview several times on the screen.... and set tag with number 100 or 1001
like
balview.tag = 100;
for (int x=0; x < 5; x++)
{
[self.view addSubview:ballView];
}
do calculation for all in one time
forin(UIImageView *view in [self.view subviews])
{
if (view.tag == 100)
{
//do something
}
}
for (int x = 0; x < 5; x++)
{
UIImageView* ball = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YourImageName"]];
ball.frame = CGRectMake(20, 20*x, 20, 20);
[self.view addSubview:ball];
}
I can think of two solutions:
1: Add all balls to an array and access them by the index (seems more feasible to me)
2: Add them tags and access them by
[self.view viewWithTag:<BallTag>];
Edit: Seems like you have chosen the first alternative. Now you need to improve your method for only one ball to move and invoke it whenever you desire.
- (void)moveBallWithIndex:(int)idx{
//for (int i=0; i<array.count;i++){
//Loop removed. It was the reason why all the balls were moving
BasketballView * ball = [array objectAtIndex:idx];
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ballmovement.y);
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
}
}
Edit: It seems like you carried the move method to the ball class, for limitations I can think o fthree alternatives:
Create two properties on the ball class named "maxX" and "maxY" during init/set after init and use them in the move method
Improve your move method with two parameters like
- (void)moveWithhinMaxX:(float)maxX andMaxY:(float)maxY;
and use them for limitation inside the move method
Improve your move method by giving the superview on the move method like:
- (void)moveWithinView:(float)newSuperview{
//[newSuperview addSubview:self]; add as subview if necessary
//Use newSuperview's bounds for limitations
and inside it you can add the ball as subview to superview and use its bounds for limitation

Adding a label to an animated sprite with CCSpriteFrameCache

I have an animated sprite using CCSpriteFrameCache that represents my game character. I want to add a text label as a child of this sprite to represent the (dynamic) character name, however I run into complications with 'CCSprite is not using the same texture id'. Because the text is dynamic, I can't include it in the sprite sheet the character is using. What is the best approach to get this text overlay on to my character?
I tend to use a CCNode derivative for this, that also implements the RGBA protocol. You can play the animation from inside the node, and add the label to the node (as well as others like a health bar), and then move the node, make it visible (or not), use some animation like fading in and out the entire content.
Composition over inheritance is true in this context.
You can see my code which should be a template for you to implement on your own.
#interface Player : CCNode
+ (instancetype)playerWithName:(NSString*)name sprite:(CCSprite*)sprite
- (id)initWithName:(NSString*)name sprite:(CCSprite*)sprite;
#property (nonatomic, retain) CCSprite* sprite;
#property (nonatomic, copy) NSString* name;
#end
#implementation Player {
BOOL _nameLabelAdded;
BOOL _spriteAdded;
}
#define LABEL_NAME_TAG 0e1
#synthesize sprite = _sprite, name = _name;
+ (instancetype)playerWithName:(NSString*)name sprite:(CCSprite*)sprite {
return [[[self alloc] initWithName:name sprite:sprite] autorelease];
}
- (id)initWithName:(NSString*)name sprite:(CCSprite*)sprite {
if (self = [super init]) {
//NOT TESTED..
self.name = name;
self.sprite = sprite;
}
return self;
}
- (void)setSprite:(CCSprite*)theSprite {
if (theSprite != _sprite) {
[_sprite release];
_sprite = [theSprite retain];
if (!_spriteAdded) {
/* initialize position etc here */
[self addChild:_sprite];
_spriteAdded = YES;
}
}
}
- (void)setName:(NSString*)theName {
if (![_name isEqualToString:theName]) {
[_name release];
_name = [theName copy];
if (![_name isEqualToString:#""]) {
CCLabelTTF* name_label;
if (!_nameLabelAdded) {
name_label = [CCLabelTTF labelWithString:_name];
/* init the name_label */
[self addChild:name_label z:1 tag:LABEL_NAME_TAG];
_nameLabelAdded = YES;
} else {
name_label = (CCLabelTTF*)[self childWithTag:LABEL_NAME_TAG];
name_label.text = name;
}
}
}
}
#end
I had this situation on my last released game.
The best way to solve this problem was to create a parent node and add the label and sprite as child. It's better to manage the node separately, mainly because you can adjust the position of both without interfering with each other.
Node hierarchy:
- Node parent
- Sprite with CCSpriteFrameCache
- Label

Changing Color/Alpha/Filter to MKMapView iOS 6

Is there a way to apply CI Filters to MKMapViews? Or something similar? I'm trying to not have my map based app looks so beige. Is there way to apply RGBA filters?
Any help/tutorials direction appreciated. I see nothing in the native documentation that talks about changing look for MKMapView.
I don't think you can change the imagery before it is rendered to the screen. However, you can use an MKOverlayView over the entire world that achieves the same effect. The following should work, but treat it like pseudocode just to get you started.
#interface MapTileOverlay : NSObject <MKOverlay>
#end
#implementation MapTileOverlay
-(id)init {
self = [super init];
if(self) {
boundingMapRect = MKMapRectWorld;
coordinate = MKCoordinateForMapPoint(MKMapPointMake(boundingMapRect.origin.x + boundingMapRect.size.width/2, boundingMapRect.origin.y + boundingMapRect.size.height/2));
}
return self;
}
#end
#interface MapTileOverlayView : MKOverlayView
#end
#implementation MapTileOverlayView
-(void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGContextSetBlendMode(context, kCGBlendModeMultiply); //check docs for other blend modes
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.5); //use whatever color to mute the beige
CGContextFillRect(context, [self rectForMapRect:mapRect]);
}
#end
You need to have some class that implements the MKMapViewDelegate protocol to create the view...
#interface MapViewDelegate : NSObject<MKMapViewDelegate>
#end
#implementation MapViewDelegate
-(MKOverlayView*)mapView:(MKMapView*)mapView viewForOverlay:(id<MKOverlay>)overlay {
if([overlay isKindOfClass:[MapTileOverlay class]]) {
return [[MapTileOverlayView alloc] initWithOverlay:overlay];
}
return nil;
}
Finally, after you initialize your map, you need to set the delegate on the map and add the overlay...you must set the delegate before you add the overlay...
MapViewDelegate* delegate = [[MapViewDelegate alloc] init]; //you need to make this an iVar somewhere
[map setDelegate:delegate];
[map addOverlay:[[MapTileOverlay alloc] init]];
You could add a view on top of the map view,
nearly transparent with a slight color (e.g blueish to compensate that brown.)

Sprite and its body don't act the same way to forces

So I created a body and filled it with a sprite. The problem is that if I apply a force on the body, the sprite goes higher than the body (which I can see from debug_draw). Any idea why this is happening?
UPDATE
- (void)tick:(ccTime) dt {
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *playerData = (CCSprite *)b->GetUserData();
playerData.position = ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
playerData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
And here's how I call this in my init method:
[self schedule:#selector(tick:)];
The for loop which gets the User_Data is kept on repeating now and then so to update the position of the creature would be difficult.. Please move the sprites position in the tick method....
-(void) moveCreature
{
// Please use the position to set the directions. This is used to fall from upside down in Portrait mode
[spriteCreature setPosition:ccp(spriteCreature.position.x,spriteCreature.position.y-5)];
}
Now in the init method schedule the sprite
-(id) init
{
if((self = [super init]))
{
// Create your World and other stuffs
[self schedule:#selector(moveCreature)];
}
return self;
}
The above stuff would carry the sprite in the body n fall down...

Problem initializing an object with init in Objective-C

I have an object that i intalize it's propertis with call to an init function, it works fine,
when i tried to add another object and intalize it the first object didn't get the properites, how do i initalize it to more then one object with diffrent or the same properties?
- (void)viewDidLoad {
pic1 = [[peopleAccel alloc] init];
}
Class peopleAccel:
- (id) init
{
self = [super init];
if (self != nil) {
position = CGPointMake(100.0, 100.0);
velocity = CGPointMake(4.0, 4.0);
radius = 40.0;
bounce = -0.1f;
gravity = 0.5f;
dragging = NO;
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
acceleratedGravity = CGPointMake(0.0, gravity);
}
return self;
}
I see a problem with setting the delegate of sharedAccelerometer. Only one object can be a delegate of another object at a time. So you should create only one peopleAccel object.
EDIT:
If you need to send accelerometer events to more than one object, you can create a specific delegate object in charge of receiving accelerometer events and broadcasting them to your several peopleAccel objects via notifications. See this question for some hints: NSNotificationCenter vs delegation?
Create a proxy so multiple objects can receive accelerometer events.
Whether you should do this or use NSNotificationCenter is debatable and there are two camps, but personally I would use this approach. NSNotificationCenter has to check string names to recognise event type; this kind of approach could be ever so slightly faster especially with a bit more optimisation. A bit more typing but I would say also easier for someone else to follow.
Something like this...
/* PLUMBING */
/* in headers */
#protocol MyAccelSubDelegate
-(void)accelerometer:(UIAccelerometer*)accelerometer
didAccelerate:(UIAcceleration*)acceleration;
#end
#interface MyAccelSubDelegateProxy : NSObject <UIAccelerometerDelegate> {
NSMutableArray subDelegates;
}
-(id)init;
-dealloc;
-(void)addSubDelegate:(id<MyAccelSubDelegate>)subDelegate;
-(void)removeSubDelegate:(id<MyAccelSubDelegate>)subDelegate;
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:
(UIAcceleration *)acceleration;
#end
/* in .m file */
#implementation MyAccelSubDelegateProxy
-(id)init { self = [super init];
if (self!=nil) subDelegates = [[NSMutableArray alloc] init]; return self; }
-dealloc { [subDelegates release]; }
-(void)addSubDelegate:(id<MyAccelSubDelegate>)subDelegate {
[subDelegates insertObject:subDelegate atIndex:subDelegates.count]; }
-(void)removeSubDelegate:(id<MyAccelSubDelegate>)subDelegate {
for (int c=0; c < subDelegates.count; c++) {
id<MyAccelSubDelegate> item = [subDelegates objectAtIndex:c];
if (item==subDelegate) { [subDelegates removeObjectAtIndex:c];
c--; continue; }
}
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:
(UIAcceleration *)acceleration {
for (int c=0; c < subDelegates.count; c++)
[((id<MyAccelSubDelegate>)[subDelegates objectAtIndex:c])
accelerometer:accelerometer didAccelerate:acceleration];
}
#end
/* SOMEWHERE IN MAIN APPLICATION FLOW STARTUP */
accelProxy = [[MyAccelSubDelegateProxy alloc] init];
[UIAccelerometer sharedAcclerometer].delegate = accelProxy;
[UIAccelerometer sharedAcclerometer].updateInterval = 0.100; // for example
/* TO ADD A SUBDELEGATE */
[accelProxy addSubDelegate:obj];
/* TO REMOVE A SUBDELEGATE */
[accelProxy removeSubDelegate:obj];
/* SOMEWHERE IN MAIN APPLICATION SHUTDOWN */
[UIAccelerometer sharedAcclerometer].delegate = nil;
[accelProxy release];