How to update or reset a UIView with a custom subview? - iphone

I have a UIView in a UIViewController to which I add a custom subview in the viewDidAppear method.
MyView *myView = [[MyView alloc] initWithLabelText:text];
[self.view addSubview:myView];
[myView release];
The text variable is a string used on a label in myView. This text changes every time you come back to the current view. But it seems that viewDidAppear does not reload the view - it rather loads a new view over the old one - so I have two labels over each other.
I tried to use viewWillAppear but it doesn't make any difference. I also tried to use [self.view setNeedsDisplay] - doesn't help. I also tried to make myView an instance variable, but it also didn't help.
What worked was to remove the view explicitly, when I declared it as an instance variable:
- (void)viewDidDisappear:(BOOL)animated
{
[_myView removeFromSuperview];
}
Although there is this workaround I would like to simply reset the view when getting back to it. Does anybody know how to do that? I would appreciate it ;)

Don't alloc and init the custom subview every time, only the first time viewDidAppear is called. Then retain it in a property for subsequent use.

The followings thing can ben considered.
viewDidLoad --> alloc and init your sub views
viewDidAppear --> update sub views
dealloc --> release sub views.

Related

How to refresh UIViewController programmatically?

I have a ViewController in which the user selects a card (a custom UIButton) out of a UIScrollView. I have intercepted the touch event selecting the card and identified it, and then removed it from the data source, but it still exists in the UISubView. How do I get rid of it? Refreshing the view should show it removed from the view. How do I do that?
you can do it in one of two places:
in your viewcontroller
directly in the view
you need to call the function setNeedsDisplay
if you do it from the viewController then [yourViewOutletVariable/viewParameter setNeedsDisplay];
if you write it in the view itself then [self setNeedsDisplay];
hope this helps
You can either let view controller observe your models or update your views manually.
I'm not very clear about your question, what is still remaining on your view?
For automatically update views when model changes, I suggest ReactiveCocoa.
Once you have a handle on your view:
UIView *v = ...;
[v removeFromSuperview];
You could also call the setNeedsDisplay method on your scroll view after calling removeFromSuperview.
If your point is to refresh "UIViewController", then:
[self viewDidLoad];

Release UIViewController if available

Hey,
I'm adding a UIViewcontroller as a subview of my current view. With something like that:
[self.view addSubview:viewcontroller.view];
The user can interact with that added viewcontroller so I can't just release it after I added it's view as my subview, right ?
So I would like to release it the dealloc methode and set it to nil in viewDidUnload when my master viewcontroller gets unloaded, right ?
The problem is, the viewcontoller I add as a subview is not added every time. So the question is, how can I ceck if the viewcontroller was added as subview and if so, release it.
Thx a lot !
Sebastian
You can check it like this:
if(viewController)
{
[viewController release];
viewController=nil;
}
and yes u need to put this in your dealloc method.
If you read correctly your code:
[self.view addSubview:viewcontroller.view];
it is not the controller which is being added as a subview, rather the view it manages.
This is an important point, since it means that the controller itself does not get retained. What is retained is the view.
So you can simply go on releasing your viewController in -dealloc as usual:
-(void)dealloc {
[viewController release]; viewController = nil;
...
}
and in your viewController's dealloc you will need to release the managed view, if you allocated it manually (or wherever it makes sense for your controller to release its view, if necessary).
On the other hand, whenever your superview is deallocated, then also the view you added as a subview will be released (as per Apple spec of addSubview behavior). So nothing to worry about here. Just release the viewController properly (and make that the view controller manages correctly tis own view).
One more note: you should not release your view controller in viewDidUnload.
In some open source code that I used to contribute to, we had a macro called RELEASE_TO_NIL which did exactly this.
#define RELEASE_TO_NIL(obj) if(obj != nil) { [obj release]; obj = nil; }
You would use it like this:
RELEASE_TO_NIL(viewController);
Simple as that.
Just add an tag to viewController.view and test if self.view contains that tag.
UIView *testView = [self.view viewWithTag:yourTag];
if(testView!=nil){
doStuff;
}
According to the Apple docs, a UIViewController instance should manage a view that fills the whole screen. If your view does not fill the screen, you could either subclass UIView to handle delegation or make the superview work as a delegate.
Also, the subviews of a view are contained in an instance of NSArray, do [myView subviews] to return this property. Ask the returned array if it contains the subview in question and you can release accordingly.
But, without knowing more, it does sound like you need to rethink how you're setting all this up.

UIViewController: viewWillAppear is called, viewDidAppear not

