how does one make a subview firstResponder - iphone

i am trying to get a subview to become firstResponder. my understanding is that this is done in its viewDidAppear method, like so:
- (void)viewDidAppear {
[self becomeFirstResponder];
}
while overriding canBecomeFirstResponder to return YES:
- (BOOL)canBecomeFirstResponder {
return YES;
}
however, when i insert the subview in its parent view's viewDidLoad method:
- (void)viewDidLoad {
subViewController = [[SubViewController alloc] init];
[self.view insertSubview: subViewController.view atIndex: 0];
[subViewController viewDidAppear: NO];
[super viewDidLoad];
}
(i call viewDidAppear manually, because it does not get triggered automatically), the subview does not become firstResponder.
why does the subview not become firstResponder? and how can i make it firstResponder?
thanks,
mbotta
btw, this is a rewrite of my original question:
i am trying to build an iphone app where a rootviewcontroller object manages two subviews, one of which should react to a user shaking his iphone.
after some digging, i concluded the subview must be made firstResponder in its view controller's viewDidAppear method. moreover, the canBecomeFirstResponder method should be modified to return YES.
so here's what happens: i instantiate the rootviewcontroller in the app delegate. in its viewDidLoad method, i tell it to addSubView firstViewController. all of this works just beautifully.
however, firstViewController does not react to any shaking. i sprinkled some NSLogs around and found that while we DO tell firstViewController in canBecomeFirstResponder to return YES, and while we DO tell it to [self becomeFirstResponder] in viewDidAppear, in actual fact, it is not the firstResponder.
so, my questions are:
1) does a subview actually need to be firstResponder in order to react to shaking?
a) if not, how does one make a subview react without being firstResponder?
2) how does one make a subview firstResponder?
what makes this interesting is that if i perform the same sequence (canBecomeFirstResponder, [self firstResponder], motionBegan:) in a different project with only one view controller, it all works flawlessly. clearly, i must be hitting a design flaw of my own making.
thanks,
mbotta

Not 100% sure, but this could be your problem. If we could see the offending methods it might be easier.
From the Event Handling Best Practices (emphasis added by me):
If you handle events in a subclass of
UIView, UIViewController, or (in rare
cases) UIResponder,
You should implement all of the event-handling methods (even if it is
a null implementation).
Do not call the superclass implementation of the methods.
If you call the superclass methods the events are probably getting passed along to the nextResponder.
EDIT
The Event Handling Best Practices link above is dead. I couldn't find that pull quote anywhere, but Event Handling Guide for UIKit Apps seems to be the most relevant.

Related

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.

Coding custom SplitViewController - when should I call viewWillAppear, viewDidAppear, etc...?

I'm writing my own SplitViewController from scratch (i.e. by subclassing UIViewController and not UISplitViewController).
It has two sub-viewControllers (one for the left panel and one for the detail right panel), to which I need to send the appropriate messages (viewWillAppear, viewDidAppear, viewWillDisapppear and viewDidDisappear).
I am already forwarding those messages when my custom SplitViewController receives them and it works fine. However I am struggling to figure out when to send them when any of the two sub-viewcontrollers is replaced by a new one, which also needs to receive those messages. I am adding the view of the new UIViewController properly but the messages are not called adequately.
My initial approach was to call them in the setter of the sub-viewControllers, calling viewWillDisappear to UIViewController about to be released and viewWillAppear to the new UIViewController set, but this one is executed before viewDidLoad and therefore I presume is wrong.
I have also seen that UIView has a method didAddSubview: that might be useful to know when to call viewDidAppear on the correspondent UIViewController.
Any help would be much appreciated!
If you want to mirror UISplitViewController, it seems best to just have dummy UIViewControllers that print out whenever each method is called.
As for your current problem of the ordering of viewWillDisappear, viewWillAppear and viewDidLoad, just do:
-(void)setSomeViewController(UIViewController newVC)
{
[oldVC viewWillDisappear];
[newVC view]; // Causes newVC to load the view,
// and will automatically call -viewDidLoad
[newVC viewWillAppear];
[oldVC.view removeFromSuperview];
[self.view addSubview:newVC.view];
//retain and release as appropriate
// other stuff you'll need to mirror, etc. etc.
}

viewWillAppear, viewDidAppear not being called, not firing

