Size a UIViewController's view to parent UIWindow's bounds - ios

I want to give a UIViewController's view a size that is different from the device's screen size. I know I can usually achieve this by adding the view controller as a child view controller of another parent UIViewController that has defined a frame for the child, but I am in a situation that seems a little different.
I have a UIWindow that only takes up a portion of the screen (it's got a frame that's basically (0, 0, DEVICE_WIDTH, HEIGHT_LESS_THAN_DEVICE_HEIGHT). This window shows up with the proper sizing and positioning when presented. I am setting a view controller as the rootViewController of the window, and then presenting the window by setting its hidden value to false. When this happens, the view controller's view ends up sized to fill the device's screen (i.e. a frame of (0, 0, DEVICE_WIDTH, DEVICE_HEIGHT)).
What I would like is for the view controller to inherit its sizing from the UIWindow it is set as the root view controller of. Is there a way to do this?
I have also tried overriding loadView() and returning a custom-sized view there. Logging the view shows that the view controller's view object is correctly sized during viewDidLoad, but is overwritten with the default size by viewWillAppear:. I would be open to using loadView() to size the view controller if inheriting sizing from the window isn't possible, but I don't know how to make the custom size stick.
Note: The reason why I am trying to add a view controller to the window is because I want to take advantage of the view controller lifecycle methods such as viewDidAppear:, which is why I am not just creating a simple UIView and adding it as a subview of the window.

As counter intuitive as it may seem, if you set set self.view.frame on viewWillAppear (IOS 8) or viewDidAppear (IOS 7) you will be able to make it work.
Swift code (IOS 8):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Banner style size, for example
self.view.frame = CGRectMake(0, 0, 320, 50)
}
For IOS 7, I had to use viewDidAppear, which is obviously an unsatisfactory solution. So I had to start the view with alpha = 0.0 and set alpha = 1.0 on viewDidAppear, after modifying self.view.frame.
Hope it helps.

Related

Mac Catalyst popover set/limit size problem

Is it possible to limit/fix (min/max) popover size on Mac Catalyst? See the attached video.
Example video
Yes, but it's a bit of a hack. Big Sur is loading these presented view controllers as their own windows, so we can grab the window's windowScene and set its sizeRestrictions. The best (?) place to do this is in the presented view controller's viewWillLayoutSubviews method:
class MyPresentedViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if #available(macCatalyst 14, *) {
view.window?.windowScene?.sizeRestrictions?.minimumSize = CGSize(width: 500, height: 500)
view.window?.windowScene?.sizeRestrictions?.maximumSize = CGSize(width: 800, height: 800)
}
}
}
If you don’t want the presented view to be resizable at all, just set the minimumSize and maximumSize to the same value.
I don't love using viewWillLayoutSubviews like this, but the windowScene is still nil in viewDidLoad and viewWillAppear, and while it is non-nil in viewDidAppear, setting sizeRestrictions there will cause a visible resize on screen.
The good news is that this problem may be fixed in Big Sur 11.1. According to the beta release notes, macOS 11.1 will respect preferredContentSize and they won't be resizable by default:
When you present a view controller with page sheet or form sheet presentation style, the size of the view controller’s root view is, by default, determined by the value returned from the presented view controller’s preferredContentSize method and the view is not resizable. You can arrange for the presented view controller to be resizable by using Auto Layout to specify the maximum and minimum sizes of its root view. To enable this, set the canResizeToFitContent property of the application’s main window to YES. One way to do this is to override the willMove(toWindow:) or didMoveToWindow() methods of a view in the main view controller. (65254666)

iOS: Resizing popover dynamically [duplicate]

So I realized this myself and found this answer to confirm:
Prevent contentSizeForViewInPopover from animating
Basically I am in a similar boat in that I want to dynamically resize my popover, depending on how much data I have to display. I am also getting this animation action where the popover view moves into place. The problem is, I can't set the popover's content size in the caller/parent, because neither the size of the content view for the text view nor the frame for the text view in my popover is known until viewDidAppear or viewDidLayoutSubviews is called, but of course at this point it's too late, the popover is already on its way to being visible, thus I am getting this unwanted animation.
I am dynamically setting the size of the text views in the popover view controller class to fit the amount of data the text views are displaying, and this only works at the point where their frame size is defined/known/however you want to word it when it hasn't gotten to that point yet in the view lifecycle.
I should mention that I have my view controllers and view all done in a storyboard.
Does this make sense? Hope I'm explaining it well. Any help or suggestions greatly appreciated.
EDIT Here is my viewDidLayoutSubviews method from the view controller I am loading in the popover - hopefully this makes it clear. The first time this loads, if I don't do this in viewDidLayoutSubviews or viewDidAppear the frame for my 2 text views are CGRectZero, the text views don't resize for the content correctly and the popover size isn't right either:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.physicianDataNewValueTextView.text = self.physicianDataNewValue;
CGRect frame = self.physicianDataNewValueTextView.frame;
frame.size.height = self.physicianDataNewValueTextView.contentSize.height;
self.physicianDataNewValueTextView.frame = frame;
self.physicianDataOldValueTextView.text = self.physicianDataOldValue;
frame = self.physicianDataOldValueTextView.frame;
frame.size.height = self.physicianDataOldValueTextView.contentSize.height;
self.physicianDataOldValueTextView.frame = frame;
self.contentSizeForViewInPopover = CGSizeMake(384.0f, self.physicianDataOldValueTextView.frame.size.height + 75.0f);
}
So I decided to use delegation. Instead of resetting the content size in the view controller for the popover, I call my delegate (which is the containing view controller) with the new size so I can set it there with the animated property set to NO. Works well.

