210

I want to display, above any other views, even the navigation bar, a kind of "pop-up" view that looks like this:

  • full screen black background with a 0.5 alpha to see the other UIViewController underneath.
  • a UIView window in the middle with some information, (a calendar if you want to know everything).

To do that, I've created a UIViewController that contains the two UIViews (background and window), and I'm trying to display it. I've tried a simple [mySuperVC addSubview:myPopUpVC.view], but I still have the navigation bar above.

I've tried to present it as a modal, but the UIViewController underneath disappears, and I lose my transparency effect.

Any idea to do this, I'm sure it's quite simple...

Thanks!

Naresh
  • 16,698
  • 6
  • 112
  • 113
Nicolas Roy
  • 3,773
  • 5
  • 27
  • 42
  • Add a subview on your application window. Handling orientation changes can be tricky: http://stackoverflow.com/questions/8774495/view-on-top-of-everything-uiwindow-subview-vs-uiviewcontroller-subview – Amar Feb 18 '14 at 10:16

17 Answers17

221

You can do that by adding your view directly to the keyWindow:

UIView *myView = /* <- Your custom view */;
UIWindow *currentWindow = [UIApplication sharedApplication].keyWindow;
[currentWindow addSubview:myView];

UPDATE -- For Swift 4.1 and above

let currentWindow: UIWindow? = UIApplication.shared.keyWindow
currentWindow?.addSubview(myView)

UPDATE for iOS13 and above

keyWindow is deprecated. You should use the following:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.addSubview(myView)
laka
  • 644
  • 8
  • 23
Rom.
  • 2,800
  • 2
  • 17
  • 19
  • 2
    Thanks :) Unfortunately if you are in landscape mode you need to rotate the view. I'm not sure I know how to do that properly. – Nicolas Roy Feb 18 '14 at 10:43
  • Yes, I'm only in landscape mode and myView appears with a 90 degrees rotation. Seems to be what they're talking about here : http://stackoverflow.com/questions/8774495/view-on-top-of-everything-uiwindow-subview-vs-uiviewcontroller-subview – Nicolas Roy Feb 18 '14 at 10:54
  • The orientation problem happens on pre-iOS8 devices, for which this solution sadly doesn't work. – gchbib Dec 01 '14 at 09:16
  • @NicolasRoy Autolayout will help – Darmen Amanbay Feb 18 '15 at 09:24
  • Adding to the window will make it appear in all your view controllers. So if you want it on a specific view controller add your view to the Navigation Bar itself as @dalef says below: [self.navigationController.view addSubview:overlayView]; – Marcos Reboucas Jun 17 '15 at 16:31
  • When I try this, it shows up on the screen but quickly goes away when the screen loads. I tried adding it to viewDidLoad, viewDidAppear, and viewWillAppear with the same results. – Orlando Jul 09 '15 at 18:26
  • 7
    it doesn't cover tab bar. Any solution for that? – Manisha Sep 16 '16 at 18:01
  • It also does not covers the status bar – Ishika Jun 15 '20 at 07:13
142

[self.navigationController.view addSubview:overlayView]; is what you really want

dalef
  • 1,941
  • 1
  • 13
  • 18
  • 5
    You need to set the frame – Gabriel Goncalves Mar 27 '15 at 22:11
  • 2
    This does not work with a child view controller's view. Changing the navigation bar's z-level (as Nam suggests) works also with child view controllers. – Tapani Nov 06 '15 at 12:23
  • It looks to ma more elegant solution than adding view to Key window. As this ensures added view, moves along the animation of base view. Because if view is added on key window it doesnt come with animation and has to be removed seperately if you want to dismiss the controller. – soan saini Sep 04 '18 at 23:46
122

Here is a simple elegant solution that is working for me. You can set the z position of the navigation bar to below the view:

self.navigationController.navigationBar.layer.zPosition = -1;

Just remember to set it back to 0 when done.

