22
votes

I have seen several similar questions to this, but none that addresses my specific need. I want to be able to write a generic helper method that returns the maximum usable frame size for a UIView, taking into account whether the app has any combination of a status bar, navigation bar and/or tab bar as I find myself doing this all the time.

Method definition would be as an extension of UIScreen:

+ (CGRect) maximumUsableFrame;

Getting the size with or without the status bar can be got from the

[UIScreen mainScreen].applicationFrame

property, but I cannot figure out a way of determining if there is a navigation bar or tab bar present. I've thought about maintaining some global flags in my app delegate but this seems really clunky and stops the code being generic and re-usable. I have also considered passing a UIView as a parameter, getting the view's window, then the rootViewController and then seeing if the navigation controller property is set. If so then checking if the navigation controller is hidden. All very clunky if you ask me.

Any thoughts would be appreciated.

Dave

EDIT: Incorporating ideas from Caleb's answer in case this is of use to anyone else:

// Extension to UIViewController to return the maxiumum usable frame size for a view
@implementation UIViewController (SCLibrary)

- (CGRect) maximumUsableFrame {

    static CGFloat const kNavigationBarPortraitHeight = 44;
    static CGFloat const kNavigationBarLandscapeHeight = 34;
    static CGFloat const kToolBarHeight = 49;

    // Start with the screen size minus the status bar if present
    CGRect maxFrame = [UIScreen mainScreen].applicationFrame;

    // If the orientation is landscape left or landscape right then swap the width and height
    if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
        CGFloat temp = maxFrame.size.height;
        maxFrame.size.height = maxFrame.size.width;
        maxFrame.size.width = temp;
    }

    // Take into account if there is a navigation bar present and visible (note that if the NavigationBar may
    // not be visible at this stage in the view controller's lifecycle.  If the NavigationBar is shown/hidden
    // in the loadView then this provides an accurate result.  If the NavigationBar is shown/hidden using the
    // navigationController:willShowViewController: delegate method then this will not be accurate until the
    // viewDidAppear method is called.
    if (self.navigationController) {
        if (self.navigationController.navigationBarHidden == NO) {

            // Depending upon the orientation reduce the height accordingly
            if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
                maxFrame.size.height -= kNavigationBarLandscapeHeight;
            }
            else {
                maxFrame.size.height -= kNavigationBarPortraitHeight;
            }
        }
    }

    // Take into account if there is a toolbar present and visible
    if (self.tabBarController) {
        if (!self.tabBarController.view.hidden) maxFrame.size.height -= kToolBarHeight;
    }
    return maxFrame;
}
3
If NavigationBar and TabBar are visible, you can find heights this way:self.navigationController.navigationBar.frame.size.height; and self.tabBarController.tabBar.frame.size.height. Why variable for tabbar height called kToolBarHeight? =)Alexander
Depending how you want to use this, at least in my case, I also updated the frame origin for the navController so I knew exactly where I could position my view in the ViewController. Otherwise you could still end up under the nav bar.Bill Burgess

3 Answers

8
votes

Use applicationFrame. [[UIScreen mainScreen] applicationFrame]

This is how I use it to crop stuffs in my screenshot.

Here is some sample code.

-(UIImage*) crop20PointsifStatusBarShowsUp
{
    CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame]; //Look at this
    float sizeOfStatusBarVar= applicationFrame.origin.y;
    if ([BGMDApplicationsPointers statusBarShowUp])
    {
        CGRect newSize = CGRectMake(0, sizeOfStatusBarVar, self.size.width, self.size.height-sizeOfStatusBarVar);
        UIImage * newImage = [self cropUIImageWithCGRect:newSize];
        return newImage;
    }
    else{
        return [self copy];
    }
}
7
votes

I think you're putting your method in the wrong place. UIScreen knows about the screen, but it doesn't (and shouldn't) know anything about what's displayed on the screen. It's the view controller that's responsible for managing the view, so that's where the method belongs. Furthermore, since the max frame depends on the configuration of a particular view controller, this should be an instance method and not a class method. I think you should add a category to UIViewController with the method:

- (CGRect) maximumUsableFrame;

It's still fairly generic, available to all view controllers, but at the same time has access to view controller properties like navigationController and tabBarController.

0
votes

To get this to work had to use application bounds and orientation switch to figure out from which side the status bar is supposed to be removed. This is a copy and tweak of the originally posted code. I made a quick app that runs this algorithm through the combinations of having navbar/tabbar/none with a status bar and it worked in all the scenarios.

- (CGRect) maxFrame
{
    // Start with the screen size minus the status bar if present
    CGRect maxFrame = [UIScreen mainScreen].applicationFrame;

    // the glass screen size
    CGRect maxBounds = [UIScreen mainScreen].bounds;


NSLog(@"MaxFrame for %d: (%f, %f, %f, %f)", self.interfaceOrientation, maxFrame.origin.x, maxFrame.origin.y, maxFrame.size.width, maxFrame.size.height);
NSLog(@"MaxBounds for %d: (%f, %f, %f, %f)", self.interfaceOrientation, maxBounds.origin.x, maxBounds.origin.y, maxBounds.size.width, maxBounds.size.height);


    // figure out the offset of the status bar
    CGFloat statusBarOffset;
    switch (self.interfaceOrientation) {
        case UIInterfaceOrientationPortrait:
            statusBarOffset = maxFrame.origin.y;
        break;
        case UIInterfaceOrientationPortraitUpsideDown:
            statusBarOffset = maxBounds.size.height - maxFrame.size.height;
        break;
        case UIInterfaceOrientationLandscapeRight:
        case UIInterfaceOrientationLandscapeLeft:
            statusBarOffset = maxBounds.size.width - maxFrame.size.width;
        break;
    }

    // If the orientation is landscape left or landscape right then swap the width and height
    if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
        CGFloat temp = maxBounds.size.height;
        maxBounds.size.height = maxBounds.size.width;
        maxBounds.size.width = temp;
    }

    // apply status bar to the top of the view
    maxBounds.origin.y = statusBarOffset;
    maxBounds.size.height -= statusBarOffset;

    // Take into account if there is a navigation bar present and visible (note that if the NavigationBar may
    // not be visible at this stage in the view controller's lifecycle.  If the NavigationBar is shown/hidden
    // in the loadView then this provides an accurate result.  If the NavigationBar is shown/hidden using the
    // navigationController:willShowViewController: delegate method then this will not be accurate until the
    // viewDidAppear method is called)
    if (self.navigationController && !self.navigationController.navigationBarHidden) {
        NSLog(@"has nav bar");
        maxBounds.size.height -= self.navigationController.navigationBar.frame.size.height;
        maxBounds.origin.y += self.navigationController.navigationBar.frame.size.height;
    }

    // Take into account if there is a toolbar present and visible
    if (self.tabBarController && !self.tabBarController.view.hidden) {
        maxBounds.size.height -= self.tabBarController.tabBar.frame.size.height;
        NSLog(@"has tab bar");
    }

NSLog(@"result for %d: (%f, %f, %f, %f)", self.interfaceOrientation, maxBounds.origin.x, maxBounds.origin.y, maxBounds.size.width, maxBounds.size.height);
    return maxBounds;
}