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

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"

Related

What is a good way to add UIPageViewController to parent UIViewController without considering status bar height?

Currently, I have a UIViewController, with its top component consists of a horizontal UICollectionView (MenuTabsView.swift)
Now, I would like to add a UIPageViewController, just below the MenuTabsView.
I have tried the following few approaches.
Programatically without taking status bar height into consideration
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Here's the outcome.
From 1st glance, it seems that UIPageViewController's view need to offset by Y status bar distance. (But why?)
Programatically by taking status bar height into consideration
func presentPageVCOnView() {
let statusBarHeight = CGFloat(20.0)
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY + statusBarHeight, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY - statusBarHeight)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Now, it looks way better.
Use container view without status bar offset
But, I don't feel comfortable, on why we need to manually consider status bar height, during programatically way. I was thinking, maybe I can add a ContainerView to UIViewController, and "attach" the UIPageViewController's view to it?
(I am not sure why during adding Container View to storyboard, an additional UIViewController will be added along. Anyhow, I just manually delete the additional UIViewController)
Then, I use the following code to "attach" the UIPageViewController's view to new container view.
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = containerView.frame
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
But, the outcome is not what as expected. Y offset still happen!!!
Use container view with status bar offset
I try to make sure, there are space of 20, between the top component MenuTabsViews and UIPageViewController's view.
I was wondering, is there any good practice/ solution, to ensure we can add UIPageViewController's view below another component, without affecting by status bar height?
You can do this all without any code -- it just takes an understanding of how UIContainerView works.
There's no real UIContainerView class... it is an automated way of adding a child view controller via Storyboard / Interface Builder. When you add a UIContainerView, IB automatically creates a "default" view controller connected to the container view with an Embed segue. You can change that default controller.
Here's step-by-step (images are large, so you'll probably want to click them to see the details)...
Start with a fresh UIViewController:
Add your "Menu Bar View" - I have it constrained Top/Leading/Trailing to safe-area, Height of 60:
Drag a UIContainerView onto the view - note that it creates a default view controller at the current size of the container view. Also note that it shows a segue. If you inspect that segue, you'll see it is an Embed segue:
Constrain the Top of the container view to the Bottom of your Menu Bar View, and Leading/Trailing/Bottom to safe-area. Notice that the size of the embedded view controller automatically takes the new size of the container view:
Select that default controller... and delete it:
Drag a new UIPageViewController onto your Storyboard and set its Custom Class to PageControllerVC:
Now, Ctrl-Click-Drag from the Container view to the newly added page view controller. When you release the mouse button, select Embed from the popup:
You now have an Embed segue from the container view to your page view controller. Notice that it automatically adjusted its size to match the container view size:
Since the Menu Bar View top is constrained to the safe-area, it will behave as expected.
Since the container view top is constrained to the bottom of the Menu Bar View, it will stay there, and should give you what you want.
No Code Needed :)
Edit
The most likely reason you ran into trouble with loading via code is with you frame setting.
If you try to set frames in viewDidLoad(), for example, auto-layout has not configured the rest of the view hierarchy... so framing will not be what you expect.
You're much better off using auto-layout / constraints, rather than setting explicit frames anyway.
Here is how I would do it from code (assumes you have your "Menu Bar View" connected via #IBOutlet):
class ViewController: UIViewController {
#IBOutlet var menuBarView: UIView!
var pageControllerVC: PageControllerVC?
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as? PageControllerVC else {
fatalError("Could not instantiate PageControllerVC!!!")
}
guard let v = vc.view else {
fatalError("loaded PageControllerVC had no view ????")
}
addChild(vc)
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: menuBarView.bottomAnchor),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor),
v.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
vc.didMove(toParent: self)
self.pageControllerVC = vc
}
}
You should remove safeArea pinning for pageVC.
Safe area includes status bar and iPhone 11+ top space
tabBar.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
// to this
tabBar.topAnchor.constraint(equalTo: self.view.topAnchor)
And in storyboards change Safe Area to SuperView

Changing width of child view controller