In a UIViewController subclass, I have the following methods:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something
myTextField.text = #"Default";
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// do something
[myTextField selectAll:self];
[myTextField becomeFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
// do something
myTextField.delegate = self;
}
The NIB has been created using Interface Builder. The corresponding view controller object is pushed by the navigation controller through pushViewController.
The inteded behavior is to show a default text entry in a text field, to select the entire text and to set the text field as first responder. [Edit: I've noticed that selecting all and making first responder makes no sense as the selection would dissapear; still, I wonder why the methods behave as described next.]
However, while methods viewDidLoad and viewWillAppear are called, the method viewDidAppear is not called. Can anybody tell me why? Most questions I found on the web and here deal with both viewWillAppear/viewDidAppear are not working; I also understood that in subviews or programmatically created views these methods are not evoked; but this does not apply in case and also I wonder why one of these "lifecycle" methods is evoked and the other not.
Any idea? Thanks!
I had this issue happen to me: viewWillAppear was being called but viewDidAppear was not!
I finally figured out that this was because I had a tabBarController where I overloaded it's own viewDidAppear and forgot the [super viewDidAppear:animated];
It threw off every VC in every tab! adding that line back in fixed it for my other VC's.
Hope this helps someone!
There is one case when viewWillAppear will be called but viewDidAppear will not.
Suppose you have two viewControllers and you push from the first to the second controller. Then, using the swipe, you want to go back to the first one, both controllers are displayed at the same time and at that moment viewWillAppear will be called from the first controller.
This way, you do not finish the swipe and stay on the second controller and viewDidAppear will not be called from the first controller.
I had the same problem.
I had copy/pasted viewDidAppear to create viewWillAppear but had forgotten to change the super.viewDidAppear() call. This then seemed to stop viewDidAppear from being called.
It sounds like somewhere in your code you have missed or messed-up a call to the superclass.
The call to viewDidAppear: should always follow viewWillAppear: unless you are doing something custom, which you say you don't. I don't know why it doesn't work but here are a few ideas:
Could it be that you are doing something strange in one of the delegate methods for UITextFieldDelegate? It's unlikely that it would affect viewDidAppear: being called but it could be a culprit.
Have you loaded a lot of stuff into memory before pushing the view? I'm not sure what would happen if you got a memory warning between viewWillAppear: and viewDidAppear:.
Have you tried to do a Clean? Sometimes that can help.
In cases like these when it should work I usually create a new class and the introduce the functionality one at a the time to see if I can get it work that way. I tried your code in a new Navigation Based project where I added a new UIViewController with an outlet to the text field. Then I pasted the code from the question and it did work as expected.
This can be because you added a child view controller to your parent VC in viewDidLoad or viewWillAppear. The child's appearance prevents the call to viewDidAppear.
This is a crazy thing to do, and I only know because this was a bug in my code. I meant to add the child VC to this VC, not the parent VC.

What is the difference between loadView and viewDidLoad?

I know there is a seemingly exact duplicate of this question here: iPhone SDK: what is the difference between loadView and viewDidLoad?
However, I have read that question and still it was not fully answered.
I'm not using IB as the UI is dynamic.
So should I create the self.view and then add the subviews in loadView,
or should I create the self.view in loadView and add the subviews in viewDidLoad?
When you load your view from a NIB and want to perform further customization after launch, use viewDidLoad.
If you want to create your view programatically (not using Interface Builder), use loadView.
For your specific question, you should add the subview in viewDidLoad. Because, if you overwrite the loadView, you have to do all the jobs, loading all the views.
Here is the explanation from Apple's documentation:
The steps that occur during the load cycle are as follows:
1.
* Some part of your application asks for the view in the view
controller’s view property.
2.
* If the view is not currently in memory, the view controller calls its loadView
method.
3.
* The loadView method does one of the following:
If you override this method, your implementation is
responsible for creating all
necessary views and assigning a
non-nil value to the view property.
If you do not override this method, the default implementation uses
the nibName and nibBundle properties of the view controller to try to load the view
from the specified nib file. If the
specified nib file is not found, it
looks for a nib file whose name
matches the name of the view
controller class and loads that file.
If no nib file is available, the method creates an empty UIView object
and assigns it to the view property.
4.
* The view controller calls its viewDidLoad method to perform any
additional load-time tasks.
It is very simple actually. If you do it without IB, then your UIViewController's view property is empty. So set it at loadView!
I only do setting of view at loadView and nothing else.
Other than that, do all thing inside viewDidLoad. Here is some example:
- (void)loadView {
CGRect frame = [[UIScreen mainScreen] applicationFrame];
baseView = [[UIView alloc] initWithFrame:frame];
[self setView:baseView];
[baseView release];
}
That's it! I am done. And would never want to add more to it. Then at the viewDidLoad, I add all those subviews I want to.
- (void)viewDidLoad {
[super viewDidLoad];
msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 200, 320, 50)];
[msg setText:#"Your profile is empty!"];
[[self view] addSubview:msg]; // hey, I have done my view at loadView, so I have it now
[msg release];
}
I could be wrong in my understanding :)
loadView is the method that actually sets up your view (sets up all the outlets, including self.view).
viewDidLoad you can figure out by its name. It's a delegate method called after the view has been loaded (all the outlets have been set) that just notifies the controller that it can now start using the outlets.
viewDidLoad:
"This method is called after the view controller has loaded its associated views into memory. This method is called regardless of whether the views were stored in a nib file or created programmatically in the loadView method."
loadView:
"If you create your views manually, you must override this method and use it to create your views."
Add subviews in viewDidLoad. That way you are 100% sure than the view did indeed load and is ready for consumption.
Use viewDidLoad for initialize views and constrols. And use loadView if you don't have Nib/Xib and would like your ViewController has custom (not UIView) view.
Only use loadView when you want to create a view yourself.
Don't use loadView after you use interface builder or init with nib since these actions have already called loadView in the underly implementation.
Also, when use loadView, assign view first before doing any other settings:
-(void)loadView {
[super loadView];
// if you do any things here before assigning a view
// it will try to get a view first by calling loadView()
// and ends up with a crash since a dead loop.
self.view = ...;//assign your view here
//do other settings
}

