tvOS preferredfocusedview is not always called - objective-c

After a viewcontroller has been presented modally, the initial preferredfocusedview is called. However, after we dismiss the viewcontroller and it has been dealloc. preferredfocusedview is not called after presenting the viewcontroller again. Running on tvOS 9.2.
Even adding the following did not help:
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
Anyone know what's going on? Or if there's anyways to debug this?
Edit:
the way I am adding the viewcontroller:
viewController = [[UIViewController alloc] init];
[viewController addChildViewController:self];
[viewController.view addSubview:self.view];
[self didMoveToParentViewController:viewController];

If you are using a container view, having multiple ViewControllers or adding only one View Controller, the preferredFocusEnvironments method must be called from the rootView Controller indicating which View Controller to focus.
For eg.
View Controller A has a container View having ViewControllers B and ViewController C inside the Container.
View Controller A should have preferredFocusEnvironments returning which ViewController to focus.
This way, preferredFocusEnvironments on ViewController B or ViewController C will be called whenever the view becomes visible.
If the ViewController A doesn't have preferredFocusEnvironments, then it won't be called on the containerView ViewControllers.

Implementing custom focus behavior in tvOS 9 is disaster. Apple already mentioned that there is a limitation on redirecting focus specially when presenting/ dismissing a viewcontroller in WWDC.
tvOS10 will handle munch better with preferredFocusEnvironments.
https://developer.apple.com/videos/play/wwdc2016/215/
When I needed to fix this focus redirection issues in viewDidAppear in tvOS 9, I had exactly same issues. Sometimes it works, sometimes not. No clue what so ever. But after I put split second delay on setNeedsFocusUpdate / updateFocusIfNeeded in viewDidAppear it was way better in terms of consistency. preferredFocusedView get called all the time.
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
});
}
Do this in both presented and presenting view controllers, if you are manually changing focus. This is all from my observation and I don't think there is a proper way to achieve some focus behavior because tvOS API is kind of new and premature. Sorry about not being able to give you good explanation why this might work. Good luck.

Related

return to ViewController after presentViewController

Im trying to return to a specific ViewController in it's current state after going from that ViewController to another using presentViewController.
But when I try to close the other ViewController (with dismissViewController) I get a white screen.
RootViewController *rootViewController
= [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil]
instantiateViewControllerWithIdentifier:#"RootViewController"];
[self presentViewController:rootViewController animated:YES completion:nil];
This isn't an option either because that instantiates a new viewcontroller and I want the old ViewController in its current state.
Do I need to pass the RootViewController as an argument when presenting the other ViewController or is there another option to return to the RootViewController in its current state?
Yes, there is a way to return original screen.
I met just like problem but solved it with following code line
[self dismissViewControllerAnimated:YES completion:nil];
one way to address this is to avoid having one view controller responsible for presenting and dismissing the other one.. what you can do is create a controller of controllers (give it a singelton method).. and have that object basically keep a reference to any view controller you are interested in maintaining its state. That way you wouldn't have worry about what's going on behind the scenes when you dismiss or present a view controller.

Unable to create unwind segues when using custom view controller containment