I added child view controller to parent view controller programmatically in swift 3.0.
But I do not want the child view controller width as full screen, I want to customise the width and height of the child view controller. I tried to open the custom size child view controller, but it is not working.
// Here is my code
let secondViewController = storyboard.instantiateViewController(withIdentifier: storyBoardName)
secondViewController.modalPresentationStyle = UIModalPresentationStyle.custom
secondViewController.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width-500, height: self.view.bounds.height)
self.present(secondViewController, animated: false, completion: nil)
Is there a way to achieve this?
In your code, you are not adding the secondViewController as childview controller. You are presenting that view controller. There is a difference.
You are trying to use UIModalPresentationStyle.custom to present viewcontroller using custom style. But using this custom style is not this trivial.
From documentation:
Creating a custom style involves
subclassing UIPresentationController and using its methods to animate
any custom view onto the screen and to set the size and position of
the presented view controller. The presentation controller also
handles any adaptations that occur because of changes to the presented
view controller’s traits
It tells you that you need to subclass UIPresentationController and subclass its method(with other methods) - (CGRect)frameOfPresentedViewInContainerView to set the frame of the presented viewcontroller.
More is explained in this link.
You can ofcourse achieve all this by actually adding a childviewcontroller.
Steps would be like this:
Create your child viewcontroller instance.
Set its view frame to whatever you want.
Add its view as a subview on to parent view controller's view using addSubview:
Call [addChildViewController] on parent viewcontrller (https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchildviewcontroller)
It depends on what you're trying to do, when I want to show some UI on top of another UIViewController I usually use a fullscreen view controller with self.modalPresentationStyle = .overFullScreen. Then I create another view which has the visible size I actually want to show to the user. This allows you to do pretty much anything you want.
But if you want an actual child viewcontroller, you need to use the appropriate functions for it.

How can I change the color/put things behind my ViewControlller's view (swift)?

First, an image:
I made it so you can drag the view controller view with your finger (which I have already done), but I want to know:
How to change the black color to another color
How I can put an image behind the view (I want to make it so if you drag the view you'll see a picture).
How do I do this? I figure I'll need to place another view directly behind this one maybe and then make make current view controlller a sub view?
You can set an image as the background of your ViewController by either changing the class of your ViewController's main view from UIView to UIImageView in Storyboard and setting the image to that ImageView's image property or by adding a UIImageView to the ViewController that has the same size as view.
By "background color", I think you mean the black color that shows when you drag the VC away, right?
That is the color of the UIWindow that you VC is running in. It's kind of the superview of every view.
To change the windows color, simply go to your AppDelegate.swift and change change it:
window?.backgroundColor = .red
To add a background image, you just need to add a UIImageView as a subview of window.
if let window = self.window
let imageView = UIImageView(frame: window.frame)
imageView.image = ...
window.addSubview(imageView)
}
If you don't want to deal with subviews and only use viewcontrollers, you can try to present a draggable viewcontroller over a normal (fixed position) viewcontroller. You can do it like this (call this code from the normal view controller to present the draggable view controller over itself):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// Set the draggable controller's identifier in Main.storyboard
let dragController = storyboard.instantiateViewController(withIdentifier: "DragController")
// Present the draggable view controller over the current one
dragController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
dragController.modalTransitionStyle = UIModalTransitionStyle.coverVertical
self.present(dragController, animated: true, completion: nil)
Use this code, and then set the background image of the normal view controller. Hope this helps :)

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

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.

Black bar flashes at top of UITableView when pushing to view with "Hides Bottom Bar When Pushed" in IB