iPhone, iOS 8: How to presentViewController smaller than original view controller?

I have two view controllers, I want to present view controller(VC) of the first VC. Second VC have smaller size. I want to show second view controller over the first one. Like a popover. You can imagine it, if we add another view controller that slides from bottom to top but stops at navigation bar.
Here is my code:
#IBAction func showSecondVC(sender: AnyObject) {
let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as! SecondViewController
secondViewController.view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x, y: header.frame.height), size: CGSize(width: view.frame.width, height: view.frame.height - header.frame.height))
secondViewController.definesPresentationContext = true
secondViewController.providesPresentationContextTransitionStyle = true
secondViewController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.presentViewController(secondViewController, animated: true, completion:nil)
}
You can see that I set frame for secondViewController. I want that it will be with this frame, but if I add completion block and show it's frame after animation, it will be same as first View Controller have.
Edit
I also want to mention, that I try to make this in Portrait orientation.
If you try this in iPhone, the built-in root view of presented view controller will always be full screen, so your efforts to resize it will not be successful. (iPad is a different story)
Just let the root view as it is (i.e. don't fight the fact it's full screen size ), but make its background color clear color. If you want to present some kind of customised view/content/whatever it is..for example a smaller view with some warning..or some options. Just add it as a subview of the root view.
UPDATE:
As the UIViewController class documentation says:
"In a horizontally compact environment, the presented view is always full screen."
The only combination I can imagine for this to work is Iphone6+ in landscape mode where the horizontal size class is then larger then compact. So you are out of luck because you want portrait.
To add the Earl Grey's response, after setting the ViewController's background color to clear. Create a segue with the following properties set:
Kind to "Present Modally"
Presentation to "Over Current Context"

Explaining difference between automaticallyAdjustsScrollViewInsets, extendedLayoutIncludesOpaqueBars, edgesForExtendedLayout in iOS7

