My question should be simple but I couldn't find solution.
I am displaying modal VC and I want to append dim background view.
Modal VC is added as a child to the parent VC. Parent VC is placed at top of Navigation Controller (topViewController)
Dim view is added to the UIScreen.windows.first! window. But the dim view doesn't fill the whole screen. I do set frame for it with CGPointZero for origin, I tried negative y value it didn't help. It just look like this.
Also I added set of auto layout constraints, set to false/true translatesAutoresizingMaskIntoConstraints property, changed clipToBounds.
Nothing helps, the dim view doesn't cover entire screen.
I can display the dim view in full screen, if I don't add modal VC as a child, i.e. I directly utilize modal VC's view, and add it to the window. It looks fine, but there is another bug. That's why I am adding modal VC, as a child VC.
How to force dim view display the whole screen.
In the app there might be several modal VC displayed simultaneously, that's why seques, (i.e presentation of VCs) is not the best choice.
P.S. Here is description of the bug :
. In case of adding modal VC (UITableVC placed into Nav. Controller)'s view directly to the window , view displays shifted table, after returning back from BG, i.e. in case of being in BG and starting to display modal VC from BG, after that switching back to Foreground, part of table's header (Question is revealed part) is shifted up and modal VC's navigation bar is expanded, i.e. they intersect, header of the table isn't display completely. I couldn't fix that.
Swift 4 Update
let dimView = UIView(frame: UIScreen.main.bounds)
dimView.backgroundColor = UIColor(white: 0.4, alpha: 0.5)
self.navigationController!.view.addSubview(dimView)
self.navigationController!.view.bringSubviewToFront(dimView)
Put your view under navigation controller's view:
UIView *dimView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
dimView.backgroundColor = [UIColor colorWithWhite:.4f alpha:.5f];
[self.navigationController.view addSubview:dimView];
[self.navigationController.view bringSubviewToFront:dimView];
You can add the view and make it dim as well and add it as a sub view to UINavigationController , status Bar as below :
// Dim navigation bar
let navDimView = UIView(frame: (self.navigationController?.navigationBar.bounds)!)
navDimView.restorationIdentifier = "navDimView"
navDimView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
self.navigationController?.navigationBar.addSubview(navDimView)
// Dim status bar
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let wFrame = window?.windowScene?.statusBarManager?.statusBarFrame
let statusBarView = UIView(frame: wFrame!)
statusBarView.restorationIdentifier = "statusBarView"
statusBarView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
window?.addSubview(statusBarView)
If you want to revert it back you can do as below:
// Revert navigation bar back to normal
for subview in self.navigationController!.navigationBar.subviews {
if subview.restorationIdentifier == "navDimView" {
subview.removeFromSuperview()
}
}
// Revert status bar back to normal
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
for subview in window!.subviews {
if subview.restorationIdentifier == "statusBarView" {
subview.removeFromSuperview()
}
}
Related
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
I've created the UI of a modal that pops up when you press a Tab Bar Item within a Tab Bar Controller. This is the first time that I've accomplished this programmatically, so there may be something that I'm missing here, but I can't seem to change the background color of the modal at all. More specifically, I'm trying to make the background transparent, but it appears in black when the modal is presented, no matter what color I change it to. I'm not sure that it matters that I've programmatically added subviews to the main view (for example, a UIView called "titleContainer"), but want to note this here just in case.
Below is part of what I have in my code:
override func loadView() {
view = UIView()
view.backgroundColor = UIColor.clear
view.isOpaque = false
titleContainer = UIView(frame: CGRect(x: 0, y: 0, width: 414, height: 180))
titleContainer.layer.borderWidth = 1.0
titleContainer.layer.borderColor = UIColor.darkGray.cgColor
titleContainer.layer.backgroundColor = UIColor.black.cgColor
titleContainer.layer.cornerRadius = 12
titleContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
titleContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleContainer)
NSLayoutConstraint.activate([
titleContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
titleContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
titleContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
titleContainer.heightAnchor.constraint(equalToConstant: 180),
])
}
UPDATE: In addition to the fact that the original code was adding a color to the layer of the UIView, the modal was also sub-classing UITabBarController and not UIViewController, hence why the background color of the modal seemed to be inheriting that of the first VC in the Tab Bar Controller. Changing the superclass to UIViewController ultimately resolved this issue.
You're adding a color to the layer of the UIView. Remove the following line to get the view's background color. Or you can update the layer's color instead.
titleContainer.layer.backgroundColor = UIColor.black.cgColor
To set the color of the previous ViewController's view background you could set it while transitioning as follows:
viewController.titleContainer.layer.backgroundColor = view.backgroundColor?.cgColor
So your solution is correct the subclassing of your modal controller. In the future, you may want to double check the parent class of your subclasses especially when something off is happening like in your case.
And secondly, another way to help you debug the application is to toggle the attributes of your controller, say. a background color, and if you change your first tab screen's bg color, and the modal copies the color, then you can get to a theory that your modal is somehow getting that attribute from the first tab screen.
Lastly, utilize the Xcode's debugging features. One of those is Debug View Hierarchy. - This will give you the hierarchies of your views in 3-dimensional perspective.
Make sure to select overCurrentContext as modalPresentation for the vc
vc.modalPresentation = overCurrentContext
// present the vc
vc.bColor = UIColor.red
and declare this inside the vc
var bColor:UIColor!
Then set it here
titleContainer.layer.backgroundColor = bColor
Make you super view color to clear for your presenter view controller
self.view.backgroundColor = .clear
Than write this where ever you want to present your view controller
let controllerObject = yourControllor object from the storyboard
controllerObject.modalPresentationStyle = .overFullScreen
controllerObject.modalTransitionStyle = .coverVertical
present(controllerOject, animated: true, completion: nil)
I want to ignore navigation bar and make full size view in navigation controller.
But the view is shown under the navigation bar.
Could I overlap navigation bar with black view?
I want to make like this picture
To achieve the given UI you should add the top view on window.To do so, First make an xib of top view. then add given code:
let frame = UIApplication.shared.keyWindow?.frame
let wrapper = UIView(frame: frame!)
wrapper.backgroundColor = UIColor.black.withAlphaComponent(0.35)
let objView = YourView() // Create your view object here.
objView.frame = wrapper.frame
objView.center = wrapper.center
wrapper.addSubview(objView)
UIApplication.shared.keyWindow?.addSubview(wrapper)
Set frame of YourView according to your requirement. wrapper makes your view transparent. You can make single view without wrapper. Use same code to add your view on window.
I have a collection View with navigation bar on StoryBoard. There is a popupView that appears when custom cell is selected.
On main StoryBoard I set a View dimView to make dark background color while pop up view is showing.
However when I test on device, dimView does not go above Navigation Bar or TabBar.
I tried following code let window = UIApplication.shared.keyWindow! window.addSubview(dimView). But it makes dimView above pop up View.
I would like to set a view above navigation controller and below popupView.
Any idea how to solve this?
Have You try adding dimview on window but below popUp like this
let window = UIApplication.shared.keyWindow!
window.insertSubview(dimView, belowSubview: popupView)
I hide my tab bar like so:
self.tabBarController.tabBar.hidden = YES;
And because now there is a black bar where it once stood I stretch the view which is a UIWebView on top(or is it under?) that empty space. The UIWebView is in a UIViewController. I do that with a constraint which by default is like so:
The code for the constraint:
if(self.tabBarController.tabBar.hidden){
self.webviewBottomConstrain.constant = -self.tabBarController.tabBar.frame.size.height;
}else{
self.webviewBottomConstrain.constant = 0;
}
However if I tap the device on the place where the TabBar was it will not execute. It is as if there is something invisible there with the size of the tab bar. I have also tried hiding it the way this thread sugests. Still the same result.
Update: It seems that when you tap on the invisible tab bar the tap is recognized by the tab bar and not by the view that is visible under the tab bar
self.extendedLayoutIncludesOpaqueBars = YES;
this will solve you problem
You hide your tabBar by setting its hidden property to NO? Try setting it to YES. Unless I am misunderstanding what you are trying to do, it seems like your tab bar is not hidden with that code.
Another thing I would check is to see if User Interaction Enabled is checked for the web view. If it is not, that can seem like there is something invisible blocking you from interacting with your view.
Well I am using quite ugly hack to fix this. I am hiding the tab bar in another way now:
if (shouldShow) {
self.hidesBottomBarWhenPushed = NO;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
} else if (shouldHide) {
self.hidesBottomBarWhenPushed = YES;
self.tabBarController.hidesBottomBarWhenPushed = YES;
self.navigationController.hidesBottomBarWhenPushed = YES;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
}
I do need that random view because I cannot push the view on itself.
I had the same issue when hiding the tab bar by moving it offscreen to the bottom. My custom UITabBarViewController was intercepting the touch events in the area vacated by the tab bar, so instead of changing the frame of the tab bar to move the tab bar offscreen, I extended the height of my tab bar view controller so that the tab bar still moved offscreen, but the child view above the tab bar now filled that space. This allowed the touches to be received by the child view.
As you may see with view hierarchy instrument, UITabBar is not directly blocking your tap, but your current view controller's view height is not full screen:
So, the tap doesn't response because your finger's y position is higher than view's maxY.
Code like this (inside your UITabBarController) will expand your view's height, according to tabbar visibility, and all tap events will work correctly.
func updateTabBarAppearanceWithDegree(_ degree: CGFloat) {
let screenHeight = UIScreen.main.bounds.size.height
let tabBarHeight = self.tabBar.frame.size.height
self.tabBar.frame.origin.y = screenHeight - tabBarHeight * degree
self.tabBar.alpha = degree
let currentNavigation = self.selectedViewController as? UINavigationController
if let currentTopView = currentNavigation?.viewControllers.last?.view {
currentTopView.frame.size.height = self.tabBar.frame.origin.y
}
}