Nam
  • 1,856
  • 2
  • 13
  • 18
  • Works great for me too. It would be great to mention that zPosition should be set back to value 0 (you just mention that It should be set back when done). And I have tabBar (from TabBarController in my screen too. I tried to set zPosition but when returning to 0 value tabBar wasn't still visible. So for tabBar it's better to use isHidden property. – Libor Zapletal Jan 26 '17 at 10:53
  • 1
    Don't work with accessibility. Panning over the view won't focus it. – antonjn Aug 22 '18 at 19:38
  • This sets the navigation controller behind everything though, making it invisible..? – Eric Nov 13 '22 at 00:22
  • Is added view is UIImageview and we apply pinch zoom In it , even setting zPosition , the navigation bar button stops working – DSRawat Nov 16 '22 at 12:30
61

Swift versions for the checked response :

Swift 4 :

let view = UIView()
view.frame = UIApplication.shared.keyWindow!.frame
UIApplication.shared.keyWindow!.addSubview(view)

Swift 3.1 :

let view = UIView()
view.frame = UIApplication.sharedApplication().keyWindow!.frame 
UIApplication.sharedApplication().keyWindow!.addSubview(view)
Elijah
  • 8,381
  • 2
  • 55
  • 49
Kevin ABRIOUX
  • 16,507
  • 12
  • 93
  • 99
27

Dalef great solution in swift:

self.navigationController?.view.addSubview(view)
Allreadyhome
  • 1,252
  • 2
  • 25
  • 46
24

Add you view as the subview of NavigationController.

[self.navigationController.navigationBar addSubview: overlayView)]

You can also add it over the window:

UIView *view = /* Your custom view */;
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[window addSubview:view];

Hope this helps.. :)

Rashad
  • 11,057
  • 4
  • 45
  • 73
  • Over view is coming but, when add a button couldn't get button action or set custom label text on the overView – Vineesh TP Feb 18 '16 at 15:20
18

@Nam's answer works great if you just want to display your custom view but if your custom view needs user interaction you need to disable interaction for the navigationBar.

self.navigationController.navigationBar.layer.zPosition = -1
self.navigationController.navigationBar.isUserInteractionEnabled = false

Like said in Nam's answer don't forget to reverse these changes:

self.navigationController.navigationBar.layer.zPosition = 0
self.navigationController.navigationBar.isUserInteractionEnabled = true


You can do this in a better way with an extension:
extension UINavigationBar {
    func toggle() {
        if self.layer.zPosition == -1 {
            self.layer.zPosition = 0
            self.isUserInteractionEnabled = true
        } else {
            self.layer.zPosition = -1
            self.isUserInteractionEnabled = false
        }
    }
}

And simply use it like this:

self.navigationController.navigationBar.toggle()
Hemang
  • 26,840
  • 19
  • 119
  • 186
8

Swift version of @Nicolas Bonnet 's answer:

    var popupWindow: UIWindow?

func showViewController(controller: UIViewController) {
    self.popupWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
    controller.view.frame = self.popupWindow!.bounds
    self.popupWindow!.rootViewController = controller
    self.popupWindow!.makeKeyAndVisible()
}

func viewControllerDidRemove() {
    self.popupWindow?.removeFromSuperview()
    self.popupWindow = nil
}

Don't forget that the window must be a strong property, because the original answer leads to an immediate deallocation of the window

iOS Unit
  • 218
  • 4
  • 8
6

I recommend you to create a new UIWindow:

UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = viewController;
window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
window.opaque = NO;
window.windowLevel = UIWindowLevelCFShareCircle;
window.backgroundColor = [UIColor clearColor];

[window makeKeyAndVisible];

Then you can manage your view in an other UIViewController. To remove the windows:

[window removeFromSuperview];
window = nil;

hope that will help!

Nicolas Bonnet
  • 1,275
  • 11
  • 15
6

There is more than one way to do it:

