transitionFromView and indexes - objective-c

I'm working with a controller (MAIN) that manage how presents 2 other controllers (A and B) views in its main view.
In MAIN controller View i have a BUTTON created into the MAIN view xib.
BUTTON must be over A and B view.
So this's the view hierarchy structure i need :
MAIN.VIEW
|------A or B view (index 0)
|------BUTTON (index 1)
Here how i create A and B into MAIN:
//MAIN CONTROLLER viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.A = [[A alloc] init];
self.B = [[B alloc] init];
//Start from A view visible in MAIN
[self.view insertSubview:self.A.view atIndex:0];
}
After a specific action call i want to insert controller B view and remove A.
I read about transitionFromView and i tried to use it this way:
[UIView transitionFromView:self.A.view
toView:self.B.view
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished) {
if (finished) {
//nothing for now...
}
}];
The problem here is that after flip animation B view is over BUTTON and doesn't take exactly the place of A in my hierarchy (A was at index 0)
I can add this code into completion Block :
[self.view sendSubviewToBack:self.B.view];
But i think it's a little tricky solution :P (and it's not a good solution too, because it seems appear effectively at the end of the animation with a bad graphic effect...)
Which is the best way to be sure that A and B are placed at index 0 ?

May be a bug in the SDK. There is a Bug report at OpenRadar with exactly this behaviour.

Related

UInavigationController Custom transition is not getting called

I'm trying to implement MAOFlipViewController in one of my application. Everything is working properly, but as per my requirement I want to move back to 0 viewcontroller whenever user come to that particular view.
For moving back to 0 view I added following code
NSUInteger targetIndex = self.flipNavigationController.viewControllers.count;
for (int i=(int)targetIndex; i>=0; i--) {
[self.flipNavigationController popViewControllerAnimated:YES];
}
It's working fine. But it is not showing the animation.
How can I use the animation that is defined with UIPanGestureRecognizer to my defauly pop navigation.
Any help will be appreciated. Thanks in advance.
I'm updating my answer, so it can be useful to someone else too.
I used another custom library for flipping the view Flip View. So to root view controller will work like :
-(void)popToRoot{
NSArray *viewControllers=[[self flipNavigationController] viewControllers];
NSUInteger cnt = viewControllers.count;
if(cnt >= 2){
UIViewController *lastVC = [viewControllers objectAtIndex:cnt-1];
UIViewController *secondVC = [viewControllers objectAtIndex:cnt-2];
[lastVC.view flipToView:secondVC.view duration:0.2 removeView:YES direction:JDFlipImageViewFlipDirectionDown completion:^(BOOL finished) {
[self.flipNavigationController popViewControllerAnimated:NO];
[self popToFirst];
}];
}
}

Stacking multiple UIViewControllers and presenting the last one in UINavigationController stack

