How to tell if UIViewController's view is visible
Asked Answered
I

18

637

I have a tab bar application, with many views. Is there a way to know if a particular UIViewController is currently visible from within the UIViewController? (looking for a property)

Incudes answered 5/5, 2010 at 23:25 Comment(1)
Related: Getting the top most UIViewControllerRasure
A
1217

The view's window property is non-nil if a view is currently visible, so check the main view in the view controller:

Invoking the view method causes the view to load (if it is not loaded) which is unnecessary and may be undesirable. It would be better to check first to see if it is already loaded. I've added the call to isViewLoaded to avoid this problem.

if (viewController.isViewLoaded && viewController.view.window) {
    // viewController is visible
}

Since iOS9 it has became easier:

if viewController.viewIfLoaded?.window != nil {
    // viewController is visible
}

Or if you have a UINavigationController managing the view controllers, you could check its visibleViewController property instead.

Alcaic answered 5/5, 2010 at 23:35 Comment(11)
The one issue with a UINavigationController's visibleViewControllee property is the case where your visibleViewController presents a modal view controller. In that case, the modal view becomes the visibleViewController, which may be undesirable. How would you handle that?Dogfish
This is probably obvious to everyone, but for me the code had to be self.isViewLoaded && self.view.windowAtticism
Note that in iOS 7, a navigation controller doesn't load a pushed view controller into memory right away. This is different from iOS 6 where you could push a view controller and immediately see that navController.topViewController.isViewLoaded was true.Trematode
Be careful in generalizing this solution to other situations. For example, if you're using a UIPageViewController, views for UIViewControllers who are not the current page may still have a non-nil window property because they are being rendered off-screen. In this case, I've had success making my own 'isCurrentlyVisible' property which gets set in viewDidAppear and viewDidDisappear.Oblast
@Dogfish in that case, use topViewController.Tenpenny
Swift way: if (self.isViewLoaded() && self.view.window != nil ) {Greed
Please note that this answer doesn't say anything about the real visibility. For example, if the app is in background above IF statement will say YES while view is not really visible.Caia
this only worked for me in viewDidAppear. When I added this to viewWillAppear self.view.window != nil always came up nilEnphytotic
does viewController.viewIfLoaded?.window work when you're using a scene manifest (UISceneSession)?Plano
Does not work correctly for custom containers, like ParchmentSharpeyed
Thanks. My music app share AVAudioPlayer playing state on a container view and presented VC, when user take on/off their Airpod, I want to change state only on the visible one.Dipper
T
91

Here's @progrmr's solution as a UIViewController category:

// UIViewController+Additions.h

@interface UIViewController (Additions)

- (BOOL)isVisible;

@end


// UIViewController+Additions.m

#import "UIViewController+Additions.h"

@implementation UIViewController (Additions)

- (BOOL)isVisible {
    return [self isViewLoaded] && self.view.window;
}

@end
Tenpenny answered 30/6, 2011 at 3:58 Comment(0)
R
57

There are a couple of issues with the above solutions. If you are using, for example, a UISplitViewController, the master view will always return true for

if(viewController.isViewLoaded && viewController.view.window) {
    //Always true for master view in split view controller
}

Instead, take this simple approach which seems to work well in most, if not all cases:

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    //We are now invisible
    self.visible = false;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    //We are now visible
    self.visible = true;
}
Ribosome answered 4/2, 2014 at 14:37 Comment(1)
Is this still true in xCode 7.1.1? The master in my UISplitViewController is returning NO for viewController.view.window. I may be doing something wrong, but I am pretty sure this is the case.Interclavicle
P
46

For those of you looking for a Swift 2.2 version of the answer:

if self.isViewLoaded() && (self.view.window != nil) {
     // viewController is visible
}

and Swift 3:

if self.isViewLoaded && (self.view.window != nil) {
         // viewController is visible
}
Prink answered 4/4, 2016 at 15:3 Comment(2)
Not sure why but I found that doing self.view.window != nil causes it to never work even when self.isViewLoaded is true. Once removed, it works fine.Ceiling
this only worked for me in viewDidAppear. When I added this to viewWillAppear self.view.window != nil always came up nilEnphytotic
C
35

For over-full-screen or over-context modal presentation, "is visible" could mean it is on top of the view controller stack or just visible but covered by another view controller.

To check if the view controller "is the top view controller" is quite different from "is visible", you should check the view controller's navigation controller's view controller stack.

I wrote a piece of code to solve this problem:

extension UIViewController {
    public var isVisible: Bool {
        if isViewLoaded {
            return view.window != nil
        }
        return false
    }

    public var isTopViewController: Bool {
        if self.navigationController != nil {
            return self.navigationController?.visibleViewController === self
        } else if self.tabBarController != nil {
            return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
        } else {
            return self.presentedViewController == nil && self.isVisible
        }
    }
}
Cattycornered answered 11/12, 2014 at 5:13 Comment(1)
Nice post! FYI isViewLoaded is a property since Swift 3.0.Martell
T
28

You want to use the UITabBarController's selectedViewController property. All view controllers attached to a tab bar controller have a tabBarController property set, so you can, from within any of the view controllers' code:

