I have a modal view controller that I show with UIModalPresentationPageSheet. The problem is that its default size is too large for my content: so I want to resize its frame to adjust accordingly with my content.
Does anyone know of a way/trick to do this?
Thanks.
Actually you can resize the view controller that gets presented with UIModalPresentationPageSheet. To do it you need to create a custom view controller class and add the following to the class:
<!--The header file-->
#interface MyViewController: ViewController{
//Used to store the bounds of the viewController
CGRect realBounds;
}
<!--In the .m file-->
//viewDidLoad gets called before viewWillAppear, so we make our changes here
-(void)viewDidLoad{
//Here you can modify the new frame as you like. Multiply the
//values or add/subtract to change the size of the viewController.
CGRect newFrame = CGRectMake(self.view.frame.origin.x,
self.view.frame.origin.y,
self.view.frame.size.width,
self.view.frame.size.height);
[self.view setFrame:newFrame];
//Now the bounds have changed so we save them to be used later on
_realBounds = self.view.bounds;
[super viewDidLoad];
}
//viewWillAppear gets called after viewDidLoad so we use the changes
//implemented above here
-(void)viewWillAppear:(BOOL)animated{
//UIModalpresentationPageSheet is the superview and we change
//its bounds here to match the UIViewController view's bounds.
[super viewWillAppear:animated];
self.view.superview.bounds = realBounds;
}
And then you display this view controller with a UIModalPresentationPageSheet. And that's it. This does work for iOS 5.1.1 and iOS 6 as of the date of this post.
You can't resize a UIModalPresentationPageSheet because its size is not modifable.
This works for resizing a view controller that's presented as UIModalPresentationFormSheet. I would give this a try, I'm not sure if it'll work or not:
navController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:navController animated:YES];
//these two lines are if you want to make your view smaller than the standard modal view controller size
navController.view.superview.frame = CGRectMake(0, 0, 200, 200);
navController.view.superview.center = self.view.center;
Related
I’m trying to make an iOS Action app extension with minimal UI. Basically it would just show a progress indicator until the action completed. I just want to be able to animate the view so that it slides down from the top & then slides back up when the action has completed. If anyone is familiar with Instapaper’s Share extension, then that’s the kind of basic UI I’m looking for.
The problem is that when I try to duplicate this functionality - I just have a small UIView that animates down from the top - I get a black background behind my view. I can’t figure out how to make that background transparent so that the stuff behind my view is still visible. Does anyone know how to do this?
As a starting point I’m just using the default Action Extension template that’s created by Xcode...
Create a new iOS app project in Xcode.
Add a new target -> Action Extension.
In the ActionViewController.m file add a viewWillAppear method to animate the view (using a 1 second animation so that the black background is easily seen):
Code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.view.frame = newFrame;
[UIView animateWithDuration:1.0 animations:^{
self.view.frame = currFrame;
}];
}
When this is run the view is animated sliding down from the top. However rather than seeing the UI of the calling App all you see is a black background.
I’ve tried a number of things - changing the modalPresentationStyle (doesn’t seem to do anything), setting the entire view to hidden (this just makes the whole screen black), etc.
For reference this is using iOS 9.3.2 and Xcode 7.3.1.
From what I understand from Apple docs
In iOS, an Action extension:
Helps users view the current document in a different way
Always appears in an action sheet or full-screen modal view
Receives selected content only if explicitly provided by the host app
The fact that the Action extension always appear in a full-screen view on an iPhone might mean that there's no way of having a transparent background.
I am certain that a Share extension can be animated (I've done it myself) how you want it and have a transparent background. That's why Instapaper's Share extension works nicely.
You are facing two problems:
1. When you present a view controller, the default behavior is the controller is full screen context. That is the reason you see a black screen.
2. You are trying to change self.view.frame when the controller is presented on full screen.
Yet there is a way to achieve this kind of behavior you are looking for, in one of three ways:
A. Specify "modalPresentationStyle" to "UIModalPresentationOverCurrentContext" and set the presenting controller to "definesPresentationContext".
That way, when you present the controller, the presenting controller will be behind the presented controller.
And insted of changing self.view.frame you will set self.view background color to clear, and add a subview and use it as a background view:
//Presenting view controller:
-(void) presentPopUpViewController {
self.definesPresentationContext = YES; //self is presenting view controller
self.presentedVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:self.presentedVC animated:NO completion:nil];
}
//Presented view controller:
-(void) viewDidLoad {
[super viewDidLoad];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.myBackroundView = [[UIView alloc] init];
self.myBackroundView.backgroundColor = [UIColor yellowColor];
self.myBackroundView.frame = newFrame;
[self.view addSubview:self.myBackroundView];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIView animateWithDuration:5.0 animations:^{
self.myBackroundView.frame = self.view.frame;
}];
}
B. Add the presented view controller as a Child view controller. that way the life cycle stays the same, but you can add I'ts view as a subview, and change I'ts frame.
//Presenting view controller:
-(void) presentChildViewController {
[self addChildViewController:self.presentedVC];
[self.view addSubview:self.presentedVC.view];
[self.presentedVC didMoveToParentViewController:self];
}
//Presented view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.view.frame = newFrame;
[UIView animateWithDuration:1.0 animations:^{
self.view.frame = currFrame;
}];
}
C. Don't use UIViewController, use UIView. Use a "Decorator" object, that you pass it the ViewController you would like the view to disaply on, and the "Decorator" will add the view as subview, and deal with the animation. No need for an example for this scenario.
It is wrong to start animations when your view hasn't yet appeared. Can you, please, try the same code in viewDidAppear.
Also animating main view controller's view will make underlying layers visible, so you've got to use another view on top of view controller's main view, like this:
UIView *progressView = [[UIView alloc] init];
progressView.frame = self.view.bounds;
progressView.backgroundColor = [UIColor redColor];
[self.view addSubView:progressView];
I'm displaying a UIViewController as one of the pages in a UIPageViewController.
This viewController (VC1) has a size of (320, 568). What I then do is display another VC using this method:
- (void)displayContentController:(UIViewController *)content {
[self addChildViewController:content];
content.view.frame = self.view.frame;
[self.view addSubview:content.view];
[content didMoveToParentViewController:self];
}
Called this way:
NewViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:#"NewViewController"];
[self displayContentController:newVC];
The reason I'm doing this instead of pushing it or presenting it modally, is because I need it to overlap the other UIViewController(VC1), blurring it while sliding with the UIPageViewController like VC1 normally would.
viewDidLoad in newVC is reporting a self.view.frame of (320, 568) at first, but then a little later it suddenly reports a self.view.frame of (320, 571) - rather than a height of 568.
I'm using autolayout.
Why is this and how can I resolve it?
In viewDidLoad() method frames don't give you actual size of controller views. To get correct sizes try to check it in viewdidlayoutsubviews() method of ViewController
Storyboard:
Constraints:
Result:
I'm trying to understand autolayout and how I can use it inside a container.
I got a default ViewController that was made for me when I opened the Storyboard. I put a View Container inside there. And then I added a loose (not connected to anything) ViewController. I want the content inside the new ViewController to be put in the container.
So the added ViewController which will be put inside the Container consists of three labels where I am using autolayout.
Clicking on the black bar for the Container View Controller in the Storyboard I go to Identity Inspector and set the Custom Class to "ContainerViewController". Then I set the Storyboard ID to "ChildController" for the loose View Controller.
Then I override viewDidLoad in ContainerViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
}
Why won't the constraints for the autolayout work when put in a container? I hoped they would, so I could take this further to a UIPageViewController.
EDIT:
So it seems to be the frame size. I need to do something like this before adding it as sub view:
child.view.frame = CGRectMake(0, 0, 265, 370);
Now I created an outlet for the Container in ViewController.h.
#property (weak, nonatomic) IBOutlet UIView *container;
But.. From ContainerViewController, how can I ask presenting view controller (ViewController) of this property when it's not set yet?
ViewController *parent = (ViewController*)self.presentingViewController;
UIView *container = parent.container;
NSLog(#"ParentView Container Width: %f, Height: %f", container.frame.size.width, container.frame.size.height);
It just gave me zero width and height, because the view hasn't loaded yet. Later on when it loads, I get the actual values..
NSLog(#"View Controller Container. Width: %f, Height: %f", self.container.frame.size.width, self.container.frame.size.height);
The question: How can I access properties of presentingViewController when they are ready/loaded?
when adding a child view controller you should set its view frame like this
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
//set the view frame
child.view.frame = self.view.bounds;
So the child view controller will resize its view frame and layout contraints will do their homework ;)
EDIT
put the code in -(void)viewDidLayoutSubviews like this:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
//set the view frame
child.view.frame = self.view.bounds;
}
Just try to set frame in viewWillLayoutSubviews for view Controller
I am working on an iPad app with a few different modal views, and this code is pretty common:
UIViewController *v1 = [[UIViewController alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
nav1.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:nav1 animated:YES completion:nil];
It could be that I am doing this wrong, but this is how I am presenting a navController-nested vc modally.
The problem is that within the v1 class, any reference to self.frame/bounds results in full screen dimensions:768x1024. Even though the navController clearly isn't being displayed with that size.
What should I be doing to make it so that the v1 vc knows how big it actually is? So that if I wanted to add, say, a tableView, it would know how big it should be?
Thanks!
EDIT:
I have tried a few more things, and still don't have a solution to this problem. I have made a simple sample project to illustrate the problem I am having. I just have one view and this is the core of the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"Bounds: %#", NSStringFromCGRect(self.view.bounds));
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(self.view.frame.size.width - 400, 0, 400, 400);
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:#selector(presentModal) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)presentModal {
SSViewController *view = [[SSViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
nav.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
When this view loads, I have a big red button that is up against the top right corner of my view. When I press the button, it loads the same VC in a modal view embedded in a navController. The button shows up nearly off screen because the frame hasn't changed. It still shows as full screen. Here is a link to the project.
Not sure why you're having the issue you're having. I'm using the following code:
- (void)presentNewView {
NewViewController *newVC = [[NewViewController alloc] initWithNibName:nil bundle:nil];
newVC.view.backgroundColor = [UIColor redColor];
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:newVC];
newNC.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:newNC animated:YES completion:NULL];
}
.. it results in the following in the simulator:
.. and when I print out the first ViewController's frame and bounds (I thought it might be an issue with the two) I get the following:
frame height: 1024.000000
frame width: 768.000000
bounds height: 1024.000000
bounds width: 768.000000
.. and when I print out the presented ViewController's frame/bounds I get the following:
frame height: 620.000000
frame width: 540.000000
bounds height: 620.000000
bounds width: 540.000000
How are you determining the size of the frame exactly? Any reference within the v1 class that was presented modally SHOULD know its actual size, like I showed above.
EDIT
The major difference I found with my code and yours, is that in my code I created a subclass of my view controller "NewViewController" and was printing out the frame from within that class. The class itself seems to be aware of its correct bounds, but the class the presented it seems not to be. This is demonstrated by printing the view property from the ViewController class that presented it:
NewViewController's View From Presenting Class: frame = (0 0; 768 1024)
..compared to printing out the self.view from within the ViewDidAppear method of NewViewController itself:
NewViewController's View Did Appear: frame = (0 0; 540 576)
Moral of the story, if you are going to be presenting a UIViewController in the way you've shown, you're likely going to want to subclass UIViewController anyway so you can customize it however you want, so within that file if you reference self.view or self.bounds you will be getting the ACTUAL view/bounds.
EDIT #2
Based on the project you provided, the reason why you are having that issue is because you are printing out the frame/bounds of the view in viewDidLoad as opposed to viewDid/viewWillAppear. Adding those NSLog statements to VWA or VDA provides you the correct frame, so as I said in my initial edit, you should be fine accessing the view of the modal correctly at that point.
It's a new feature of iOS7. If you embed a UIViewController in navigation bar, it won't get smaller, because by default navigation bar is translucent.
You will see it if you change the background color of a view controller, that the top part of it is actually behind the navigation bar.
To lay out the v1 view controller underneath the navigation bar, you can use following code:
if ([v1 respondsToSelector:#selector(edgesForExtendedLayout)]) {
v1.edgesForExtendedLayout = UIRectEdgeNone;
}
It will behave just as in iOS6.
when presenting view controllers modally from a child view controller (one that has lass than the full screen and is a child of another view controller..) it is important to do this so that the modal controller knows the size of the canvas its appearing in
childViewController.definesPresentationContext = YES;
modalViewControllerWhichIsAboutToBePushed.modalPresentationStyle = UIModalPresentationCurrentContext
See below - the tableView cells are getting cut off. Why doesn't this work? The width of the popover is 240.
(In a subclass of UITableViewController)
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.frame = CGRectMake(0,0,200,200);
}
You have to specify the content size of the ui controller you are displaying. You can do it in 2 ways:
access the ui controller from your popover controller and set it size:
UIViewController* yourViewController = yourPopOverController.contentViewController;
yourViewController.contentSizeInViewController = CGSizeMake(300, 600);
check the checkbox "Use Explicit Size" for popover in the inspector of the viewController in storyboard
As you see the content ui controller is the one responsible of setting the size of your popover.
Have you tried specifying the popover's content size?
i.e.
self.popoverController.popoverContentSize = CGSizeMake(400, 500);
I've found that while popover's are "suppose" to adjust to the appropriate size based on the content view controller, this doesn't always work as well as it should.