Can't swipe a `UIPageViewController` with a `UINavigationController` - ios

I've got an app with a UIPageViewController that is set up like this:
UINavigationController *navController = (UINavigationController *)[self.storyboard instantiateViewControllerWithIdentifier:#"VCNavController"];
self.navViewControllers = [NSArray arrayWithObjects:navController, nil];
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainPageViewController"];
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
if ([self.navViewControllers count] != 0)
{
[self.pageViewController setViewControllers:#[self.navViewControllers[0]]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO completion:nil];
}
I've implemented all the necessary delegate functions and my MainViewController that is hosting the UIPageViewController is conforming to UIPageViewControllerDataSource and UIPageViewControllerDelegate.
Hierarchy
MainViewController
UIPageViewController
UINavigationController
UIViewController
The last UIViewController in my hierarchy is displayed correctly, however, I cannot swipe the UIPageViewController. Is there an obvious reason I haven't accounted for? I'm thinking perhaps the UINavigationController doesn't work all that well with the delegate functions supplied by the delegate/datasource, or that the UINavigationController interferes with the touches. Nevertheless, I cannot get it to work.

I don't understand why you would need the UINavigationController, is there a specific reason for this? Try removing the UINavigationController and adding the UIViewControllers directly to the UIPageViewController. Then handle UIGestures in the UIPageViewController. This should achieve the desired effect if I understood you correctly.

Related

iOS obj-c move from UIPageViewController

I have successfully created a "tutorial" pages that will show up at the first time user open the app. And i am facing the problem on how to move to another "branch" of view controllers in my storyboard.
So basically I have two main flows in my storyboard:
1. Page View Controllers
2. Main app view controllers
As you can see from screenshot below
So, the question is, how to move to my main app flow after the user clicked on the skip button from my UIPageViewController?
In my tutorial page view controller, i have this code to set the page view controllers
- (void)viewDidLoad {
[super viewDidLoad];
self.arrPageTitles = #[#"A",#"B",#"C"];
self.arrPageImages =#[#"1.jpg",#"2.jpg",#"3.jpg"];
// Create page view controller
self.PageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"pageViewController"];
self.PageViewController.dataSource = self;
TutorialPageContentViewController *startingViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = #[startingViewController];
[self.PageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
// Change the size of page view controller
self.PageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30);
[self addChildViewController:self.PageViewController];
[self.view addSubview:self.PageViewController.view];
[self.PageViewController didMoveToParentViewController:self];
}
Or is there any better approach to achieve this? Is my design wrong?
I have searching for any clue but can't find any. Thanks!
First of all I'd recommend you to use different storyboards for Tutorial flow and for main app screens. It's very hard to store everything in single storyboard: you will get some mess when project will have at least 20-30 view controllers.
And you can replace root view controller at any time using this code:
let storyboard = UIStoryboard(name: "<storyboard_name>", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "<vc_name>")
UIApplication.shared.keyWindow?.rootViewController = viewController

IOS AdaptiveView Popover and NavigationController: how do they work?

I am updating an old app to the new adaptive size way of doing things and having difficulty getting a popover with a navigation controller to work.
My goal: I want to be able to open a popover from a button when the app is either compact and regular horizontal. The popover has a tableview and uses a navigation controller to push view controllers when the user touches a row on the table. I can get the popover to open correctly, but I can’t figure out who to make the pushes work.
Here’s the code that opens the popover:
OptionsController *vc = [[OptionsController alloc] initWithNibName:#"OptionsView" bundle:nil];
vc.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popover = [vc popoverPresentationController];
popover.delegate = self;
[self presentViewController:vc animated: YES completion: nil];
popover.permittedArrowDirections = UIPopoverArrowDirectionUp; // change as necessary
popover.sourceView = self.view;
CGRect popoverRect = [self.view convertRect:[sender frame] fromView:[sender superview]];
popover.sourceRect = popoverRect;
This code correctly opens a popover in either compact or regular size.
In the OptionsController’s didSelectRowAtIndexPath method, I have this(controllersArray is an array of UIViewControllers, each of which corresponds to a row in the table):
UIViewController *nextController = [self.controllersArray objectAtIndex: [indexPath row]];
[self.navigationController pushViewController:nextController animated:YES];
All this executes, but no push occurs, so the next view never appears.
I clearly am not understanding something about using the UIViewController’s navigationController, or how to install a navigationController to make this work. After three or four days of digging around to try to understand how to make this work, I'd appreciate any insights, or links to documentation about how to do this. Thanks in advance.
Crud - this has a very easy answer. Just took me thinking a different way and digging through Larcerax's comments. Here's how to make this work:
OptionsController *vc = [[OptionsController alloc] initWithStyle:UITableViewStylePlain];
vc.title = #"Options";
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController: vc];
nc.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popover = [nc popoverPresentationController];
popover.delegate = self;
[self presentViewController:nc animated: YES completion: nil];
popover.permittedArrowDirections = UIPopoverArrowDirectionUp; // change as necessary
popover.sourceView = self.view;
CGRect popoverRect = [self.view convertRect:[sender frame] fromView:[sender superview]];
popover.sourceRect = popoverRect;
The difference is that I create the UINavigationController in the usual manner...
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController: vc];
...set the navigationController's presentation style to popover, then get the popoverPresentationController from the navigationController - before I was doing those two methods on the UIViewController.
nc.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popover = [nc popoverPresentationController];
Finally, I present the navigationController:
nc.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popover = [nc popoverPresentationController];
This approach presents a popover, in compact and regular horizontal sizes, that contains navigation controller functionality: just what I wanted.
Thanks again to Larcerax whose answer wasn't what I needed, but made me re-think what I was doing in a different way. As usual, StackOverflow comes through.
Here's what you should try. I use about 10-40 navigation controllers per app for the apps I work on and I've had this same sort of issue, I've not used popovers, but I've subclassed the crap out of navigation controllers and view controllers to have encountered your same problem. The thing is that if you do this:
UIViewController * ff = [UIViewController new]
[self presnetViewController:ff ... blah blah blah
There is apparently NO navigation system attached to the modal view controller and therefore you can't navigate to anything else, you can only close the modal and move on. So, this is what I do to resolve this and it works everytime, well it works everytime for UIViewControllers, give it a shot
see the following, it's not for popovers, but the principle is the same:
NSHTermsOfServiceViewController * pvc = [NSHTermsOfServiceViewController new];
UIBarButtonItem * backBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"exit-button"] style:UIBarButtonItemStylePlain target:self action:#selector(backerPressed)];
NSHNavigationController * ssf = [[NSHNavigationController alloc] initWithRootViewController:pvc];
[[pvc navigationItem] setLeftBarButtonItem:backBarButtonItem];
[[self navigationController] presentViewController:ssf animated:true completion:nil];
NSHTermsOfServiceViewController <== is a subclass of another subclass of a UIViewcontroller, it's basically a UIViewController on steroids, that's all
NSHNavigationController is a UINavigationController that is subclassed and pumped up on steroids for animations
The flow is this:
create a viewController
create a new UINavigationController
Set the view controller you created in step 1 as the root view controller of the navigation controller created in step 2
present the NAVIGATION CONTROLLER, not the UIViewController, you can present this navigationController from a view controller like so ..
v
[self presentViewController:ssf animated:true completion:nil];
or you can present it from the current view controller's navigation controller which is what I prefer, like so:
[[self navigationController] presentViewController:ssf animated:true completion:nil];
Your code, modified, the only problem is that I don't know if you can present a UIPopOverViewController by rooting it inside a navigation controller
OptionsController *vc = [[OptionsController alloc] initWithNibName:#"OptionsView" bundle:nil];
UIPopoverPresentationController *popover = [vc popoverPresentationController];
UINavigationController * stuff = [[NSHNavigationController alloc] initWithRootViewController:popover];
stuff.modalPresentationStyle = UIModalPresentationPopover;
stuff.delegate = self;
[self.navigationController presentViewController:stuff animated: YES completion: nil];
popover.permittedArrowDirections = UIPopoverArrowDirectionUp; // change as necessary
popover.sourceView = self.view;
CGRect popoverRect = [self.view convertRect:[sender frame] fromView:[sender superview]];
popover.sourceRect = popoverRect;
Yep, my bad, doesn't work for popover, I just tried it
So, with that said, is it absolutely necessary to use a popover? Why are you using this and now just a UIViewcontroller that you reconfigure to look like a popover and then you have what you need?
Here's this, I just tried it with the Ipad simulator, and it allowed a push, just as it should have.
NSHLoginViewController * pvc = [NSHLoginViewController new];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:pvc];
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:navController];
UIView * stuff = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 1000, 1000)];
[self.view addSubview:stuff];
[popover presentPopoverFromRect:[[self contentView] nameField].frame inView:stuff permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
this: [[self contentView] nameField].frame is just a uitextfield, nothing special, that's all, and the method above presented the login viewcontroller, when I put in my credentials, I pressed log in and it pusehed the next viewcontroller as it normally would, there's probalby something wrong with the touches being intercepted by your uitableview or whatever, perhaps not, but this method did work for me.

UINavigationController clear background color

That simple example but that don't work;
I have ViewController where inside on NavigationConroller, then I want to add new ViewConroller with its self navigation controller.
In main viewController:
CustomViewController *vc = [[CustomViewController alloc] init];
NewNavigationVC *nav = [[NewNavigationVC alloc] initWithRootViewController:vc];
[self presentViewController:nav animated:NO completion:nil];
Two controllers has a background color clear, but still black color.
Navigation bar I can do clear, but not a view.
UPDATE:
if i change self.window.backroundColor to red for example, that work but not clear
UPDATE 2:
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
and when I want to dealloc vc
[vc willMoveToParentViewController:nil];
[vc.view removeFromSuperview];
[vc removeFromParentViewController];
All work ok without navigation controller
A viewController's view's backgroundColor can't be clear (as in showing the previous viewController's view on the stack). Pushing or presenting a viewController will put the new viewController on the stack and hide the previous viewController completely.
If you want a clear backgroundColor on the view, you will need to either:
1) set the viewController as a childViewController of the previous viewController - then animate the transition yourself.
Or
2) transplant the viewController logic into the previous viewController and have a new uiview act as that view (you also need to animated the transition yourself).
The solution is as follows. For clear example we use tableViewController:
UITableViewController *modalVC = [UITableViewController new];
UINavigationController *modalNVC = [[UINavigationController alloc] initWithRootViewController:modalVC];
UIViewController *mainVC = [UIViewController new];
UINavigationController *mainNVC = [[UINavigationController alloc] initWithRootViewController:mainVC];
modalVC.view.backgroundColor = UIColor.clearColor;
mainVC.view.backgroundColor = UIColor.redColor;
mainNVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[mainNVC presentViewController:modalNVC animated:YES completion:NULL];
The key feature is that you have to set modalPresentationStyle of presentingViewController to UIModalPresentationCurrentContext.
It works fine BUT without slide animation. You will get result immediately.
But you can still use "blood hack" to retain visual animation by successive presenting, dismissing and presenting again:
modalVC.view.backgroundColor = UIColor.clearColor;
mainVC.view.backgroundColor = UIColor.redColor;
[mainNVC presentViewController:modalNVC animated:YES completion:^{
[modalNVC dismissViewControllerAnimated:NO completion:^{
mainNVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[mainNVC presentViewController:modalNVC animated:NO completion:NULL];
}];
}];
You basically need to tell the navigation controller to:
navigation.modalPresentationStyle = .overCurrentContext
In other words:
A presentation style where the content is displayed over another view controller’s content.
and that's it.
You can also make sure that:
navigation.view.backgroundColor = .clear