I have been reading a lot about iOS7 UI transition.
I am not able to get what these three properties automaticallyAdjustsScrollViewInsets, extendedLayoutIncludesOpaqueBars, edgesForExtendedLayout??
For example I am trying to make my view controllers start below the status bar but I am not able to achieve it.
Starting in iOS7, the view controllers use full-screen layout by default. At the same time, you have more control over how it lays out its views, and that's done with those properties:
edgesForExtendedLayout
Basically, with this property you set which sides of your view can be extended to cover the whole screen. Imagine that you push a UIViewController into a UINavigationController. When the view of that view controller is laid out, it will start where the navigation bar ends, but this property will set which sides of the view (top, left, bottom, right) can be extended to fill the whole screen.
Let see it with an example:
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor redColor];
UINavigationController *mainNavigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
Here you are not setting the value of edgesForExtendedLayout, therefore the default value is taken (UIRectEdgeAll), so the view extends its layout to fill the whole screen.
This is the result:
As you can see, the red background extends behind the navigation bar and the status bar.
Now, you are going to set that value to UIRectEdgeNone, so you are telling the view controller to not extend the view to cover the screen:
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor redColor];
viewController.edgesForExtendedLayout = UIRectEdgeNone;
UINavigationController *mainNavigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
And the result:
automaticallyAdjustsScrollViewInsets
This property is used when your view is a UIScrollView or similar, like a UITableView. You want your table to start where the navigation bar ends, because you wont see the whole content if not, but at the same time you want your table to cover the whole screen when scrolling. In that case, setting edgesForExtendedLayout to None won't work because your table will start scrolling where the navigation bar ends and it wont go behind it.
Here is where this property comes in handy, if you let the view controller automatically adjust the insets (setting this property to YES, also the default value) it will add insets to the top of the table, so the table will start where the navigation bar ends, but the scroll will cover the whole screen.
This is when is set to NO:
And YES (by default):
In both cases, the table scrolls behind the navigation bar, but in the second case (YES), it will start from below the navigation bar.
extendedLayoutIncludesOpaqueBars
This value is just an addition to the previous ones. By default, this parameter is set to NO. If the status bar is opaque, the views won't be extended to include the status bar, even if you extend your view to cover it (edgesForExtendedLayout to UIRectEdgeAll).
If you set the value to YES, this will allow the view to go underneath the status bar again.
If something is not clear, write a comment and I'll answer it.
How does iOS know what UIScrollView to use?
iOS grabs the first subview in your ViewController's view, the one at index 0, and if it's a subclass of UIScrollView then applies the explained properties to it.
Of course, this means that UITableViewController works by default (since the UITableView is the first view).
Not sure if you are using storyboards, but if you are, to make your view controllers start below the status bar (and above the bottom bar):
Select the view controller in IB,
In the attributes inspector, deselect 'Extend Edges - Under Top Bars' and 'Extend Edges - Under Bottom Bars'.
I am using storyboards and using the above advice worked however I wasn't exactly sure how to implement it. Below is a short example in swift of how it cleared up the problem by putting the recommended solution into the ViewController.
import Foundation
import UIKit
// This ViewController is connected to a view on a storyboard that
// has a scrolling sub view.
class TheViewController: UIViewController {
// Prepares the view prior to loading. Putting it in viewDidAppear didn't work.
override func viewWillAppear(animated: Bool) {
// this method is an extension of the UIViewController
// so using self works as you might expect.
self.automaticallyAdjustsScrollViewInsets = false
// Default is "true" so this sets it to false tells it to use
// the storyboard as you have it placed
// and not how it thinks it should place it.
}
}
My Problem:
Auto Adjust set to true by default causing a difference between storyboard design and simulator
Resolved:
Code above applied, turning off the auto-adjust.
I solved this problem by adding this line, but my problem was related to a UIView, not UIScrollView
self.navigationController.navigationBar.translucent = NO;
Just bare in mind that
automaticallyAdjustsScrollViewInsets
property works only if some kind of scroll view (table view, collection view,...) is either
The view of VC, or
First subview of this view
Other suggested, that it doest work even if it is the first subview, but there are other scroll views in the view hierarchy.
EDIT (extension DIY)
If you want similar behaviour even if you can't fulfil these conditions (e.g. you have a background image below the scroll view), you can adjust the scroll view insets manually. But please don't set it to constant like 44 or 64 or even 20 like many suggest around SO. You can't know the size ever. There can be the incall/gps/audio notification, navigation bar doesn't have to be always 44 pts etc.
I think the best solution is to use layoutGuide length in didLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: 0, right: 0)
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
You can use the bottomLayoutGuide in the same way.

Prevent animation when dynamically resizing popover

So I realized this myself and found this answer to confirm:
Prevent contentSizeForViewInPopover from animating
Basically I am in a similar boat in that I want to dynamically resize my popover, depending on how much data I have to display. I am also getting this animation action where the popover view moves into place. The problem is, I can't set the popover's content size in the caller/parent, because neither the size of the content view for the text view nor the frame for the text view in my popover is known until viewDidAppear or viewDidLayoutSubviews is called, but of course at this point it's too late, the popover is already on its way to being visible, thus I am getting this unwanted animation.
I am dynamically setting the size of the text views in the popover view controller class to fit the amount of data the text views are displaying, and this only works at the point where their frame size is defined/known/however you want to word it when it hasn't gotten to that point yet in the view lifecycle.
I should mention that I have my view controllers and view all done in a storyboard.
Does this make sense? Hope I'm explaining it well. Any help or suggestions greatly appreciated.
EDIT Here is my viewDidLayoutSubviews method from the view controller I am loading in the popover - hopefully this makes it clear. The first time this loads, if I don't do this in viewDidLayoutSubviews or viewDidAppear the frame for my 2 text views are CGRectZero, the text views don't resize for the content correctly and the popover size isn't right either:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.physicianDataNewValueTextView.text = self.physicianDataNewValue;
CGRect frame = self.physicianDataNewValueTextView.frame;
frame.size.height = self.physicianDataNewValueTextView.contentSize.height;
self.physicianDataNewValueTextView.frame = frame;
self.physicianDataOldValueTextView.text = self.physicianDataOldValue;
frame = self.physicianDataOldValueTextView.frame;
frame.size.height = self.physicianDataOldValueTextView.contentSize.height;
self.physicianDataOldValueTextView.frame = frame;
self.contentSizeForViewInPopover = CGSizeMake(384.0f, self.physicianDataOldValueTextView.frame.size.height + 75.0f);
}
So I decided to use delegation. Instead of resetting the content size in the view controller for the popover, I call my delegate (which is the containing view controller) with the new size so I can set it there with the animated property set to NO. Works well.

Resources