I have a class defined as
#interface Board : SKSpriteNode
+ (Board *) initWithScreenSize:(CGRect)screen;
#end
#implementation Board
+ (Board *) initWithScreenSize:(CGRect)screen
{
Board *board = (Board *) [SKSpriteNode spriteNodeWithImageNamed:#"turn4_board"];
return (Board *)board;
}
#end
There are other methods/properties but this is the relevant portion. My problem is that after initializing board (via the SKSpriteNode call), the variable is of type SKSpriteNode. I can't make it of type "Board", thus later run-time calls to an instance of "Board" cause a crash saying that my method doesn't exist in the class SKSpriteNode. I am apparently making a rookie mistake, but I'm starting to go bald over this one. Please provide the requisite smack upside my head, and then please follow up with a helpful fix/suggestion. Thanks!
I should, perhaps, mention that I'm using Xcode 5.1.1 developing for iOS 7 under MacOS 10.9.4.
Because you created a SKSpriteNode not Board. Cast fool the compiler but it won't make it working.
#interface Board : SKSpriteNode
+ (instancetype) boardWithScreenSize:(CGRect)screen;
#end
#implementation Board
+ (instancetype) boardWithScreenSize:(CGRect)screen
{
return [self spriteNodeWithImageNamed:#"turn4_board"];
}
#end
You need to create a proper init method and conventionally a convenience constructor will start with boardWith...:
#interface Board : SKSpriteNode
+ (Board *)boardWithScreenSize:(CGRect)screen;
- (instancetype)initWithScreenSize:(CGRect)screen;
#end
#implementation Board
+ (Board *)boardWithScreenSize:(CGRect)screen
{
return [[Board alloc] initWithScreenSize:screen];
}
- (instancetype)initWithScreenSize:(CGRect)screen
{
self = [super initWithImageNamed:#"turn4_board"];
if (self) {
// Additional init
}
return self;
}
#end
Related
I have some abbreviated iOS Objective-C sample code (simplified from a larger project) that causes a crash in NSUndoManager that I can't explain.
Namely, when an object that is only held onto by the NSUndoManager deallocs (because it's beyond the levels of undo), and, according to the docs calls removeAllActionsWithTarget:self, I get an EXC_BAD_ACCESS.
// SimpleViewController.m
#interface ViewController ()
#property (nonatomic, strong) NSUndoManager *undoManager;
#end
#implementation ViewController
#synthesize undoManager;
// called from a simple button
- (IBAction)doItTapped:(id)sender
{
CoolObject *object = [CoolObject new];
object.undoManager = self.undoManager;
// according to docs, object will be retained by NSUndoManager here
// but target will not (which should be okay)
[self.undoManager registerUndoWithTarget:self selector:#selector(notCool:) object:object];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.undoManager = [NSUndoManager new];
self.undoManager.levelsOfUndo = 3;
}
and
// CoolObject.m
#implementation CoolObject
- (void)dealloc
{
[self.undoManager removeAllActionsWithTarget:self];
}
#end
After the 4th tap of the button (levelsOfUndo + 1), it crashes.
If I swap NSUndoManager with GCUndoManager, no crash.
Tested in iOS 10.2 sim and devices.
Thanks for any ideas!
Their are chances that you might be getting this error because self.undoManager is not retained at that point where you are using it. When the object is already deallocated and you try to access it, you will get bad access exception.
Try to change your code from this:
CoolObject *object = [CoolObject new];
to this:
#interface ViewController (){
CoolObject *object;
}
#property (nonatomic, strong) NSUndoManager *undoManager;
#end
#implementation ViewController
- (IBAction)doItTapped:(id)sender
{
object = [CoolObject new];
object.undoManager = self.undoManager;
// according to docs, object will be retained by NSUndoManager here
// but target will not (which should be okay)
[self.undoManager registerUndoWithTarget:self selector:#selector(notCool:) object:object];
}
#end
Hope this will help.
Just like me, you seem to have misinterpreted the admittedly inaccurately written documentation. The docs talk about "target", "object" and "target object" as if they were different things when they really mean exactly one and the same: the (id)target parameter of -removeAllActionsWithTarget:
In other words, in my opinion you should not need to call -removeAllActionsWithTarget: inside of CoolObject at all because CoolObject has been specified as the object of -registerUndoWithTarget:selector:object: whereas the target is your ViewController.
You may have to call -removeAllActionsWithTarget: in your NSViewController's -dealloc but even that is unnecessary in your example because your NSViewController owns the NSUndoManager and thus ViewController won't go away before undoManager does.
I'm following an Objective-C tutorial on how to animate a menu with UIKit Dynamics and I'm having some trouble translating the following code from Objective-C to Swift.
animator.h
#interface Animator : NSObject
+ (instancetype)animatorWithScreen:(UIScreen *)screen;
- (void)addAnimation:(id<Animation>)animatable;
- (void)removeAnimation:(id<Animation>)animatable;
#end
#interface UIView (AnimatorAdditions)
- (Animator *)animator;
#end
animator.m
#implementation Animator
{
}
+ (instancetype)animatorWithScreen:(UIScreen *)screen
{
if (!screen) {
screen = [UIScreen mainScreen];
}
Animator *driver = objc_getAssociatedObject(screen, &ScreenAnimationDriverKey);
if (!driver) {
driver = [[self alloc] initWithScreen:screen];
objc_setAssociatedObject(screen, &ScreenAnimationDriverKey, driver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return driver;
}
#implementation UIView (AnimatorAdditions)
- (Animator *)animator
{
return [Animator animatorWithScreen:self.window.screen];
}
#end
I was able to get everything else working, but I'm unsure how to get the UIView to have the animator property using Swift and also how to properly translate:
objc_setAssociatedObject(screen, &ScreenAnimationDriverKey, driver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
There is already an existing discussion about objc_setAssociatedObject for swift: "Is there a way to set associated objects in Swift?".
What you are trying to do with the Animator is called an extension. That of course is available for swift as well - take a look at the docs for how to create one yourself.
I have a custom object ProductCategory.
.h file:
#import <Foundation/Foundation.h>
#interface ProductCategory : NSObject
#property int productCategoryId;
#property NSString *name;
#property NSArray *children;
#property int parentCategoryId;
- (id)initWithId:(int)productCategoryId name:(NSString*)name;
- (id)initWithId:(int)productCategoryId name:(NSString*)name children:(NSArray*)chidren parentCategoryId:(int)parentCategoryId;
#end
.m file:
#import "ProductCategory.h"
#implementation ProductCategory
- (id)init {
if((self = [super init])) {
self.parentCategoryId = 0;
}
return self;
}
- (id)initWithId:(int)productCategoryId name:(NSString*)name {
if((self = [super init])) {
self.productCategoryId = productCategoryId;
self.name = name;
self.parentCategoryId = 0;
}
return self;
}
- (id)initWithId:(int)productCategoryId name:(NSString*)name children:(NSArray*)chidren parentCategoryId:(int)parentCategoryId {
if((self = [super init])) {
self.productCategoryId = productCategoryId;
self.name = name;
self.children = chidren;
self.parentCategoryId = parentCategoryId;
}
return self;
}
#end
It's just a normal object, I have done this 100000 times. The problem is, sometimes the instance of this object returns "0 objects" and sometimes returns the correct object.
For example, if I do this ProductCategory *category = [[ProductCategory alloc]init]; sometimes it returns a ProductCategory instance, and sometimes it returns "0 objects" so I cannot assign any value to this object.
I guess it should be something really stupid but I don't see it.
The way to fix it:
Restart XCode.
Why it happen?:
Apple should answer this question.
It seems a garbage in memory issue after a period of time using XCode.
Workarounds if you get trap there
#HotLicks is right about the advising of use NSLog and po to be sure about the state of that object.
Also you can invoke methods and read properties of the object in question by using expression command in debugger window after a breakpoint.
zevarito is on the right track. A bit more seems to solve the long-irritating problem:
Close the project.
Xcode -> Window -> Projects
For the project in question (and all others is probably a good housecleaning idea), click Derived Data -> Delete.
Close Xcode.
Close Simulator.
Restart Xcode and resume what you were doing.
I know this topic is covered in a hundred posts here, but I'm having a lot of trouble with this particular instance and cannot figure it out.
Basically, I'm using Spritebuilder to import sprites/nodes into my game. I import a sprite of some specific class in the body of the GameScene class, but I want to be able to define a variable inside of my sprite class, and then edit it from the GameScene class. For example, if my sprite collects a coin inside the GameScene, I want to change the speed of my sprite inside of the update method in the sprite class.
Below is my code, but unfortunately it isn't working. The variable increaseY and increaseX do not appear to be available in my GameScene class. I know this is because I didn't instantiate the Penguin class properly, but I don't know how to properly create an instance of this class while simultaneously importing the .ccbi file of it. The problem line is commented on and has a bunch of ** next to it to easily find it. It's in GameScene.m. I really appreciate the help, been stuck on this for several hours.
Penguin.h
#import "CCSprite.h"
#interface Penguin : CCSprite
{
float xPosition;
float yPosition;
}
#property (nonatomic,assign) float increaseY;
#property (nonatomic,assign) float increaseX;
#end
Penguin.m
#import "Penguin.h"
#implementation Penguin
#synthesize increaseX;
#synthesize increaseY;
- (id)init {
self = [super init];
if (self) {
CCLOG(#"Penguin created");
}
return self;
}
-(void) update:(CCTime)delta
{
self.position = ccp(self.position.x + increaseX,self.position.y + increaseY);
}
#end
GameScene.h
#import "CCNode.h"
#interface GameScene : CCNode
#end
GameScene.m
#import "GameScene.h"
#import "Penguin.h"
#implementation GameScene
{
CCPhysicsNode *_physicsNode;
CCNode *_catapultArm;
CCNode *_levelNode;
CCNode *_contentNode;
}
// is called when CCB file has completed loading
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
CCScene *level = [CCBReader loadAsScene:#"Levels/Level1"];
[_levelNode addChild:level];
}
// called on every touch in this scene
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
[self launchPenguin];
}
- (void)launchPenguin {
// loads the Penguin.ccb we have set up in Spritebuilder
CCNode* penguin = [CCBReader load:#"Penguin"];
penguin.position = ccpAdd(_catapultArm.position, ccp(16, 50));
[_physicsNode addChild:penguin];
//THE FOLLOWING LINE DOES NOT WORK********************************
penguin.increaseY = 1;
// Gives Error------Property "increaseX" not found on object of type "CCNode *"
self.position = ccp(0, 0);
CCActionFollow *follow = [CCActionFollow actionWithTarget:penguin worldBoundary:self.boundingBox];
[_contentNode runAction:follow];
}
You have to change this line:
CCNode* penguin = [CCBReader load:#"Penguin"];
To this line:
Penguin* penguin = (Penguin*)[CCBReader load:#"Penguin"];
In the old line you were using the compiler was giving you an error because the CCNode class does not have a property called increaseX. increaseX is part of the Penguin class. If you want to access properties of the Penguin class you need to use a cast and let the compiler know, that what you are loading using the CCBReader is actually a Penguin instance.
I have an abstract interface in Objective-C where every sub-class needs to set up a property and then do the exact same thing with that property at the end of init. I'm trying to avoid duplicated code with something like this:
Interface File
#interface Shape : NSObject
#property (nonatomic) PropertyType *prop;
- (id)init;
- (void)initProperty;
#end
Implementation File
#implementation Shape
- (id)init
{
if(self = [super init]) {
[self initProperty];
[prop doSomething];
}
return self;
}
- (void)initProperty
{
}
#end
My problem is that every sub-class will need a different set of parameters passed to initProperty in order to implement the method correctly:
#implementation Rectangle
- (void)initPropertyWithRect:(CGRect)rect
{
prop = [RectangleStuff rectangleWithRect:rect];
}
#end
#implementation Circle
- (void)initPropertyWithRadius:(CGFloat)radius
{
prop = [CircleStuff circleWithRadius:radius];
}
#end
Is there a clean way to do what I'm trying to do in Objective-C? So far, my options seem to be:
Create a "property bag", and just pass around an NSDictionary.
Duplicate the [property doSomething]; code in every subclass.
Somehow pass in a factory object to init, and have the factory object create prop. This approach seems the cleanest, but I'd need the factory object to keep the rect and/or radius as internal state somehow, and that doesn't seem clean to me.
Any thoughts?
I would probably choose #2 (to keep it simple). If the property is only set once
(in the subclass init method), you could override the property setter method in the
superclass, and do the additional stuff there.
Untested code:
- (void)setProp:(PropertyType *)prop
{
_prop = prop; // (Assuming ARC)
[_prop doSomething];
}
First, I feel obligated to mention that your init function should not do anything besides initialize the object. That said, every rule has a time and a place to be broken, so I'll offer what suggestions I can.
Your init function is no different than any other function. You can do things before and after you call super. While generally discouraged, this would be a good place to do it. Your init in your subclass would now look like this:
- (id)init
{
self.myProperty = value;
self = [super init];
if (self) {
// more init stuff
}
return self;
}
I ended up using a variant of what was suggested in the other two answers:
Shape.h
#interface Shape : NSObject
#property (nonatomic) PropertyType *prop;
- (id)initWithProperty:(PropertyType *prop);
#end
Shape.m
#implementation Shape
- (id)initWithProperty:(PropertyType *)prop
{
if(self = [super init]) {
_prop = prop;
[_prop doSomething];
}
return self;
}
#end
Rectangle.m/Circle.m
#implementation Rectangle
- (void)initWithRect:(CGRect)rect
{
return [self initWithProperty:[RectangleStuff rectangleWithRect:rect]];
}
#end
#implementation Circle
- (void)initWithRadius:(CGFloat)radius
{
return [self initWithProperty:[CircleStuff circleWithRadius:radius]];
}
#end