Possibly simple request here but I can't find the solution and it is bugging me for days.
I'm building simple options page where users could jump to desired page and I'm using UINavigationController instance to manage hierarchy. My storyboard looks like this:
Viewcontrollers are connected with push segues fired on next button, while I use [self.navigationController popViewControllerAnimated:YES] for previous button. If I connect, for instance, button labeled 2 on 5VC with 2VC through push segue, I get to the second page, but if I want to use previous button I will land to options page or 5VC which is something I don't want. Instead, I would like to be able to use previous button to go to first page, while on second page.
The way I see it, if I am on third page (3VC) and I call options page (5VC) and select button 3, system should stack 1VC-2VC and present 3VC, so I would be able to go to 2VC through [self.navigationController popViewControllerAnimated:YES] request.
I think the solution is somehow connected with setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated, but I don't know the syntax how to make things work.
You have 3 cases
Back to one of ancestors in the middle with push
case 5VC=>2VC, 5VC=>3VC:
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 2; i > 0; i--) {
// find the target and its parent view controller
// i.e. class of 2VC is ViewController2
if([vcs[i] isKindOfClass:[ViewController2 class]]) {
UIViewController *target = vcs[i];
UIViewController *parent = vcs[i - 1];
// pop to its parent view controller with NO animation
[self.navigationController popToViewController:parent animated:NO];
// push the target from its parent
[self.navigationController pushViewController:target animated:YES];
return;
}
}
Back to the root view controller with push
case 5VC=>1VC:
UIViewController *root = self.navigationController.viewControllers.firstObject;
// reset view controllers stack with self as root.
[self.navigationController setViewControllers:#[self] animated:NO];
// push target from self
[self.navigationController pushViewController:root animated:YES];
// reset navigation stack with target as root.
[self.navigationController setViewControllers:#[root] animated:NO];
Push new VC from one of ancestors
case 5VC=>4VC
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 1; i >= 0; i--) {
// find the parent view controller
if([vcs[i] isKindOfClass:[ViewController3 class]]) {
UIViewController *parent = vcs[i];
// pop to the parent with NO animation
[self.navigationController popToViewController:parent animated:NO];
// perform segue from the parent
[parent performSegueWithIdentifier:#"push4VC" sender:self];
return;
}
}
On the particular your case(5VC=>4VC), you know 3VC is the self's parent, you can get the parent directly:
NSArray *vcs = self.navigationController.viewControllers;
UIViewController *parent = vcs[vcs.count - 2]; // [vcs.count-1] is self.
[self.navigationController popToViewController:parent animated:NO];
[parent performSegueWithIdentifier:#"push4VC" sender:self];

pushviewcontroller after uiwebview finishes its load

I am properly pushing viewController B from A using navigationController. However, I would like to do it once uiwebview from viewController B finishes its load and not immediately. I tried firstly init B and push A when load ends but with no success, controller is not viewed. How can it be done? thank you.
from controllerA,
self.controllerB = [[BViewController alloc] initWithNibName:#"BViewController" bundle:nil anUser:self.idUser aLang:self.lang];
//[[self navigationController] pushViewController:controllerB animated:NO]; working if pushed directly here
[self.controllerB view];
then, controllerB is initialized, viewDidLoad triggered and when webviewDidFinishLoad, B must be pushed now or viewed at front.
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
{
AViewController *theInstance = [[AViewController alloc] init];
[theInstance pushBcontroller]; }
on AViewController,
-(void)pushBcontroller{
[[self navigationController] pushViewController:self.controllerB animated:NO];
}
not working...
The line AViewController *theInstance = [[AViewController alloc] init]; creates a new instance of a AViewController. Since it's new it isn't part of the view controller hierarchy and is therefore not connected to the navigation controller.
Give your BViewController a reference to the previous controller and use that instead of creating a different one. Or, perhaps better, send a notification when loading is done that the original AViewController uses to know when to change the display.

UIPageViewControllerSpineLocation Delegate Method Not Firing

Major head-scratcher all day on this one :-(
I have an instance of a UIPageViewController that does not appear to be firing the delegate method:
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.
Code Explanation (simplified for example)
Consider three classes as follows:
RootViewController - loaded when the app starts
PageViewController - loaded by RootViewController upon user initiation
PageContentViewController - loaded by PageViewController when pages are needed
Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:
PageViewController *pvc = [[PageViewController alloc] initWithNibName:#"PageView"
bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];
In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.
PageViewController
This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...
#interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
#property (nonatomic, strong) UIPageViewController *pageViewController;
#property (nonatomic, strong) NSMutableArray *modelArray;
#end
#implementation TXCategoryController
-(void)viewDidLoad
{
[super viewDidLoad];
// Simple model for demo
self.modelArray = [NSMutableArray alloc] init];
for (int i=1; i<=20; i++)
[self.modelArray addObject:[NSString stringWithFormat:#"Page: %d", i]];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
startupVC.pageLabel = [self.modelArray objectAtIndex:0];
[self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
self.pageViewController.view.frame = self.view.bounds;
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Setting a break point in here - never gets called
if (UIInterfaceOrientationIsPortrait(orientation))
{
// Relevant code to create view...
return UIPageViewControllerSpineLocationMin;
}
// Relevant code to create 2 views for side-by-side display and
// set those views using self.pageViewController setViewControllers:
return UIPageViewControllerSpineLocationMid
}
#end
This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.
This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).
If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.
As usual, any and all help would be very much appreciated.
Side Note
I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)
Finally found the problem. It was something of a red herring in its symptoms, but related just the same.
Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html
The most relevant point in there was:
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.
The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:
[self presentViewController:pac animated:YES completion:NULL];
Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.
That was easily tweaked by editing viewDidLoad as follows:
PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];
if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
forKey:UIPageViewControllerOptionSpineLocationKey];
PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
[pagesArray addObject:page1];
[pagesArray addObject:page2];
}
else
{
[pagesArray addObject:page1];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:pageViewOptions];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
Job done and problem solved.

UINavigationBar topItem/items seems to double-pop on back

I am managing my own UINavigationBar. I need to do this due to extensive skinning. The documentation for UINavigationController warns that there are limitations to skinning the UINavigationBar when used with a UINavigationController.
I have put in extensive logging and from everything I can tell, pushing the "Back" button in the UINavigationController pops two items off of of the stack instead of one. I get a single delegate callback telling me that it is removing the logical item, but it actually removes that one and one more.
The item added to the UINavigationController in awakeFromNib should never be removed. It is being removed for some reason.
There are two similar questions, but neither have satisfactory answers. The two questions are:
UINavigationBar .items accessor doesn't return the current UINavigationItem
UINavigationBar seems to pop 2 items off stack on "back"
- (void)awakeFromNib {
[headerView setDelegate: self];
[headerView pushNavigationItem: tableDisplay animated: NO];
}
- (void) selectedStory: (NSNotification *)not {
[headerView pushNavigationItem: base animated: NO];
NSLog(#"Selected story: %#", base);
}
- (void) baseNav {
NSLog(#"Current items: %#", [headerView items]);
BaseInnerItem *current = (BaseInnerItem *)[headerView topItem];
[self addSubview: [current view]];
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPushItem: (UINavigationItem *)item {
return YES;
}
- (BOOL)navigationBar: (UINavigationBar *)navigationBar shouldPopItem: (UINavigationItem *)item {
return YES;
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item {
NSLog(#"didPushItem: %#", item);
[self baseNav];
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item {
NSLog(#"didPopItem: %#", item);
[self baseNav];
}
Edited to add relevant debugging from a single run:
2010-10-13 02:12:45.911 Remix2[17037:207] didPushItem: <TableDisplay: 0x5d41cc0>
2010-10-13 02:12:45.912 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>"
)
2010-10-13 02:12:49.020 Remix2[17037:207] didPushItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:49.021 Remix2[17037:207] Current items: (
"<TableDisplay: 0x5d41cc0>",
"<WebDisplay: 0x591a590>"
)
2010-10-13 02:12:49.023 Remix2[17037:207] Selected story: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.498 Remix2[17037:207] didPopItem: <WebDisplay: 0x591a590>
2010-10-13 02:12:59.499 Remix2[17037:207] Current items: (
)
You always have to call [super awakeFromNib] when your subclass implements that method, per the documentation for -awakeFromNib:
You must call the super implementation of awakeFromNib to give parent classes the opportunity to perform any additional initialization they require
Importantly, however, ...
I don't understand why you have to actually manage your own navigation bar. If you subclass UINavigationBar and only override certain drawing or layout methods such as -drawRect:, -layoutSubviews, etc., then all of the logic behind managing the navigation bar in a navigation controller will just fall back on the original UINaviationBar class.
I've had to do extensive view customization for almost every major UIKit class, but I always left the complicated logic to the original classes, overriding only drawing methods to customize the look and feel.
Incidentally, it's actually much easier to skin an entire app without subclassing at all if all you're doing is using custom image assets. By setting a layer's contents property, you can either customize the look and feel of a UIView-based class on an as-needed basis or throughout your entire app:
#import <QuartzCore/QuartzCore.h>
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage * navigationBarContents = [UIImage imageNamed:#"navigation-bar"];
self.navigationController.navigationBar.layer.contents =
(id)navigationBarContents.CGImage;
}
You can set the contents for any class that inherits from UIView: navigation bars, toolbars, buttons, etc. It's a lot easier to manage this way without having to subclass at all.
This appears to be a bug in the implementation of -[UINavigationBar items]
When called from inside the -navigationBar:didPopItem: delegate method, it will omit the last object. You can check this by calling [navigationBar valueForKey:#"_itemStack"] to retrieve the underlying array and see that the expected items are still there.
Adding a dispatch_async inside -navigationBar:didPopItem:method successfully works around the issue in my app.