if([[[self tabBarController] selectedViewController] isEqual:self]){
     //we're in the active controller
}else{
     //we are not
}
Tephrite answered 5/5, 2010 at 23:36 Comment(3)
This doesn't work if the view controller is contained inside a navigation controller and that controller is added to the tab bar controller. The call to selectedViewController will return the navigation controller and not the current view controller.Hanleigh
@AntonHolmberg in that case, get the visible view controller like this: ((UINavigationController *)self.tabBarController.selectedViewController).visibleViewControllerTenpenny
Or even use 'self.tabBarController.selectedIndex' property if we've gone this far.Highroad
H
14

I made a swift extension based on @progrmr's answer.

It allows you to easily check if a UIViewController is on screen like so:

if someViewController.isOnScreen {
    // Do stuff here
}

The extension:

//
//  UIViewControllerExtension.swift
//

import UIKit

extension UIViewController{
    var isOnScreen: Bool{
        return self.isViewLoaded() && view.window != nil
    }
}
Hydrus answered 20/5, 2015 at 8:36 Comment(0)
M
7

For my purposes, in the context of a container view controller, I've found that

- (BOOL)isVisible {
    return (self.isViewLoaded && self.view.window && self.parentViewController != nil);
}

works well.

Mesoblast answered 18/6, 2014 at 15:27 Comment(0)
P
6

I use this small extension in Swift 5, which keeps it simple and easy to check for any object that is member of UIView.

extension UIView {
    var isVisible: Bool {
        guard let _ = self.window else {
            return false
        }
        return true
    }
}

Then, I just use it as a simple if statement check...

if myView.isVisible {
    // do something
}

I hope it helps! :)

Paunchy answered 31/8, 2019 at 2:30 Comment(0)
D
5

Good point that view is appeared if it's already in window hierarchy stack. thus we can extend our classes for this functionality.

extension UIViewController {
  var isViewAppeared: Bool { viewIfLoaded?.isAppeared == true }
}

extension UIView {
  var isAppeared: Bool { window != nil }
}
Delude answered 25/9, 2020 at 12:47 Comment(0)
K
4

I found those function in UIViewController.h.

/*
  These four methods can be used in a view controller's appearance callbacks to determine if it is being
  presented, dismissed, or added or removed as a child view controller. For example, a view controller can
  check if it is disappearing because it was dismissed or popped by asking itself in its viewWillDisappear:
  method by checking the expression ([self isBeingDismissed] || [self isMovingFromParentViewController]).
*/

- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);

- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);

Maybe the above functions can detect the ViewController is appeared or not.

Kikuyu answered 21/5, 2015 at 7:2 Comment(0)
T
4

XCode 6.4, for iOS 8.4, ARC enabled

Obviously lots of ways of doing it. The one that has worked for me is the following...

@property(nonatomic, readonly, getter=isKeyWindow) BOOL keyWindow

This can be used in any view controller in the following way,

[self.view.window isKeyWindow]

If you call this property in -(void)viewDidLoad you get 0, then if you call this after -(void)viewDidAppear:(BOOL)animated you get 1.

Hope this helps someone. Thanks! Cheers.

Tarsuss answered 23/7, 2015 at 21:8 Comment(0)
R
3

if you're utilizing a UINavigationController and also want to handle modal views, the following is what i use:

#import <objc/runtime.h>

UIViewController* topMostController = self.navigationController.visibleViewController;
if([[NSString stringWithFormat:@"%s", class_getName([topMostController class])] isEqualToString:@"NAME_OF_CONTROLLER_YOURE_CHECKING_IN"]) {
    //is topmost visible view controller
}
Rickirickie answered 5/10, 2013 at 23:38 Comment(1)
I have found this way to be more reliable than the accepted answer, when a navigation controller is available. This can be shortened to:if ([self.navigationController.visibleViewController isKindOfClass:[self class]]) {Alloplasm
W
3

The approach that I used for a modal presented view controller was to check the class of the presented controller. If the presented view controller was ViewController2 then I would execute some code.

UIViewController *vc = [self presentedViewController];

if ([vc isKindOfClass:[ViewController2 class]]) {
    NSLog(@"this is VC2");
}
Womanlike answered 26/1, 2014 at 22:19 Comment(0)
K
3

If you are using a navigation controller and just want to know if you are in the active and topmost controller, then use:

if navigationController?.topViewController == self {
    // Do something
}

This answer is based on @mattdipasquale's comment.

If you have a more complicated scenario, see the other answers above.

Kibosh answered 25/7, 2016 at 17:27 Comment(1)
this will never be called if the app goes in background and then in foreground. I am looking for a solution where I can check if the view controller is visible to the user or not. User could background the app for a few days and when it comes back in foreground, I would like to update UI. Please let me know if you can help.Implore
F
2

you can check it by window property

if(viewController.view.window){

// view visible

}else{

// no visible

}
Flummery answered 17/7, 2013 at 10:1 Comment(0)
D
1

I needed this to check if the view controller is the current viewed controller, I did it via checking if there's any presented view controller or pushed through the navigator, I'm posting it in case anyone needed such a solution:

if presentedViewController != nil || navigationController?.topViewController != self {
      //Viewcontroller isn't viewed
}else{
     // Now your viewcontroller is being viewed 
}
Dionisio answered 22/8, 2018 at 4:50 Comment(0)
K
0

Window:

window.isVisible
viewController.view.window?.isVisible ?? false

View (macOS):

extension NSViewController {
    var isOnScreen: Bool {
        return ( self.isViewLoaded && view.window != nil )
    }
}
Khamsin answered 19/12, 2022 at 2:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.