(This is both question and answer since it took quite a bit of digging to find the real answer.)
Symptom: viewWillAppear, viewDidAppear were not being called in my UIViewController.
Cause: Embedding a UINavigationController or UITabBarController (my case) in a UIViewController somehow interrupts with the calling of these methods.
Solution: Call them manually in the UIViewController that contains the aforementioned UINavigationController / UITabBarController.
For example (assuming projectNavigationController is your UINavigationController):
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[projectNavigationController viewWillAppear:animated];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[projectNavigationController viewWillDisappear:animated];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[projectNavigationController viewDidAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[projectNavigationController viewDidDisappear:animated];
}
In my case I had an inner UITabBarController and I called the methods accordingly and all was solved.
(Attribution on the solution: http://davidebenini.it/2009/01/03/viewwillappear-not-being-called-inside-a-uinavigationcontroller/)
I'm going to go ahead and disagree with #St3fan, and use UIKit as the counter-example.
However, the wisdom (or lack thereof), of embedding controllers in general should be guided by sane UI design principles.
The easiest counter-example is UINavigationControllers embedded in UITabBarControllers. These appear all over the place. Just off the top of my head, the iPod app on iPhone, and Contacts within the Phone app on iPhone.
I was curious enough to bother to check what they do with the views (add to the "super-controller" view or to the UIWindow. I was pretty sure I was going to find that the sub-controller views were descendants of the super-controller views in the view hierarchy, which is contrary to St3fan's recommendation.
I whipped up a very quick iPhone app hooking everything up in InterfaceBuilder to create a UITabBarController based app with two tabs, the first of which was a UINavigationController with a plain ole UIViewController as it's root view controller, and a 2nd tab with a plain old UIViewController just so I had a 2nd tab to click later.
Sprinkle in some NSLog statements to output the various UIView's for the controllers we see this:
tabBarController.view = <UILayoutContainerView: 0x5b0dc80; ...
navigationController.view = <UILayoutContainerView: 0x59469a0; ...
rootViewController.view = <UIView: 0x594bb70; ...
Superview: <UIViewControllerWrapperView: 0x594cc90; ...
Superview: <UINavigationTransitionView: 0x594a420; ...
Superview: <UILayoutContainerView: 0x59469a0; ... // navigationController.view
Superview: <UIViewControllerWrapperView: 0x594b430; ...
Superview: <UITransitionView: 0x5b0e110; ...
Superview: <UILayoutContainerView: 0x5b0dc80; ... // tabBarController.view
Superview: <UIWindow: 0x5942a30; ...
The lines prefixed with "Superview" were the output from walking up the rootViewController.view's superview chain until hitting nil.
Then of course a quick glance at the call stack in a couple of places where viewDidDisappear would get called on the root view controller.
First, the call stack when viewDidDisappear is called on the root controller as a result of a new controller being pushed on to the stack:
-[RootController viewDidDisappear:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
...
Second, the call stack when another tab is selected in the top-most UITabBarController:
-[RootController viewDidDisappear:]
-[UINavigationController viewDidDisappear:]
-[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:]
So in all cases, it seems that Apple decided that controllers should be calling the various viewDidAppear, etc methods on their embedded subcontrollers and that the view's should be embedded similarly. I think the OP hit this nail right on the head if we're to take UIKit design as a good lead to follow.
I just saw this same circumstance. Earlier the interface builder segue triggered by a table cell selection had stopped working and after some exasperation digging into the code, I just set it up manually, calling from the cell-selection override in the table view delegate.
Later I made some layout changes in the called view controller and saw that viewDidAppear wasn't being called, as described above. The debug output made reference to "nested push operations" or something, and since I had a big comment to myself in my manual push operation
#warning I SHOULD NOT HAVE TO DO THIS!!
I breakpointed the segue code and, sure enough, the IB segue was now working, and it was my manual operation in the table cell selection code that was messing up the delegate calls in the called view. I removed the manual code and everything is fine again.
It does seem odd that the cell select code gets called after the view is pushed. I have to do a protocol and delegate to get the indexpath of the selected cell in the caller.

viewWillAppear not called in UITableViewController?

I have a couple of UITableViewController classes and I just noticed that these methods aren't being called:
-(void)viewWillAppear:(BOOL)animated;
-(void)viewDidAppear:(BOOL)animated;
I read in http://discussions.apple.com/thread.jspa?threadID=1529769&tstart=0 that I would have to call those methods myself when pushing view controllers, but that's strange, since it works for anything but UITableViewController.
Also makes it a bit of an issue when I need to have a UITableViewCell deselected in the UIViewController that pushed the UITableViewController.
I can't find it in the documentation, but I think this might be because you are using a UINavigationController.
How about setting the UINavigationController's delegate property and then implementing UINavigationControllerDelegate? It provides two optional methods:
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
For example, navigationController:willShowViewController:animated: might look something like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:[UITableViewController class]]) {
[viewController viewWillAppear:animated];
}
}
Anyway, this will get you the behavior you want without having to hack calls to viewWillAppear: all over your project.
Did anyone resolve this because the original post is correct - simply using UITableViewController and pushing the table view of that controller onto the navController does NOT trigger these methods despite the fact that it should. I have a series of UITableViewControllers and table views that are pushed and popped to display hierarchical data - nothing fancy, but the "viewWill/Did/Appear/Disappear" methods are never called. Only the viewDidLoad and viewDidUnload are called.
There must be a wiring problem in both our setups, but simply pushing a view into the navigationController should be all that is required (?) - hard to believe that this could have gone unnoticed as a fundamental bug this long.
?
These two methods are called by default to notify for the changes. UITableViewController is a subclass of UIViewController, so there will be the same behavior. You can see more in the View Controller Programming Guide
The viewWillAppear: and viewDidAppear: methods give subclasses a chance to perform any additional actions related to the appearance of the view.
How do you know that these methods are not called? Can you provide some more codes, or at least you test them with a NSLog() to see if there are some messages printed.
I'm seeing the same problem. I have a simple UIView from the IB and I do a addSubview with a class that extends UITableViewController.
I can see the view of the TableViewController without problems in my application, but the viewWillAppear function is never called in this situation.
Well, the discussion linked from the question has the answer right in it. UINavigationController needs to receive the "viewWillAppear" message in order for it to send those messages to the view controllers you push onto it.
So ironically if you don't do what Apple recommends, and you subclass UINavController for your view controller, then everything works great.
However, if you just create a UINavController inside of your view controller, then you need to implement "viewWillAppear", "viewDidAppear" and so on and forward those to your nav controller.
Note that this is especially important if you're using Three20, because its view controller hierarchy expects the "viewWillAppear" message to be received. If its not you can end up with TTTableViews that don't draw.
The same can occur if you use a UITabViewController. You need to force the viewWillAppear call by implementing either the UITabViewControllerDelegate or UINavigationControllerDelegate callbacks
This explanation may help: http://www.mlsite.net/blog/?p=210