1- Add your UIView on UIWindow instead of adding it on UIViewController. This way it will be on top of everything.

   [[(AppDelegate *)[UIApplication sharedApplication].delegate window] addSubview:view];

2- Use custom transition that will keep your UIViewController showing in the back with a 0.5 alpha

For that I recommend you look at this: https://github.com/Citrrus/BlurryModalSegue

Dani
  • 1,228
  • 1
  • 16
  • 26
4
[[UIApplication sharedApplication].windows.lastObject addSubview:myView];
Aamir
  • 16,329
  • 10
  • 59
  • 65
jold
  • 152
  • 1
  • 4
3

In Swift 4.2 and Xcode 10

var spinnerView: UIView? //This is your view

spinnerView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
//Based on your requirement change width and height like self.view.bounds.size.width
spinnerView?.backgroundColor = UIColor.black.withAlphaComponent(0.6)
//        self.view.addSubview(spinnerView)
let currentWindow: UIWindow? = UIApplication.shared.keyWindow
currentWindow?.addSubview(spinnerView!)

In Objective C

UIView *spinnerView;//This is your view

self.spinnerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height)];  
//Based on your requirement change width and height like self.view.bounds.size.width
self.spinnerView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
// [self.view addSubview:self.spinnerView];
UIWindow *currentWindow = [UIApplication sharedApplication].keyWindow;
[currentWindow addSubview:self.spinnerView];

This can work either Portrait OR Landscape mode.

One more simple code is:

yourViewName.layer.zPosition = 1//Change your view name
Naresh
  • 16,698
  • 6
  • 112
  • 113
2

Note if you want add view in Full screen then only use below code

Add these extension of UIViewController

public extension UIViewController {
   internal func makeViewAsFullScreen() {
      var viewFrame:CGRect = self.view.frame
      if viewFrame.origin.y > 0 || viewFrame.origin.x > 0 {
        self.view.frame = UIScreen.main.bounds
      }
   }
}

Continue as normal adding process of subview

Now use in adding UIViewController's viewDidAppear

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

     self.makeViewAsFullScreen()
}
Paresh Navadiya
  • 38,095
  • 11
  • 81
  • 132
1

I'd use a UIViewController subclass containing a "Container View" that embeds your others view controllers. You'll then be able to add the navigation controller inside the Container View (using the embed relationship segue for example).

See Implementing a Container View Controller

Dulgan
  • 6,674
  • 3
  • 41
  • 46
1
UIApplication.shared.keyWindow?.insertSubview(yourView, at: 1)

This method works with xcode 9.4 , iOS 11.4

Ahmed R.
  • 1,209
  • 1
  • 12
  • 22
0
[self.navigationController.navigationBar.layer setZPosition:-0.1];
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(10, 20, 35, 35)];
[view setBackgroundColor:[UIColor redColor]];
[self.navigationController.view addSubview:view];
[self.navigationController.view bringSubviewToFront:view];
self.navigationController.view.clipsToBounds = NO;
[self.navigationController.navigationBar.layer setZPosition:0.0];

enter image description here

Fadi Abuzant
  • 476
  • 8
  • 13
0

You need to add a subview to the first window with the UITextEffectsWindow type. To the first, because custom keyboards add their UITextEffectsWindow, and if you add a subview to it this won't work correctly. Also, you cannot add a subview to the last window because the keyboard, for example, is also a window and you can`t present from the keyboard window. So the best solution I found is to add subview (or even push view controller) to the first window with UITextEffectsWindow type, this window covers accessory view, navbar - everything.

let myView = MyView()
myView.frame = UIScreen.main.bounds

guard let textEffectsWindow = NSClassFromString("UITextEffectsWindow") else { return }
let window = UIApplication.shared.windows.first { window in
    window.isKind(of: textEffectsWindow)
}
window?.rootViewController?.view.addSubview(myView)
Viktoria
  • 119
  • 6