I'm attempting to convert our application to storyboards and have hit what I believe is a bug in the handling of unwind segues when dealing with custom container controllers. We have a view controller which displays another and uses the view controller containment api to do this, I wire up the segue in IB then select a custom class for the implementation. The perform method looks something like this:
-(void) perform {
UIViewController *container = [self sourceViewController];
UIViewController *child = [self destinationViewController];
[container addChildViewController:child];
[container.view addSubview:child.view];
child.view.center = container.view.center;
[UIView transitionWithView:container.view
duration:0.35
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
child.view.alpha = 1;
} completion:^(BOOL finished) {
[child didMoveToParentViewController:container];
}];
}
That works perfectly, however I can't make it perform the unwind segue back to the container controller. I override viewControllerForUnwindSegueAction: fromViewController: withSender: and ensure that it's returning the correct value:
-(UIViewController *) viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
id default = [super viewControllerForUnwindSegueAction:action fromViewController:fromViewController withSender:sender];
NSAssert1(default == self, #"Expected the default view controller to be self but was %#", default);
return default;
}
I can also confirm that canPerformUnwindSegueAction:fromViewController:withSender is being called and doing the right thing, but to be sure I overrode it to return YES
-(BOOL) canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
return YES;
}
The next step I would expect to happen is for segueForUnwindingToViewController:fromViewController:identifier: to be called, however it never is. Instead the application crashes with an NSInternalInconsistencyException.
2012-10-01 10:56:33.627 UnwindSegues[12770:c07] *** Assertion failure in -[UIStoryboardUnwindSegueTemplate _perform:], /SourceCache/UIKit_Sim/UIKit-2372/UIStoryboardUnwindSegueTemplate.m:78
2012-10-01 10:56:33.628 UnwindSegues[12770:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not find a view controller to execute unwinding for <USCustomContainerViewController: 0x75949a0>'
*** First throw call stack:
(0x1c8e012 0x10cbe7e 0x1c8de78 0xb61f35 0x581711 0x45ab54 0x10df705 0x16920 0x168b8 0xd7671 0xd7bcf 0xd6d38 0x4633f 0x46552 0x243aa 0x15cf8 0x1be9df9 0x1be9ad0 0x1c03bf5 0x1c03962 0x1c34bb6 0x1c33f44 0x1c33e1b 0x1be87e3 0x1be8668 0x1365c 0x1e7d 0x1da5)
libc++abi.dylib: terminate called throwing an exception
Has anyone successfully used unwind segues combined with the view controller containment APIs? Any idea what step I'm missing? I've uploaded a demo project to github which shows the issue in the simplest demonstration project I could come up with.
The problem in your example is that there's no there there. It's too simple. First, you create your container view controller in a rather odd way (you don't use the new IB "container view" which is there to help you do this). Second, you've got nothing to unwind: nothing was pushed or presented on top of anything.
I have a working example showing that canPerformUnwindSegueAction really is consulted up the parent chain, and that viewControllerForUnwindSegueAction and segueForUnwindingToViewController are called and effective, if present in the right place. See:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch19p640presentedViewControllerStoryboard2
I have now also created a fork of your original example on github, correcting it so that it illustrates these features:
https://github.com/mattneub/UnwindSegues
It isn't really a situation where "unwind" is needed, but it does show how "unwind" can be used when a custom container view controller is involved.
This seems to be a bug – I would also expect unwind segues to work as you implemented.
The workaround that I used is explicitly dismissing the presented view controller in the IBAction method:
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController
identifier:(NSString *)identifier
{
return [[UIStoryboardSegue alloc] initWithIdentifier:identifier
source:fromViewController
destination:toViewController];
}
- (IBAction)unwind:(UIStoryboardSegue*)segue
{
UIViewController *vc = segue.sourceViewController;
[vc willMoveToParentViewController:nil];
if ([vc respondsToSelector:#selector(beginAppearanceTransition:animated:)]) {
[vc beginAppearanceTransition:NO animated:YES]; // iOS 6
}
UIView *modal = vc.view;
UIView *target = [[segue destinationViewController] view];
[UIView animateWithDuration:duration animations:^{
modal.frame = CGRectMake(0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
} completion:^(BOOL finished) {
[modal removeFromSuperview];
[vc removeFromParentViewController];
if ([vc respondsToSelector:#selector(endAppearanceTransition)]) {
[vc endAppearanceTransition];
}
}];
}
Brief history before the answer: I just ran into the same exact error message when trying to use multiple Container Views on one iPad screen in iOS 6 and calling unwind segues from code. At first I thought this may be a problem because my segue was created using Storyboards by CTRL-dragging from File Owner to Exit instead of from some UI control to Exit, but I got same results when I put test Close buttons on each VC and had them trigger the unwind segues. I realized that I'm trying to unwind an embed segue, not a modal/push/popup segue, so it makes sense that it fails to do it. After all, if the unwind segue succeeds and the view controller is unloaded from a Container View, iOS 6 thinks there'll just be an empty space on the screen in that spot. (In my case, I have another container view taking up screen real estate that's shown behind the container view which I'm trying to unload, but iOS doesn't know that since the two aren't connected in any way.)
Answer: this led me to realize that you can only unwind modal, push, or popover segues, be it within the main window or as part of a Navigation/Tab Controller. This is b/c iOS then knows that there was a previous VC responsible for the whole screen and it's safe to go back to it. So, in your case, I'd look into a way to tell iOS that your child container view is connected to your parent container view in a way that makes it safe to dismiss the child container view. For example, perform a modal/push/popover segue when displaying the child container view, or wrap both into a custom UINavigationController class (I assume you don't want the navigation bar, that's why custom class).
Sorry I can't give exact code, but this is the best I got to so far and I hope it's helpful.
Looks like this bug is fixed in iOS9.

Is this the right way to add a view?

I make a program that shows a table.
If people click the search I will add another view covering the original view. The original view is [BNUtilitiesQuick listnewcontroller];
[[BNUtilitiesQuick window] addSubview:[BNUtilitiesQuick searchController].view];
[[BNUtilitiesQuick searchController] viewWillAppear:YES] is indeed called. So it seems that UIView has a pointer to it's controller
However, the view that the [[BNUtilitiesQuick listnewcontroller] viewWillDisappear] is not called
Moreover, [[BNUtilitiesQuick listnewcontroller] viewWillAppear] is also not called even when the user has finished modifying search term with this code:
[self.view removeFromSuperview];
I think I may be missing something here. What exactly should I do anyway so IOs knows that the searchController.view will be covering listNewController?
This is NOT the right way to do it. If the searchController is a full screen controller you should present it modally using presentViewController or push it onto the navigation stack as #StuR suggested.
In case your search view covers only part of the listnewcontroller you should use the containment API in iOS5.
Inside listnewcontroller (parent view controller) you would write:
[self addChildViewController:self.searchController];
[self.view addSubview:self.searchController.view];
[self.searchController didMoveToParentViewController:self];
For more in-depth information check out the WWDC 2011 session video "Implementing UIViewController Containment". Also watch "The Evolution of View Controllers on iOS" from 2012 because there are some changes and deprecations in iOS6.
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
I'd consider using pushViewController for adding a full screen view. addSubview is for views that don't cover the entire screen.
viewWillDisappear and viewWillAppear will only me called if you pop or push the given viewController. You are simple adding a subview with it's own viewController inside(on top) of the present viewController. As StuR said, if you want to dismiss the current ViewController you should use:
BNUtilitiesQuick *searchController = [BNUtilitiesQuick alloc] init];
[self.navigationController pushViewController:searchController animated:YES];
You can read more about ViewControllers here: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457

Navigating back to main ViewController dismissViewControllerAnimated dilemma

I have 2 ViewControllers directly connected with a push segue. I am navigating from first to second view controller by calling [self performSegueWithIdentifier:#"segueIdentifier" sender:sender]. On the second one I have an IBAction method that is bound to a "Done" button. Pressing that button should basically cause the first view controller to be displayed (sort of a back button). I managed to do that with:
NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController:[viewControllers
objectAtIndex:0] animated:YES];
I did try to achieve the same effect by using:
[self dismissViewControllerAnimated:YES completion:nil];
No matter what I tried though this didn't do the job. I am trying to understand what exactly am I missing but I can't figure it out. Does dismissViewControllerAnimated method work only with Modal segues ( this is the only thing that came to mind ).
Thank you
Yes,
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
is when a UIViewController is displayed modally.
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
should do what you are seeking.
So basically, in your second VC:
[self.navigationController popViewControllerAnimated:YES];
You will save you a lot of trouble if you read the UIViewController and UINavigationController references. Twice ;)

UIModalPresentationFullScreen not working in iPad landscape mode?

I have added a ViewvController(B) as subview on ViewController(A). In ViewController A(SuperView) UIModelPresentationFullScreen working fine. But when am calling UIModelPresentationFull in ViewController B(SubView) it modelview showing in Portrait mode and that is also not fully viewed. How to solve this problem. Can any one help me please. I have tried 2 days.
This is what I tried in both the superview and subview...
picFBCapture *fbCapt = [[picFBCapture alloc] init];
//[self.navigationController pushViewController:fbCapt animated:YES];
//fbCapt.modalPresentationStyle = UIModalTransitionStyleFlipHorizontal;
fbCapt.modalTransitionStyle = UIModalPresentationFullScreen;
[self presentModalViewController:fbCapt animated:NO];
[fbCapt release];
Thanks in advance..
The problem is that if you add a view controller as a subview it's not connected to the view controller hierarchy and thus certain things doesn't work. You should avoid adding view controllers as subviews whenever possible, since this is not how Apple intend view controllers to be used, but sometimes it can't be avoided.
If this is one of those cases when it can't be avoided you should save a reference to view controller A in view controller B and then call presentModalViewController: on view controller A (that is connected to the view controller hierarchy) instead of self (view controller B, that isn't connected).
EDIT: In controller A you probably have code looking something like:
[self.view addSubview:controllerB.view];
In conjunction to this line add:
controllerB.controllerA = self;
I hope you know how to create properties, but if not here's a hint:
#property (nonatomic, assign) UIViewController *controllerA;
The rest you should be able to figure out using Google and the documentation.
You will have to handle viewController B's view in landscape by yourself. Since viewController B has been added as a subview, its view controller will not be handling its landscape orientation. The UIModalPresentationFullScreen style (landscape and portrait) will work only if viewController B is shown, ie not as subview but as a full view itself.