Assignment of an ivar not working - objective-c

I'm seeing a bizarre case where simple assignment to an instance variable is not working. The right hand object is non-nil, but refuses to be assigned to an instance variable (with the same type). This is not happening consistently within the app.
I have a class like this:
#interface FooViewController : UIViewController {
UIView *contentView;
BOOL testBool;
}
#end
#implementation FooViewController
- (void)viewDidLoad {
UIView *theContentView = [[UIView alloc] initWithFrame:self.view.bounds];
// this assignment fails for some reason:
contentView = [theContentView retain];
/* at this point, contentView == nil */
UIView *bar = theContentView; // this works
testBool = YES; // this works
}
#end
and a subclass like this:
#interface BarViewController : FooViewController {
}
#end
When I instantiate the BarViewController from a nib, when the execution gets to the viewDidLoad defined in the superclass, the strange behavior above happens. Instance variables "refuse to assign".
However, if I add a dummy instance variable to the subclass, the expected behavior returns. Instance variables correctly assign in the viewDidLoad.
I'm seeing this at runtime in iOS 4.3 in the simulator using Xcode 4.0.2. This happens both in LLVM+GCC and GCC.
This is not happening in isolation. When I try this in a test project, it performs normally.

I'm sorry, but I don't believe you.
My first question is how you determine that contentView is equal to nil? Do you have a breakpoint after the assignment? If you break on the line of the assignment, that line will not yet have been executed, so contentView will be nil at that point.
If you're testing contentView in some other method it might just be that viewDidLoad has not been run yet.
Show me the output of:
- (void)viewDidLoad {
UIView *theContentView = [[UIView alloc] initWithFrame:self.view.bounds];
// this assignment fails for some reason:
contentView = [theContentView retain];
/* at this point, contentView == nil */
NSLog(#"theContentView: %p", theContentView);
NSLog(#"contentView: %p", contentView);
UIView *bar = theContentView; // this works
testBool = YES; // this works
}
If they differ I'll start believing you. (Well, maybe not, I'll still consider the possibility that you're lying.)

Related

NSViewController New vs. InitWithNibName issues

I am having a weird error with NSViewController where if I allocate a view using the viewcontroller's regular init message, the view created is not my view, but when using the default NIB name, it does work.
Specifically, this code works all the time. It creates the view defined in the nib file, and displays it in the parentView.
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [[TransparentAccessoryViewController alloc] initWithNibName:#"TransparentAccessoryViewController" bundle:nil];
NSLog(#"%#", [controller.view class]); // Returns "TransparentAccessoryView" -- CORRECT
[parentView addSubview:controller.view];
}
However, the following code works SOME of the time (which is weird in that it doesn't always fail). With some parentViews, it works perfectly fine, and with others, it doesn't. The parent views are just random custom NSViews.
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [TransparentAccessoryViewController new];
NSLog(#"%#", [controller.view class]); // Returns "NSSplitView" -- INCORRECT
[parentView addSubview:controller.view];
}
The errors that comes up are as follows (I have no idea why it is bringing up an NSTableView, as I don't have an NSTableView here at all. Also, it is weird that it complains about an NSTableView when the type it prints is an NSSplitView):
2013-04-07 21:33:12.384 Could not connect the action refresh: to
target of class TransparentAccessoryViewController
2013-04-07 21:33:12.384 Could not connect the action remove: to target
of class TransparentAccessoryViewController
2013-04-07 21:33:12.385 * Illegal NSTableView data source
(). Must implement
numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
The NIB file defines a custom subclassed NSView, called TransparentAccessoryView, and hooks this up to the File Owner's view property, standard stuff (all I did was change the custom class name to TransparentAccessoryView). I added an NSLog's to see what was going on, and for some reason, in the second case, the view class type is incorrect and thinks it is an NSSplitView for some reason. The ViewController class is as follows:
#implementation TransparentAccessoryViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
- (void)awakeFromNib {
self.textField.stringValue = #"";
}
+ (void)createTransparentViewCenteredInView:(NSView*)parentView withText:(NSString*)text duration:(NSTimeInterval)duration {
TransparentAccessoryViewController* controller = [[TransparentAccessoryViewController alloc] initWithNibName:#"TransparentAccessoryViewController" bundle:nil];
NSLog(#"%#", [controller.view class]);
[parentView addSubview:controller.view];
}
#end
I thought that the default init message triggers the viewcontroller to load the NIB named after the viewcontroller, which seems to be the case some of the time as the second version of my code works in certain conditions.
Does anyone know why this behavior is occurring at all?
From the docs:
If you pass in a nil for nibNameOrNil then nibName will return nil and
loadView will throw an exception; in this case you must invoke
setView: before view is invoked, or override loadView.
Therefore, if you're initializing a NSViewController with -init, you should call -setView: to set the view controller's view, or override -loadView. In the latter case, you could certainly implement the UIViewController-like behavior that you're probably expecting -- if nibNameOrNil is nil, try to load a nib that has the same name as the class.
I think that when you call init on a NSViewController, you're assuming that the implementation of init for NSViewController searches for a nib with the same name as the view controller and uses it. However, this is undocumented API or at least I can't seem to find any documentation supporting that assumption. The link you posted on your comments doesn't cite any documentation either and even reiterates that this is undocumented and that Apple could change this implementation at any point.
I think to assure that your code works in future versions of the SDK (and since it is already creating undesired behavior), you should not rely on this assumption. To achieve the same outcome simply override init and initWithNibName:bundle: in such a way as explained by this post:
#implementation MyCustomViewController
// This is now the designated initializer
- (id)init
{
NSString *nibName = #"MyCustomViewController";
NSBundle *bundle = nil;
self = [super initWithNibName:nibName bundle:bundle];
if (self) {
...
}
return self;
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
// Disregard parameters - nib name is an implementation detail
return [self init];
}

Does a view reset all its variables every time it opens?

I have a int that resets itself every time the view re-opens/leaves. I have tried every way of declaring the int that i can think of, from public, to instance variable to global variable, but it still seems to reset!
#interface MainGameDisplay : UIViewController
extern int theDay;
#implementation MainGameDisplay
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"%i", theDay);
}
- (IBAction)returnToHome:(id)sender {
ViewController *new = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
- (IBAction)theDayAdder:(id)sender {
theDay++;
}
Okay so theDay is a global integer variable. on View load NSLog returns an output of 0. I can then click theDayAdder as many times as I want, and when I click returnToHome, it will tell me what theDay is. When I come back to MainGameDisplay page however, theDay will be reset back to zero, even though it is a global variable?
Output:
0
N (number of times you clicked 'theDayAdder' button)
0
The problem is that you alloc init'ing a new instance of MainGameDisplay every time you go back to it, so of course your global variable will be reset to 0. You need to create a property (typed strong) in ViewController, use that to go back to the same instance each time.
- (IBAction)returnToGameDisplay:(id)sender {
if (! self.mgd) {
self.mgd = [[MainGameDisplay alloc] initWithNibName:nil bundle:nil];
}
[self presentViewController: self.mgd animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
In this example mgd is the property name created in the .h file.
You should know that viewDidLoad() is called when the view is loaded--not when when the view "opens" as you say. You might have a view opened in a retained value and re-opened time and time again and have vieDidLoad() called only once. However, whenever the view becomes visible, then viewWillAppear() is the delegate that is called. So, try outputting your value in viewWillAppear()--instead of viewDidLoad() and call the view appropriately (i.e., have it stick around and not created every time you need it). This will keep the view from being destroyed between calls. The code for your view should look like the following:
#interface MainGameDisplay : UIViewController
extern int theDay;
#implementation MainGameDisplay
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
NSLog(#"%i", theDay);
}
- (IBAction)returnToHome:(id)sender {
ViewController *new = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
- (IBAction)theDayAdder:(id)sender {
theDay++;
}
The parent of the view (I assume the appDelegate) should do the following
#property (nonatomic, strong) MainGameDisplay *mainGameDisplay = [[MainGameDisplay alloc] initWithNib:#"MainGameDisplay" …]
ViewDidLoad() is called once--after the view is created and loaded. However, viewWillAppear() and other functions triggered by IBAction etc. are called appropriately.
extern variables are meant to be constant. If you expect your MainGameDisplay class to be long-lived, or if theDay is otherwise only supposed to be tied to that class, why not either declare theDay as a property, or, if you only ever need to set it internally in MainGameDisplay, as an ivar.
The other alternative, if you want that value to continue to exist independently of the class instance where it's declared, is to declare it static. A static var will retain its value, even across the lifetime of different instances of the class where it's declared.

Displaying an UIImageView property from another class programmatically

Well, here's the situation:
I've got...
a custom class with an UIImageView-property, let's call it the Enemy-class
a ViewController
a NSMutableArray to help create multiple instances of Enemy (called enemies)
and what I want:
be able to create an unlimited amount of Enemy-Instances through a method in my ViewController (like [self spawnEnemy];, where self is the ViewController)
and, subsequently, display the UIImageView property (let's call it "enemyImage") on the view that is controlled by my ViewController
I've tried something like this:
-(Enemy *) spawnEnemy
{
Enemy *tempEnemy = [Enemy new];
[enemies addObject:(Enemy*)tempEnemy];
[self.view addSubview:(UIImageView*)[[enemies objectAtIndex:[enemies count]] enemyImage]];
//randomLocation is declared in the Enemy-Class and just assigns a random
//CGPoint to self.enemyImage.center
[[enemies objectAtIndex:[enemies count]] randomLocation];
return [[enemies objectAtIndex:[enemies count]]createEnemy];
}
This runs without errors, randomLocation gets called (tried with NSLog), AND if I do something like this in another Method of ViewController:
[[self spawnEnemy] enemyTestMethod];
enemyTestMethod is being executed as well.
But still, no enemieViews are displayed on the screen...
What am I doing wrong?
Thank you so much for your help and time.
==== Edit ====
Here's the relevant code from Enemy.h/Enemy.m:
#interface Enemy : NSObject
{
UIImageView *enemyImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *enemyImage;
-(Enemy*) createEnemy;
//Enemy.m
#implementation Enemy
#synthesize enemyImage, speed;
-(Enemy *) createEnemy
{
self.enemyImage = [[UIImageView alloc] init];
[self.enemyImage setImage:[UIImage imageNamed:#"enemy.png"]];
return self;
}
I also corrected the last line in the spawnEnemy-Method to properly send createEnemy.
You don't include the code in Enemy where you alloc/init the UIImageView property. Unless this code explicitly specifies a CGRect with the size and origin that you want, the view will be initialized with CGRectZero, which means even if you're correctly adding the subview (and it looks like you are) you still won't see it anywhere.
Post the Enemy code, and the problem will probably be immediately apparent.
Have you called -createEnemy before you added them to your view?
OK, you've got this checked.
Then maybe you should check as #MusiGenesis suggested.
To do this, you need to inspect the properties of your enemyImage.
You can do this in either of the following ways:
print its frame.size by:
NSLog(#"enemyImage frame size: %#", NSStringFromCGRect(enemy.enemyImage));
set a breakpoint where you feel great, and check with your debugger:
p (CGRect)[[enemy enemyImage] frame]

Protocol Memory

I'm sorry for repost. What have really bug me is if property retain should release when:
case 1 : (code below) button is already alloc in init: then add to subview then release it, and by any chance I set the button property in another class (I didn't release in dealloc:) it will leak then?
case 2 : button is already alloc in init: then add to subview then release it, and by any chance I didn't set the any button property in another class (I didn't use the property) (I release it in dealloc) then it will crash.
So what should I do if I want to alloc button in init: and I want to set the property too ?
#interface SomeClass : UIView {
UIButton *_button;
}
#property (nonatomic, retain)UIButton *button;
#implementation SomeClass
#synthesize button = _button;
- (id)init {
_button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
[[_button titleLabel] setFont:BUTTON_FONT];
[_button setBackgroundImage:[[UIImage imageNamed:#"button_blue.png"] stretchableImageWithLeftCapWidth:20.0f topCapHeight:15.0f] forState:UIControlStateNormal];
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button setTitleShadowColor:[UIColor blackColor] forState:UIControlStateNormal];
[[_button titleLabel] setShadowOffset:CGSizeMake(0.0f, 1.0f)];
[_button addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubView:_button];
[_button release];
}
- (void)dealloc {
//[self.button release]; // case 1
[self.button release]; // case 2
[super dealloc];
}
#end
So what should I do if I want to alloc button in init: and I want to set the property too ?
Here's how your code should look:
#implementation SomeClass
#synthesize button = _button;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
UIButton *button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
// Setup the button here...
[self addSubView:button];
self.button = button;
[button release];
}
return self;
}
- (void)dealloc {
[_button release];
[super dealloc];
}
#end
Changes I made:
initWithFrame: is the designated initializer of the UIView, so that's the init method you've got to override
Always check to make sure that your superclass initialized successfully before you setup your class
You've got to return self at the end of your init statements. I don't think your code would have compiled as written.
You created a property, you should use it. Use a temp variable to do all the setup for your button, then when you're finished with setup, use the property accesssor to set the variable and release your temp variable.
Because you only use the property to get/set your button, when it's time to dealloc you can guarantee that the _button iVar will either be valid or nil. Either way calling release on that variable is OK.
I don’t understand what you mean by “using it for read-only”. As declared, the property is read-write both for the class itself and from the outside. But the question can be reasonably answered nevertheless – once your class retains some object, it must release it when it gets deallocated.
P.S. I think you can safely drop the underscore prefix for private variables, it serves no real purpose and makes you write more code. In fact, with modern runtimes you can even drop the instance variable declaration:
#interface Foo : NSObject {}
#property(assign) BOOL bar;
#end
#implementation Foo
#synthesize bar;
#end
OK, third attempt: The problem is that you are in fact setting the property by assigning to the instance variable. Properties and instance variables are closely tied, when you give the following declaration…
#synthesize button = _button;
…you are saying that the _button instance variable should be used to store the value of the button property. Which means that your code over-releases, since you alloc the button in init (+1), release the button in init (-1) and then release again in dealloc (-1 again).
If you have not yet studied the Cocoa Memory Management Guide, do it. Even if you don’t plan to read any other documentation (which would be a pity), make an exception with this one, it will pay you back plenty.
sure, You should release it, because you have used retain for it.
In your -init method, you have a balanced retain/release call. So you don't need to release it again. But by doing it, you are sacrificing the reliability of the value held by _button. If somewhere down the lane the button is removed from the subviews, the button's retainCount could hit zero and it can be deallocated. Then the value held by _button is garbage. So you should not release _button in -init and rather you should do that in -dealloc.
Now, if you access the property elsewhere (outside this UIView object), you don't need to release it again unless you retain it there.
Try to replace [self.button release]; with [self.button removeFromSuperview];

Reference CustomView from my Controller

This is driving me crazy. Very grateful if someone could help me out!
Problem:
I have subclassed NSView (and implemented initWithRect: and drawRect:) and connected it to a customView in IB. Then in my Controller.h I am trying to create a reference to this instance by using Viewer *view; (Viewer is my subclass of NSView). However, when I try to reach a dummy function that only performs printf("something") nothing happens. Since I haven't allocated any memory for this instance [view retainCount] gives 0. My understanding was that IB would instantiate this class for me. The reason that I want to be able to reference the instance is so that I can call [view setNeedsDisplay: YES] so that the view will be redrawn. I have connected my CustomView with the view outlet in IB and saved.
#import "Viewer.h"
#import "Controller.h"
#implementation Viewer
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
return self;
}
-(void)awakeFromNib
{
printf("awake!\n"); //works!
}
- (void)drawRect:(NSRect)rect
{
CGContextRef myContext = [[NSGraphicsContext currentContext]graphicsPort];
for (int i=0; i<8; i++) {
for (int j=0; j<8; j++) {
printf("%f\n",gPopulation[i][j]/2);
CGContextSetRGBFillColor (myContext, gPopulation[i][j]/2, 0.3, 0.1, 1); // Set color
CGContextFillRect (myContext, CGRectMake (i*50, j*50, 50, 50 ));
}
}
}
**- (void) redraw { //dummy function that I can't reach from controller with [view redraw]. Gives no error, but retainCount = 0**
printf("redraw------\n");
//[self display];
}
#end
#import <Cocoa/Cocoa.h>
#import "Viewer.h"
double gPopulation[8][8];
#interface Controller : NSObject {
NSMutableArray *emptySpots;
int nEmpty, nWhite, nBlack;
NSOperationQueue *queue;
IBOutlet Viewer *view;
}
- (void) main;
- (id) initWithMain;
- (void) updatePopulation;
- (void) initPopulation;
#end
The steps you describe aren't entirely clear, but here are several things that stand out:
1 - It's not your place to ask an object for its -retainCount to determine whether it's being used or not. You have no way of knowing (nor are you supposed to know or depend upon knowing) what else might have an interest in this object.
2 - You check for a valid object by seeing if the object pointer ("view" in your case) is valid (points to an object) or is nil.
3 - When creating a custom NSView subclass and instantiating a copy within your nib/xib, you need to drag an NSView instance out from the library, then set its class name to that of your subclass, otherwise Interface Builder is just creating an instance of NSView. I don't think this is your problem (see #4) but you didn't say this so it's another thing to check.
4 - When you send a message to nil, nothing is exactly what is supposed to happen, so it's likely your "view" pointer/outlet is nil.
5 - It's easy to confuse "an instance I created and referenced in a nib/xib" with "an instance I created at runtime". This happens frequently with those new to Cocoa. Are you absolutely positive that the instance of the object that holds the connection (named "view") is the same as the instance you're examining at runtime? For example, you create a controller class named MyController, instantiated it in your nib/xib (as a blue cube), wired it up, etc. Then at runtime, you instantiate a new MyController ([[MyController alloc] init]...) and tried to access its (nil) "view" outlet, which points to nothing because it's not the same instance as that in your nib/xib.