0

I can configure my UIViewController's edgesForExtendedLayout so that it will extend underneath content such as the navigation bar or tab bar. If I do this, is there some way to determine the frame that is not obscured?

As a possible alternative, is there a way for a UIViewController to determine the default contentInset to apply to a UIScrollView it contains?

Use case

I have zoomable UIScrollView containing an image.

When it is fully zoomed out I want to adjust the content inset too allow the content to stay centred (details here). However, my modified insets don't take in to account the insets that the UIViewController applies automatically so that its content isn't obscured by navigation bars, etc.

I also need to compute the minimum zoom for the content – that at which the whole image will be visible and not obscured. To compute this, I need to know the size of the unobscured part of the content view.

Benjohn
  • 13,228
  • 9
  • 65
  • 127

1 Answers1

0

You need this

-(CGRect) unobscuredBounds
{
    CGRect bounds = [self.view bounds];
    return UIEdgeInsetsInsetRect(bounds, [self defaultContentInsets]);
}

-(UIEdgeInsets) defaultContentInsets
{
    const CGFloat topOverlay = self.topLayoutGuide.length;
    const CGFloat bottomOverlay = self.bottomLayoutGuide.length;
    return UIEdgeInsetsMake(topOverlay, 0, bottomOverlay, 0);
}

You could put this in a category for easy reusability.

These methods correctly handle the changes that occur when the view resizes after a rotation – the change to the UINavigationBar size is correctly handled.

Centring Content

To use this to centre content by adjusting insets, you'd do something like this:

-(void) scrollViewDidZoom:(UIScrollView *)scrollView
{
    [self centerContent];
}

- (void)centerContent
{
    const CGSize contentSize = self.scrollView.contentSize;
    const CGSize unobscuredBounds = [self unobscuredBounds].size;

    const CGFloat left = MAX(0, (unobscuredBounds.width - contentSize.width)) * 0.5f;
    const CGFloat top = MAX(0, (unobscuredBounds.height - contentSize.height)) * 0.5f;

    self.scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
}

Your content insets will now reflect the default insets that they need (to avoid being covered up) and will also have the insets they need to be nicely centred.

Handling Rotation & Zoom

You probably also want to perform centring when animating between landscape and portrait. At the same time, you might want to adjust your minimum zoom scale so that your content will always fit. Try out something like this:

-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self centerContent];

    const bool zoomIsAtMinimum = self.scrollView.zoomScale == self.scrollView.minimumZoomScale;

    self.scrollView.minimumZoomScale = [self currentMinimumScale];
    
    if(zoomIsAtMinimum)
    {
        self.scrollView.zoomScale = self.scrollView.minimumZoomScale;
    }
}

-(CGFloat) currentMinimumScale
{
    const CGFloat currentScale = self.scrollView.zoomScale;
    const CGSize scaledContentSize = self.scrollView.contentSize;
    const CGSize scrollViewSize = [self unobscuredBounds].size;

    CGFloat scaleToFitWidth = currentScale * scrollViewSize.width / scaledContentSize.width;
    CGFloat scaleToFitHeight = currentScale * scrollViewSize.height / scaledContentSize.height;

    return MIN(scaleToFitWidth, scaleToFitHeight);
}

The willAnimateRotationToInterfaceOrientation:… method is called within the view animation block, so the changes that it applies will lead to nice smooth animated changes as you switch from landscape to portrait.

Community
  • 1
  • 1
Benjohn
  • 13,228
  • 9
  • 65
  • 127