This is a weird error that may just be an issue in Xcode for all I know. I have a tab bar controller where the first view is a UITableView with (obviously) a number of cells. When you select a cell, I've set up a segue on the MainStoryboard to go to a detail view controller. I want the tab bar to be hidden when I go to the detail view, so I went into the storyboard, chose my detail view, and clicked "Hides Bottom Bar on Push" in the editor screen that starts with "Simulated Metrics."
Everything works just fine, except that when I tap on a cell, a black bar flashes at the top of the UITableView screen, dropping the tableview cells down (as if the cells are falling down below the tab bar at the bottom), just before the screen pushes over to the detail view. The effect isn't harmful at all, but it's very disconcerting, and I'd like to smooth that out.
The only fix I've found is to uncheck the "Hides Bottom Bar when Pushed" option on the storyboard. That indeed does get rid of that black bar flash, but of course the tab bar stays on the screen when I go to the detail view, which is what I don't want.
Any ideas?
Just for completeness' sake, I went ahead and ran
[self.navigationController setToolbarHidden:YES animated: YES];
on the detail view controller's viewWillAppear method (and even tried it with the storyboard option both on and off), but there was no difference. The toolbar did indeed hide just fine, but I still got that black line at the top. So weird.
I know it is too late !!! I ran into same issue. It seems like the Auto resizing mask for the view was incorrect to be exact the UIViewAutoresizingFlexibleTopMargin. I checked this on in the xib file. If you are trying to do it in code make sure this flag -UIViewAutoresizingFlexibleTopMargin - is not included in the autoresizing mask.
Hope this will help some one in the future
I know it is a bit late, but I have same problem and I can't solve it with any of the previous answers. (I suppose this is the reason non was accepted).
The problem is that view size of the SecondViewController is same as view size of a previous ViewController, so too small to fit in a ViewController with Toolbar hidden. Thats why black background of a UITabBarController is visible at the top when transition is happening, and on a viewDidAppear view will stretch on right size.
For me it help to subclass root UITabBarController and set background color to same background color as SecondViewController has.
class RootViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Style.backgroundColor
}
}
Then you can leave checkbox checked inside storyboard and it will look ok.
P.S.
If you have some views, that is position on the bottom part of the view, you need to set bottom constraints so they are smaller by 49 (because this is the height of the toolbar), and then on viewDidAppear set the right constraint.
For example:
I have view that need to be position 44 px from bottom edge. Before, I have constraint set to 44 and I have some strange behaviour of that view. It was placed to height and then jump on the right place.
I fix this with setting constraint to -5 (44-49), and then in viewDidAppear set the constraint back to 44. Now I have normal behaviour of that view.
Wow I just had the same issue now, very painful, and no info on the net about it.
Anyway, a simple workaround for me was to change the current view's Frame moving the y coordinates up and making the height bigger by the height of the tab bar. This fixed the problem if done straight after pushing the new view onto the navigation controller. Also, there was no need to fix the Frame afterwards (it must be updated when the view is shown again).
MonoTouch code:
UIViewController viewControllerToPush = new MyViewController();
viewControllerToPush.HidesBottomBarWhenPushed = true; // I had this in the MyViewController's constructor, doesn't make any difference
this.NavigationController.PushViewController(viewControllerToPush, true);
float offset = this.TabBarController.TabBar.Frame.Height;
this.View.Frame = new System.Drawing.RectangleF(0, -offset, this.View.Frame.Width, this.View.Frame.Height + offset);
Objective C code (untested, just a translation of the monotouch code):
UIViewController *viewControllerToPush = [MyViewController new];
viewControllerToPush.hidesBottomBarWhenPushed = YES; viewControllerToPush.hidesBottomBarWhenPushed = YES;
float offset = self.tabBarController.tabBar.frame.size.height; float offset = self.tabBarController.tabBar.frame.size.height;
self.view.frame = CGRectMake(0, -offset, self.view.frame.width, self.view.frame.height + offset); self.view.frame = CGRectMake(0, -offset, self.view.frame.size.width, self.view.frame.size.height + offset);
Do this in viewWillAppear of detailViewController, it should work fine
subclass your navigation controller, or just find the navigation bar
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let backdropEffectView = navigationBar.subviews[0].subviews[0].subviews[0] //_UIBackdropEffectView
let visualEffectView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = backdropEffectView.frame
backdropEffectView.superview?.insertSubview(visualEffectView, aboveSubview: backdropEffectView)
backdropEffectView.removeFromSuperview()
}

Resources