Thanks for your help in advance!
I'm new to OOP, so this problem may be really basic, but I've searched for hours and still cannot find a good solution.
I'm using Cocos2d and Box2d in the project. In my GameLayer.mm file, I have a label to show current score. And there's is a custom sprite derived from CCSprite.
Now, I wanna increment current score from my custom sprite class when the property of sprite "isDead" is changed to true. As follows:
- (void) setIsDead
{
isDead = 1;
// then increment score
}
My question is how I can increment score from this subclass? I cannot access the instance or instance method of GameLayer.mm from this subclass. I tried to change the function of incrementing score from instance method to class method, and make score as a global instance, but I got duplicate error later.
Thanks for any advice!
Here is another approach I like: delegates.
First, go to your custom CCSprite header and create a new protocol. Basically, add this:
#protocol MyCustomDelegate
-(void)spriteGotKilled:(CCSprite*)sprite;
#end
Next, you need to modify your custom CCSprite to store its delegate. Your interface would look like this:
#interface MySprite {
id delegate;
}
#property (retain,nonatomic) id delegate;
Now go to GameLayer.h and make it implement the protocol:
#interface GameLayer : CCLayer <MyCustomDelegate>
Next implement the protocol method in your layer:
-(void)spriteGotKilled:(CCSprite*)sprite {
NSLog(#"%# got killed!",sprite);
}
And, finally, go to your setIsDead method:
-(void)setIsDead {
isDead = 1;
// Check if we have a delegate set:
if ([self delegate] != nil) {
// Check if our delegate responds to the method we want to call:
if ([[self delegate]respondsToSelector:#selector(spriteGotKilled:)]) {
// Call the method:
[[self delegate]spriteGotKilled:self];
}
}
}
When you create your sprite, you must set the layer as its delegate. Something like this:
MySprite *sprite = [[MySprite alloc]init];
[sprite setDelegate:self];
Now whenever your sprite dies, spriteGotKilled will be called in your layer.
You could use The Observer Design Pattern here in which an observer listens to an event and performs an action accordingly.
So in your GameLayer.mm add the observer in the init function:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveIsDeadNotification:)
name:#"SpriteIsDeadNotification"
object:nil];
and add a function:
- (void) receiveIsDeadNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"SpriteIsDeadNotification"])
//update your label here
}
and in your custom sprite ,add the following line in the setIsDead method
-(void) setIsDead{
isDead =1;
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SpriteIsDeadNotification"
object:self];
}
also remember to remove the observer in the dealloc of GameLayer.mm
[[NSNotificationCenter defaultCenter] removeObserver:self];
This pattern will reduce coupling in your code as instances of one class are not trying to access the methods of another.
Related
Description of the problem is shown below:
Function contained in the UIViewController link functions contained in the NSObject
NSObject return value to another function contained in UIViewController
When a function referenced to UIViewController it does not update UILabel
UILabel found in UIViewController
NSObject class:
-(void)getCategoryId:(NSString *)categoryid {
categoryMap *catMap = [[categoryMap alloc] init];
[catMap getCategoryId:categoryid];
catMap.nePOI = categoryid;
}
UIViewController:
-(void)getCategoryId:(NSString *)categoryid {
self.label.text = categoryid;
}
You are creating an new categoryMap View controler object and you are updating its label, not the one from the categoryMap view controller that is being currently displayed.
To achieve what you want, the "NSObject" instance would have to have a refen-rence (property) pointing to the ViewController that is actually displayed on screen.
-(void)getCategoryId:(NSString *)categoryid {
// View controller is created
categoryMap *catMap = [[categoryMap alloc] init];
// label updated
[catMap getCategoryId:categoryid];
catMap.nePOI = categoryid;
// End of the method, View controller is destroyed
// See the problem here??? You are not updated the good viewController...
}
Although this is not a recommended way to go. Your viewController should ask the object what to display, not the other way around. But, in your case, you can fix this with a notification. Replace the implementation of getCategoryId: in the object class by
[[NSNotificationCenter defaultCenter] postNotificationWithName:#"categorySelected" object:categoryId];
and subscribe to it in your view controler viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getCategoryId:) name:#"categorySelected" object:nil];
and then replace the getCategoryId: method of your view controller by:
-(void)getCategoryId:(NSNotification *)notif {
self.label.text = (NSString *)(notif.object);
}
That should do... ;)
To better understand architecture behind the ViewController principles, please refer to Apple MVC documentation: https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
I want to update the view controller that is covered with a modal view in my application, I'm using swift. What I have currently is this:
The view on the right is the caller, and the views on the left are the modal views triggered by each button from the first one. They do basic edit, add new operations. These views are modally presented over the main one, and what I want to do is update the table view controllers enbedded in the two containers once I save the data from one of the modals.
I researched the use of one of the viewLoad events, but I'm kinda stuck at the moment. How can I do this?
Well you can do it in two ways
1. Delegate
2. NSNotificationCenter
In your Model class.h file
#protocol someProtoColName<NSObject>
-(void)finishedDoingMyStuff;
#end
#interface ModelClass()
#property (nonatomic,weak) id<someProtoColName> delegateObj;
Then in your ModelClass.m
-(void)someactionHappend{
//this is the method where you save your things call the delegate method inside here like this.
[self.delegateObj finishedDoingMyStuff];
}
Then in your CallerClass.h
#import ModelClass.h
#interface CallerClass:UIViewController<someProtoColName>
Inside CallerClass.m viewDidLoad
-(void)viewDidLoad{
ModelClass *model = [[ModelClass alloc]init];
model.delegateObj = self;
}
//Now declare the delegate method
-(void)finishedDoingMyStuff{
//update your code that this has happend. this will be called when your model class's button action inside which you sent the `self.delegateObj`message is invoked instantaneously.
}
NSNotificationCenter
CallerClass.m
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(someMethod) name:#"NOTIFICATIONNAME" object:nil];
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"NOTIFICATIONNAME" object:nil];
}
-(void)someMethod{
//something has happened, do your stuff
}
And in each of the Model class (if they are many or one dsnt matter)|
ModelClass.m
-(void)someactionHappend{
//this is your action method that you want to do stuff in the model
[[NSNotificationCenter defaultCenter] postNotificationName:#"NOTIFICATIONNAME" object:nil userInfo:nil];
}
Hope this helps you out.
I have a ViewController with a UITableView. As I wanted to split out the data handling I created an own class that answers UITableViewDataSource.
This class is supposed to first fetch data from CoreData and afterwards from a REST API.
How can the DataSource talk back to the ViewController to tell it to call reloadData on the TableView?
What's the best practice here?
I thought about:
KVO the DataSource's data and when the array change call reloadData
Handing over a block (with [self.table reloadData]) to the DataSource which gets executed every time the data changes in the DataSource
Make the table property public on the ViewController so the DataSource could call reloadData (which I don't really like as an idea)
Have a property on the DataSource which holds the ViewController with the Table to use it as a delegate (which sounds to me like a loop)
Are there any smart ways to do it? Or even common practice how to solve this?
Update:
I'm less interested in code how to do implement a certain design pattern. I'm more interested in the reasoning why to chose one pattern over the other.
Without more details, it sounds like you need a callback here. There are several methods that will work. If you have a 1 to 1 relationship (meaning your dataSource only needs to talk to the VC), then this is a good case for either:
1.) A delegate. Create your own delegate protocol for your dataSource and then have the VC adhere to that protocol (be the delegate).
2.) Do the same thing just using a block for a callback.
KVO will work just fine as well, but the above two are more in line with your scenario.
You could add a tableView property to your custom data source but that then blurs the lines of why you created that class in the first place.
For situations like this, I prefer delegates.
#class CupcakePan;
#protocol CupcakePanDelegate <NSObject>
- (void)cupcakesAreReadyForPan:(CupcakePan *)pan;
#end
#interface CupcakePan : NSObject <UITableViewDataSource>
#property (weak) id<CupcakePanDelegate> delegate;
#end
#implementation CupcakePan
- (void)bakingComplete {
[self.delegate cupcakesAreReadyForPan:self];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [cupcakes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [make a CupcakeCell];
}
#end
#interface CupcakeViewController <CupcakePanDelegate>
#end
#implementation CupcakeViewController
- (void)cupcakesAreReadyForPan:(CupcakePan *)pan {
[_tableView reloadData];
}
#end
I frequently use NSNotificationCenter for these types of interactions.
In your datasource write the following code:
#define ABCNotificationName #"ABCNotificationName"
#define ABCNotificationData #"ABCNotificationData"
// ...
[[NSNotificationCenter defaultCenter] postNotificationName:ABCNotificationName object:self userInfo:#{ ABCNotificationData: data }];
In your view controller do the following:
-(void)loadView {
// setup your view
[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(datasourceUpdated:) name:ABCNotificationName object:dataSource];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)dataSourceUpdated:(NSNotification*)notification {
id data = notification.userInfo[ABCNotificationData];
// respond to the event
[self.tableView reloadData];
}
Note, that if you don't have any piece of data to communicate back to the controller it becomes even easier. In your datasource write the following code:
#define ABCNotificationName #"ABCNotificationName"
// ...
[[NSNotificationCenter defaultCenter] postNotificationName:ABCNotificationName object:self];
In your view controller do the following:
-(void)loadView {
// setup your view
[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(datasourceUpdated) name:ABCNotificationName object:dataSource];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)dataSourceUpdated {
// respond to the event
[self.tableView reloadData];
}
I'm quite new to iOS development, and I've been trying to solve the following problem:
I have a ViewController displaying information that changes with time. I have another controller (TimeController) managing time. TimeController has an NSTimer firing every second to check whether I've entered a new time slot (there's some logic behind it, and if I've entered a new time slot that means that the information in the ViewController needs to be updated.
In my understanding, I need some kind of callback procedure but I couldn't figure out how to do it - I read about blocks but to be honest they're quite overwhelming and I couldn't relate my problem to the examples I saw.
The TimeController looks something like the following:
// TimeController.h
#interface TimeController : NSObject
#property (weak) NSTimer *periodicTimer;
#property NSInteger timeslot;
#end
// TimeController.m
#import "TimeController.h"
#implementation TimeController
-(void)startTimer {
self.periodicTimer = [NSTimer scheduledTimerWithTimeInterval:(1) target:self
selector:#selector(onTimer) userInfo:nil repeats:YES];
}
-(void)onTimer {
// check if anything has changed.
// If so, change timeslot. Notify "listening" objects.
}
In a simple example with a single ViewController depending on TimeController, I'm imagining something like this:
// ViewController.h
#interface ViewController : UIViewController
#property TimeController* timeCtrl;
#end
// ViewController.m
#import "ViewController.h"
#import "TimeController.h"
-(void)onNotificationFromTimeController {
// timeslot has changed in the TimeController.
NSInteger tslot = timeCtrl.timeslot;
// figure out new display value depending on tslot. Update the view
}
What's missing here (among other stuff like proper initialisation of timeCtrl) are the callback mechanisms. I'd appreciate any help on this!
Notifications
Add a listener to the event (let's call it "TimeNotificationEvent") in ViewController:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotificationFromTimeController) name:#"TimeNotificationEvent" object:nil];
In the onTimer method in TimeController.m, add the following code to post a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TimeNotificationEvent" object:nil];
Sidenote: To stop listening for notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"TimeNotificationEvent" object:nil];
Easy to setup
You can have multiple listeners for the same event
Blocks
Add a property in TimeController.h:
#property (nonatomic, copy) dispatch_block_t timerBlock;
Add a call to the timerBlock in TimeController.m:
-(void)onTimer {
// Check for nil to avoid crash
if (_timerBlock) {
_timerBlock();
}
}
Assign the block in ViewController:
_timeCtrl.timerBlock = ^{
// Do stuff to the UI here
};
A bit more complex (retain cycles, syntax etc)
Only one listener (with this particular example implementation)
Easier to follow the code
Most of the tutorials that I've read only explain Cocos2D examples in the HelloWorld class, but as I've started to build a simple game I need to know how to send an event to different classes, and for them to respond whenever it happens.
I have GameSceneLayer, a CCLayer class which loads in my different CCLayers of Sprites:
#implementation GameSceneLayer
+ (CCScene *)scene {
CCScene *scene = [CCScene node]; // Create a container scene instance
GameSceneLayer *gameLayer = [GameSceneLayer node]; // Create an instance of the current layer class
[scene addChild:gameLayer]; // Add new layer to container scene
return scene; // Return ready-made scene and layer in one
}
-(id)init
{
self = [super init];
if (self != nil)
{
Background *background = [Background node];
[self addChild:background z:0];
Player *player = [player node];
[self addChild:player z:1];
MainMenu *mainMenu = [MainMenu node];
[self addChild:mainMenu z:2];
}
return self;
}
#end
However, when my MainMenu CCLayer START sprite is touched I would like it to spawn in the PLAYER sprite from the Player CCLayer.
I'm guessing that I need a GlobalVariables.h with something like:
#define gameStart #"0"
So when the START sprite is pressed it changes gameStart to 1, and somewhere in the PLAYER sprite there is
if (gameStart == 1)
{
[self addChild:PLAYER];
}
However I'm not sure how to set up the code so that the PLAYER sprite is always looking for that information.
You have objects (instances of classes). You want one object to communicate with another. In Objective-C, that's called sending messages. In other languages it's simply calling a method.
You don't need global variables. Instead, the receiving object MainMenu needs to send a message to (call a method on) the Player object. How do you get two objects to know each other? You could put them in a stinky, loud, overcrowded discotheque and hope for the best.
Or you could simply let them talk to each other, but alas, they shouldn't. Since both are siblings of GameSceneLayer, they shouldn't hold references to each other themselves (danger of creating retain cycles unless you're using weak references).
But both have the same parent. So what does a good parent do when two siblings won't talk to each other? It relays the message!
In MainMenu, send a message to the parent GameSceneLayer:
[(GameSceneLayer*)self.parent gameWillStart];
The GameSceneLayer implements that selector, and forwards the message to any other object that should be informed about starting the game:
-(void) gameWillStart
{
[player gameWillStart];
[ingameUI gameWillStart];
// etc.
}
And Player also implements said selector:
-(void) gameWillStart
{
[self addChild:PLAYER];
}
Just one other thing: in GameSceneLayer, make player and all other objects ivars (instance variables) so that GameSceneLayer has these references readily available. The alternative would be to tag (less often used) objects and then use getChildByTag:
PS: adding PLAYER as child looks dubious to me. If you have already created whatever node PLAYER is, you should add it right away and if necessary, set it to be invisible and/or paused while the game hasn't started yet.
You can use a singleton GameState Manager, which keeps information on the game state.
Here is a little snippet:
+(GameManager*) sharedGameManager {
if (!_sharedGameManager) {
_sharedGameManager = [[self alloc] init];
}
return _sharedGameManager;
}
+(id) alloc {
NSAssert(_sharedGameManager == nil, #"Cannot create second instance of singleton Game Manager");
return [super alloc];
}
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
in that game manager you can have an enum with the game states, and set a property for it.
//Header
typedef enum {
kGameStarted,
kGameEnded
}GameState
#interface GameManager : CCNode {
}
#property (nonatomic) GameSate gameState;
Then back in your implementation file, you synthesize that GameState property, and create your own setter
//Implementation
#synthesize gameState = _gameState;
//Create your own setter, so you can notify all listeners
-(void) setGameState:(GameState) newGameState {
if (newGameState != _gameState) {
_gameState = newGameState;
//Notify listeners via NSNotification
[[NSNotificationCenter defaultCenter] postNotificationName:#"gameState" object:nil];
}
}
in the classes where you want to get the messages you just have to subscribe to the "gameState" notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onGameStateChange:)
name:#"gameState"
object:nil];
-(void) onGameStateChange{
if ([GameManager sharedManager].gameState == kGameStarted){
//start game
}
}