Problem with iPhone shakes: viewDidAppear isn't called when loading viewController

I'm trying to detect a shake on the iPhone device, so for what i've read I need to set my view controller as first responder on the viewDidAppear method. This method is supposed to be called automatically, but for some reason this method is never called. Only viewDidLoad is called.
this is my code (I put only the relevant parts):
BuildHouseViewController.h:
#interface BuildHouseViewController : UIViewController {
BuildHouseViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view becomeFirstResponder];}
-(void)viewDidAppear:(BOOL)animated{
[self becomeFirstResponder];
[super viewDidAppear:animated];}
-(BOOL)canBecomeFirstResponder {
return YES;}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
NSLog(#"shake");
if ( event.subtype == UIEventSubtypeMotionShake )
{ }
if ( [super respondsToSelector:#selector(motionEnded:withEvent:)] )
[super motionEnded:motion withEvent:event];
}
I added breakpoints on the viewDidAppear method and it is never called. Shakes are never detected, I suppose it is because as this methods are never called, so the view controller can never become first responder. I don't understand why this is happening.
Any help will be appreciated.
Edit:
I call the view from another view using:
[self.view addSubview:nextScreen.view];
The view is displayed on screen
Thanks for the quick answers.
I've found something interesting. I tried loading the same view I'm having problems with in different ways and I'm getting different results
-As I said before if I call it from another view using:
[self.view addSubview:nextScreen.view];
viewDidLoad is never called and I cannot detect shakes.
-Now if I call it from the AppDelegate using:
[window addSubview:nextScreen.view];
viewDidLoad is called!! and I am able to detect shakes, however this solution is not possible, I should be able to call it from another view
-If I call it from another view using:
[self presentModalViewController:nextScreen animated:YES];
viewDidLoad is called!! However I don't want to use a modal view controller, but it appears to be the only solution to my problem, shakes are detected.
It is strange that the first method doesn't load the view correctly, is it a bug??
The [self becomeFirstResponder] and the like don't actually make that become the first responder. The method gets called when the view is going to become the first responder. So that's not doing what you think it is.
Secondly, the viewDidAppear will only be called when the view, well, did appear. Is it showing up on the screen? Where are you telling it to be displayed? You need to either add the view controller's view as a subview of another view, or pushed onto a navigation controller stack, or pushed as a modal view.
viewDidAppear:(BOOL)animated only gets called when the view is shown by UINavigationController or UITabBarController. If you add a view controller's view to a subview (such as a scrollview or what have you), it won't get called. You would think it would but you'd be wrong. Just got bit by this myself.
Further to #Genericrich's comments, you can manually call viewDidAppear after you put the subview in yourself.
[self.view addSubview:theViewController.view];
[theViewController viewDidAppear:FALSE];
This worked for me. Hope it helps someone else.