I have a UIView subclass that I manipulate a lot of graphics on based on touches. All the [self setNeedsDisplay] calls seem to be fine.
However, I used an instance variable pointer to my same UIView subclass instance, and then tried manipulating it and then calling [UIViewSubClass setNeedsDisplay] from another UIView class, and DrawRect is never being called. Are there restrictions to where you can call setNeedsDisplay from?
(This method is called when a button is clicked on another UIView subclass. The method is being called, but not DrawRect)
-(IBAction)loadGrid2;
{
tempSoundArray = musicGridView1.soundArray;
[musicGridView1.soundArray setButtonArrayToNull];
[musicGridView1 setNeedsDisplay];
musicGridView1.soundArray = tempSoundArray;
NSLog(#"loadGrid2 was called");
}
drawRect: will only be called when it makes sense; ie, the view must be visible, onscreen, and dirty. Is your drawRect: ever called? It should be called when the view is first brought onscreen as well.
To add to Ben:
This most likely means that you have problems elsewhere. Your pointer may not be nil or otherwise invalid or the view may not be added to the hierarchy properly.
You may want to consider not handling this type behavior within the view and instead in the view controller. Control behavior and save presentation state in the view controller and don't subclass the view classes. It will simplify your code with less "pointer passing". This will also make it easier to debug this type of problem.
If you feel your view controller is getting bloated, consider splitting the responsibilities up among multiple view controllers.
Related
Periodically, I add a UIView to the UIDynamicAnimator, which has some behaviours of its own. But when I remove the UIView from it's superview (when it falls offscreen) the UIDynamicAnimator still keeps the UIView's behaviours in its 'behaviors' property.
My question is, exactly what is the best approach to handling the behaviours in a UIDynamicAnimator?
Do I have to manually keep track of all the behaviours pertaining to that UIView and manually remove them before removing the UIView from the view hierarchy?
[myBehavior removeItem:item] does not throw an exception if item is not part of myBehavior, so what you could do is have a generic removeView method that removes a view from all behaviors that could possibly pertain to it, something like:
- (void) removeView: (UIView *) view{
[_gravity removeItem: view];
[_collisions removeItem:view];
[_otherBehavior removeItem:view];
//and et cetra for all of your behaviors
[self.view removeItem: view];
}
Which could be called whenever you need to remove a view. Even if, say, the view is not part of _otherBehavior this method would still properly remove the view.
If a new iOS project is created with an Empty App template in Xcode 4.3.2, and in AppDelegate.m:
self.window.rootViewController = [[FooViewController alloc] init];
and in FooViewController's viewDidLoad, the following:
NSLog(#"self.view is %p", self.view);
NSLog(#"self.view is %#", self.view);
will print out the view, so it looks like the default loadView will instantiate a view and assign it to self.view.
So if I override loadView with an all empty method, and comment out the second NSLog statement above, I expect the first NSLog statement to print out 0x0, but instead the app crashed due to bad memory access right at that NSLog line. Why would that be?
Okay, after a knee-jerk and obviously wrong answer, I tried this. The Empty App template would not have a rootViewController, so I used a single screen template. After running, I see that you are getting a stack overflow. In trying to access self.view, you are calling the view property on the superclass, which is then trying to load the view in order to return it, which is calling viewDidLoad, etc., as far as I can see. The other NSLog statement does the same.
The documentation for the view property in UIViewController states:
Because accessing this property can cause the view to be loaded automatically, you can use the isViewLoaded method to determine if the view is currently in memory.
It also has a link to The View Controller Life Cycle, which states:
The steps that occur during the load cycle are as follows:
The load cycle is triggered when the view controller's view property is accessed and the view is not currently in memory.
The view controller calls its loadView method. The default implementation of the loadView method does one of two things:
If the view controller is associated with a storyboard, it loads the views from the storyboard.
If the view controller is not associated with a storyboard, an empty UIView object is created and assigned to the view property.
The view controller calls its viewDidLoad method to allow your subclass to perform any additional load-time tasks.
So when you say:
So if I override loadView with an all empty method
You're deliberately breaking the life cycle, because when your overridden version of loadView finishes, it should have loaded a view. Because it didn't, you get a crash.
I have a view controller managed in a UINavigationController. My view controller puts up a "loading" screen, and uses ASIHTTP to asynchronously load table data. Then it renders its table, and in each cell there's an image that it uses ASIHTTP to asynchronously load. When it lands each image, it uses [self.tableView reloadRowsAtIndexPaths] to reload that row, inside which the image is fed to the UIImageView in each row.
Works great. But if I back out of the view before it's done loading, it crashes.
Where it crashes is in my -tableView:numberOfRowsInSection method, and NSZombies tells me it dies because it's asking for the -count of an NSArray called self.offers that has been deallocated.
That method looks like this:
-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [self.offers count];
}
Wrapping that return in if (self.offers) made no difference.
My -dealloc releases and sets-to-nil every one of these properties, including both self.offers and self.tableView itself. I even tried setting up a BOOL disappearing property, hitting it with YES in -viewWillDisappear, and hanging conditional behavior off that, but it doesn't work because viewWillDisappear doesn't seem to get called! Far as I can tell we're not getting ANY method called when the navigation bar pops us off.
What do I do about this?
EDIT:
Thanks to #cduhn (who's bucking for a check!), I did a bunch more looking at this. The problem has been, my -dealloc just isn't getting called when I pop this viewcontroller (nor my -viewWillDisappear nor -viewDidUnload or anything else I could use to unhook the delegation structure that's at the root of this problem).
Then I realized: THIS viewController isn't the one on the NavController stack! What's at the top of the stack right here is a shell view, just a segmented controller and a big empty UIView. I toggle the contents of that UIView between two other UIViewController subclasses depending on the state of my segmented controller. So when my view with the table on it's PARENT view gets popped from the nav stack, this CHILD I'm working on doesn't seem to get any notice about it. Which is odd, because I'm definitely releaseing it.
I can call its -dealloc from my shell controller. I could call its -viewWillDisappear too, for that matter. Is that how I should be handling this? Probably I should put something into my shell controller's viewWillDisappear like:
[[self.mainView subviews] makeObjectsPerformSelector:#selector(viewWillDisappear)];
...so that message propagates down to my child views.
Am I on the right track here, you think??
(Oh man... and that also explains why actions from inside this child table view can't get to self.navigationController! I've been puzzled about that for weeks!)
Bugs like this, where a method gets called on an object after it's been deallocated, often happen when a method gets called on a delegate after that delegate has been deallocated. The recommended practice to avoid bugs like these is to set any delegate (or delegate-like) properties of an object to nil before you release that object in the delegate's dealloc method. I know that's a confusing sentence, so I'll explain it in the context of your bug.
You have an asynchronous image download that finishes after you've backed out of your table view controller. When this happens, you're calling reloadRowsAtIndexPaths:withRowAnimation:, which results in a call to tableView:numberOfRowsInSection: on the table view's dataSource. This call is failing because that dataSource no longer exists.
The problem is that the table view object still has your controller set as its dataSource and delegate properties, even after your controller has been deallocated. The solution is to simply set these properties to nil in your controller's dealloc, like this:
- (void)dealloc {
self.tableView.dataSource = nil;
self.tableView.delegate = nil;
self.tableView = nil; // Releases as a side-effect due to #property (retain)
[super dealloc];
}
Now when your table view tries to call tableView:numberOfRowsInSection: on its dataSource, it will send the message to the nil object, which swallows all messages silently in Objective C.
You should also do the same thing with your ASIHTTPRequest's delegate, by the way.
Any time you have an asynchronous operation that calls delegate methods upon completion, it's particularly important that you set that delegate to nil. When using UITableViewController under normal circumstances you can typically get away without setting the dataSource and delegate to nil, but in this case it was necessary because you're calling methods on the tableView after its controller has gone away.
As far as I can tell, the user cannot actually "back out" of a view while the UITableView is loading it's data. The methods are not run on a thread and block the main one, also blocking UI interaction. I cannot replicate your results. Even, scrolling the table view quickly and then pressing the back button.
I suggest that the stack popping is not the problem here.
I have seen many posts about this problem but didn't get an answer. I have a controller which view is added to the main window. The controller's view has a subview which has a drawRect. The problem is that this function is never called even if I call [self setNeedsDisplay].
Thanks
It is -(void)drawRect:(CGRect)rect right? Make sure the method signature is correct, and you don't omit the rect argument even if you don't use it.
-setNeedsDisplay should be called the the subview, not self.
Also, -setNeedsDisplay won't call -drawRect: immediately. It only flushes the graphics cache so that -drawRect: is forced to be called in the next update of the frame.
I have a UIViewController that is initialised with a correct frame, however somewhere in my code the frame gets mangled and I'm having difficulty finding out where.
In situations like this it is usually handy to watch a variable in the debugger, however I have no way of accessing the controller->view->frame property in my variable view, since it isn't a variable, it's a property (surprisingly enough)
Drilling into the UIView in the variables display shows a few things but nothing I can relate to the frame, I thought perhaps that would be in layer but it isn't.
Is there any way to watch for changes in a private API? I guess not, since the variables are essentially 'hidden' and so you can't specify exactly what to watch.
Alternatively, what other approach could I use? I already tried subclassing UIView, setting my UIViewController's view to point to this subclass and breaking on the setFrame method but it didn't seem to work.
EDIT: the subclassing UIView method DID work, I just had to set the view to point to my test subclass in viewDidLoad and not the init method. Leaving this question open as I'm not sure if this is the best way of approaching this kind of problem...
Subclass your the view you want to track and rewrite the setFrame method:
#implementation MyTableView
- (void)setFrame:(CGRect)frame;
{
NSLog(#"%#", frame);
[super setFrame:frame];
}
#end
Then use the debugger to add a breakpoint to it and check when it gets called. Eventually, you'll see when the frame gets changed and where does the change comes from.
I discovered this can be done using key value observers.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html
You could create an ivar, view2, and just assigned it to your view in your loadView method. That should enable you to watch it like a normal variable.