Objective C - UINavigationController in Modal View

I'm developing for iPad and I have the following problem:
I'm trying to show a modal with his own size and his own UINavigationController, I already done that, but when I present the modal with his UINavigationController, I get this:
I want the UINavigationController to fit to modal size. I leave the code of how I'm presenting the modal:
- (void)createANewEvent:(id)sender
{
AddEditEventViewController *addEditEventViewController = [[(ScheduleViewController *)self.viewContainer storyboard] instantiateViewControllerWithIdentifier:#"AddEditEventViewControllerID"];
addEditEventViewController.modalPresentationStyle = UIModalPresentationFormSheet;
addEditEventViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addEditEventViewController];
[self.viewContainer presentViewController:navigationController animated:YES completion:nil];
}
How I resize the modal:
AddEditEventViewController.m
// Resize the view
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.view.superview.bounds = CGRectMake(0, 0, 548, 768);
self.view.superview.backgroundColor = [UIColor clearColor];
}
Hope you can help me!
Thanks in advance! :)
A view should never modify its superview's size or position.
The problem is that you're setting modalPresentationStyle on the root view controller instead of the view controller you're presenting, navigationController.
Delete:
addEditEventViewController.modalPresentationStyle = UIModalPresentationFormSheet;
addEditEventViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
Delete the viewWillLayoutSubviews method you posted.
Add:
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
navigationController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
I would sub-class UINavigationController and implement the method:
preferredContentSize
Something like
- (CGSize) perferredContentSize {
return CGSizeMake(548,768);
}
That's all you should need to do to have properly resized modal views.
and have it return a CGSize structure with the size that you want the modal to be. This is the 'correct' way to do it per Apple documentation.
In conjunction, remove the viewWillLayoutSubviews method, that's just going to cause problems sometime in the future.
After [self.viewContainer presentViewController....] add:
navigationController.view.superview.bounds = CGRectMake(0, 0, 548, 768);
and get rid of your viewWillLayoutSubviews

