I have a view controller set up programmatically which has a child UIPageViewController. This displays various other view controllers that are also set up programmatically. These view controllers' views have translatesAutoresizingMaskIntoConstraints set to YES, but the page view controller's view uses constraints to position itself in the top view controller.
The problem is, when the user rotates the device the page view controller resizes its frame but its child view controllers don't. By didRotateFromInterfaceOrientation, its frame is still from the old orientation. I've verified that the rotation methods are called on the child view controllers, their frame just doesn't change.
I set up the page view controller like this:
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.view.backgroundColor = [UIColor clearColor];
self.pageViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;
[self.pageViewController willMoveToParentViewController:self];
[self.view addSubview:self.pageViewController.view];
[self addChildViewController:self.pageViewController];
[self.pageViewController didMoveToParentViewController:self];
self.currentPageIndex = 0;
self.currentPageController = [self pageForIndex:self.currentPageIndex];
self.currentPageController.delegate = self;
[self.pageViewController setViewControllers:#[self.currentPageController] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
I've tried calling setNeedsLayout but it's a clumsy rotation animation and the next page that I swipe to is also not resized properly.
Why is the page view controller not resizing its child view controllers, and how do I get it to do this?
Thanks!
First, set up everything with auto layout, programmatically or Storyboard. Then, catch the orientation change in the parent view controller and force the UIPageViewController's view to layout again.
This is my working code (iOS 8.0 and later). childController1 was set up on a Storyboard with all layout using constraints, and instantiated with [self.storyboard instantiateViewControllerWithIdentifier:#"ChildControllerId"].
- (void)createPageController
{
...
pageController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
pageController.dataSource = self;
[pageController setViewControllers:#[childController1] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self addChildViewController:pageController];
[self.view addSubview:pageController.view];
UIView *pageView = pageController.view;
pageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[pageView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(pageView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[pageView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(pageView)]];
[pageController didMoveToParentViewController:self];
...
}
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[pageController.view setNeedsLayout];
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
}
Hope it helps.
Adding a controller's view directly into another controller's view and expecting the rotation and life cycle methods to be called isn't the best policy. I kind of feel adding it to the controller hierarchy could work, but I can't comment much on it.
I tested with a tab bar navigator and my UIPageViewController got the events along with the controller that was being shown in the UIPageViewController. I'm guessing that if I push the controller into a UINavigationController I'll get the same result.
I'd recommend you to show your UIPageVIewController in a more standard way than adding it's view to another controller's view.
Related
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
I need advice to create user interface like this UI.
Is it better to use UITableView or UICollectionView because its display is row based? But, when I swipe left and right, it has different section like the collection view horizontal scroll. It makes me confused because I have never created such a UI. I prefer to use UITableViewController, but when I see the segment, I can't use segmented controller because I can't swipe to change the filter.
1) Create storyboard view controller (UIViewController). Controller's view has two subviews: title view and container view (two IBOutlet views).
2) Title view - it's a top view (in your case - collection view with items).
Container view - it's a view for pageViewController.
You will do something like this:
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:#[[self viewControllerForIndex:index]]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.containerView addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
- (UIViewController *)viewControllerForIndex:(NSUInteger)index {
UITableViewController *vc = [[UITableViewController alloc] init];
return vc;
}
I am making an app where I want to show the child view controller only within the parent view controller i.e. only in 3/4 part of the parent view controller. I have implemented the following code but the child view controller is filling up the whole parent view controller.
My code is:
- (CardsChildViewController *)viewControllerAtIndex:(NSUInteger)index {
CardsChildViewController *childViewController = [[CardsChildViewController alloc] initWithNibName:#"CardsChildViewController" bundle:nil];
childViewController.index = index;
return childViewController;
}
and on viewDidLoad function I am writing:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.pageController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageController.dataSource = self;
[[self.pageController view] setFrame:[[self view] bounds]];
CardsChildViewController *initialViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:self.pageController];
[[self view] addSubview:[self.pageController view]];
[self.pageController didMoveToParentViewController:self];
}
I made this by taking help from : http://www.appcoda.com/uipageviewcontroller-tutorial-intro/
Try to change pageController's frame to the following.
CGRect frame = self.view.frame;
CGRect insetFrame = CGRectInset(frame, frame.size.width * 1/8, frame.size.height * 1/8);
Your page view controller is the child, not the view controller you create with your viewControllerAtIndex method.
The easiest way to set up a child view controller is to put a container view in your storyboard, make the child view controller a separate scene, and control-drag an embed segue from the container view to the view controller that you want to be the child.
When you do that the compiler does all the work to set up the connections to manage the child correctly. Your parent view controller's prepareForSegue method will fire when the view is first loaded and the child is installed. At that point you can hook up any outlets, delegate connections, or whatever else you might need.
Failing that, you can adjust the frame of your page view controller's view before adding it as a subview, as #ErAdhish suggests. But you will have a bunch of setup to do in order to forward housekeeping messages from the parent to the child.
When i press a button on my view controller, i would like to present another controller on top of it, but in the middle and not in full screen.
How could i present a controller on top of another controller in such way?
If you are trying it on iPad you can always set up a Popover that contains your new view.
UIYourNewViewController *vc = [[UIYourNewViewController alloc] init];
UIPopoverController *popVc = [[UIPopoverController alloc] initWithContentViewController:vc];
[popVc setPopoverContentSize:*the size that you want or your resized vc*];
[popVc presentPopoverFromRect:*position of the screen you want to show the popover* inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
With this you will create a Popover of the size of the view of your viewcontroller and you can pop it up in the position that you want.
To make sure it works on iPhone also, you should create a category for the UIPopoverController and add this method in the .m
+ (BOOL)_popoversDisabled {
return NO;
}
Remember to declare the method in the .h of the category.
You need to set this property to your controller before presenting it.
controller.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:controller animated:YES completion:nil];
Yes it is possible you have to present view controllers view with animation. Please refer the below code. You will get some idea its showing animation from bottom of screen to middle of screen.
YourViewController *viewController = [[YourViewController alloc] init];
[viewController.view setFrame:CGRectMake(0, -(CGRectGetHeight(viewController.view.frame)), CGRectGetWidth(viewController.view.frame), CGRectGetHeight(self.view.frame))];
[self.view addSubview:viewController.view];
[UIView animateWithDuration:0.8 animations:^{
[viewController.view setFrame:CGRectMake(0, 0, CGRectGetWidth(viewController.view.frame), CGRectGetHeight(viewController.view.frame))];
} completion:^(BOOL finished) {
[self.navigationController pushViewController:viewController animated:NO];
}];
In iPhone, presenting a UIViewController is always fullscreen. On iPad, you can use a UISplitViewController or build a custom container, but the view controllers you present will fill the containers in both a UISplitViewController and custom container controller.
To present content on only part of the screen, you can animate a UIView onto your view controller. There are ways to present a view controller and still have another view controller show up behind it, but this is not recommended.
Check out this other question for more information on creating a custom container view controller..
The app is set to only support landscape mode.
The custom UIViewController has the following code:
- (void)viewDidLoad {
[super viewDidLoad];
MBMainViewController *mainViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MBMainViewController"];
[self addChildViewController:mainViewController];
[self.view addSubview:mainViewController.view];
[mainViewController didMoveToParentViewController:self];
self.mainViewController = mainViewController;
}
When app is launched, the frame for the view of the child viewcontroller (mainViewController) is still set to the dimension of a portrait.
Is there something I'm missing so the right frame size is set on the child viewcontroller's view?
This apparently only happens to UIViewController that are instantiated from storyboard. In this case, it only works if we set the autoresizingMask of the view belonging to the child view controller:
- (void)viewDidLoad {
[super viewDidLoad];
MBMainViewController *mainViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MBMainViewController"];
// Set the autoresizingMask as a fix
mainViewController.view.autoresizingMask = UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleHeight;
[self addChildViewController:mainViewController];
[self.view addSubview:mainViewController.view];
[mainViewController didMoveToParentViewController:self];
self.mainViewController = mainViewController;
}
My guess is that when you instantiate a view controller from the storyboard, the constraints have already been set or that it doesn't come with the right autoresizingMask values.