Objective-C/iPhone: Windows' view not responding to button clicks!

So in my app delegate I add a call add the myViewController.view to the main window:
1. [window addSubview:myViewController.view];
In myViewController I do the following code in the viewDidAppear method:
2. [self presentModalViewController: yourViewController animated: YES];
In my yourViewController class I do the following to try and go back to the main window
3. [self dismissModalViewControllerAnimated:YES];
My main windows view appears with buttons in all, but the buttons won't react to any click or anything. It's like there is something over them that I can't see.
Also, the main windows button works before this process but doesn't after the process.
Any help would be appreciated.
If the dismiss method call is in the modal view controller (not the parent that presents it), then you actually want to call [self.parentController dismissModalViewControllerAnimated:YES];
There are a number of reasons why things might not be responding to your touches. Here are two that have happened to me:
The frame of the view you want to touch is too small. UIViews can draw outside of their frames, so it might look ok, but not respond if the touch is technically outside of the frame -- you also have to check that all the superview's up the hierarchy also have a large enough frame.
If anything in your view is a UIImageView or child thereof, it won't respond to user touches because UIImageView has userInteractionEnabled set to NO by default. You can fix this just by setting myImageView.userInteractionEnabled = YES;
Edit: Oli pointed out in the comments that dismissModalViewControllerAnimated: should work if called on either self.parentController or simply self, since that method is smart enough to call the parent if needed, according to the docs. The docs also make it sound like this might behave differently if you have multiple model views open at once, though, so I would still consider it cleaner code to call the method on self.parentController directly.