Issue with definesPresentationContext / UIModalPresentationCurrentContext - Current context view controller gets lost

This only accours if you are presenting in a view controller that is managed by a navigation controller.
The reproduction steps are:
1 - Present a view controller using UIModalPresentationCurrentContext
self.definesPresentationContext = YES;
ViewController* viewController = [[ViewController alloc] init];
viewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presentOnViewController presentViewController:viewController animated:YES completion:nil];
2 - Present a view controller over the top using the default full screen presentation style
ViewController* viewController = [[ViewController alloc] init];
[self presentViewController:viewController animated:YES completion:nil];
3 - Dismiss the top presented view controller (the full screen one)
[self dismissViewControllerAnimated:YES completion:nil];
Now the problem is the 2nd view controller (presented using UIModalPresentationCurrentContext) disappears. Also it is impossible to present another view controller using UIModalPresentationCurrentContext, because the system thinks its still there.
I believe the issue is a bug in the framework. As mentioned it only occurs when the presenting in a view controller managed by a navigation controller. There is a nasty work around which uses the containment API. It creates a dummy view controller which views are presented from. The steps are:
1 - When presenting a view in context who's parent is a navigation controller, use a dummy view controller:
- (void)presentInContext
{
UIViewController* presentOnViewController = self;
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
// Work around - Create an invisible view controller
presentOnViewController = [[DummyViewController alloc] init];
presentOnViewController.view.frame = self.view.frame;
// Containment API
[self addChildViewController:presentOnViewController];
[self.view addSubview:presentOnViewController.view];
[presentOnViewController didMoveToParentViewController:self];
presentOnViewController.definesPresentationContext = YES;
}
ViewController* viewController = [[ViewController alloc] init];
viewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presentOnViewController presentViewController:viewController animated:YES completion:nil];
}
2 - When dismissing the view controller tidy up
- (void)dismissSelf
{
__weak UIViewController* presentingViewController = self.presentingViewController;
[self dismissViewControllerAnimated:YES completion:^{
// Remove the dummy view controller
if ([presentingViewController isKindOfClass:[DummyViewController class]])
{
[presentingViewController willMoveToParentViewController:nil];
[presentingViewController.view removeFromSuperview];
[presentingViewController removeFromParentViewController];
}
}];
}
Thats it... The fix is dirty, but does the trick with no visual flicker.

Resources