Search code examples
ipadanimationuitoolbar

Animated Resize of UIToolbar Causes Background to be Clipped on iOS <5.1


I have implemented a custom split view controller which — in principle — works quite well.

There is, however one aspect that does not work was expected and that is the resize-animation of the toolbar on iOS prior to version 5.1 — if present:

After subclassing UIToolbar to override its layoutSubviews method, animating changes to the width of my main-content area causes the toolbar-items to move as expected. The background of the toolbar — however — does not animate as expected.

Instead, its width changes to the new value immediately, causing the background to be shown while increasing the width.

Here are what I deem the relevant parts of the code I use — all pretty standard stuff, as little magic/hackery as possible:

// From the implementation of my Split Layout View Class:
- (void)setAuxiliaryViewHidden:(BOOL)hide animated:(BOOL)animated completion:(void (^)(BOOL isFinished))completion
{
auxiliaryViewHidden_ = hide;
    if (!animated)
    {
        [self layoutSubviews];
        if (completion)
            completion(YES);

        return;
    }

    // I've tried it with and without UIViewAnimationOptionsLayoutSubviews -- didn't change anything...
    UIViewAnimationOptions easedRelayoutStartingFromCurrentState = UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState;
    [UIView animateWithDuration:M_1_PI delay:0.0 options:easedRelayoutStartingFromCurrentState animations:^{
        [self layoutSubviews];
    } completion:completion];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // tedious layout work to calculate the frames for the main- and auxiliary-content views

    self.mainContentView.frame = mainContentFrame; // <= This currently has the toolbar, but...
    self.auxiliaryContentView.frame = auxiliaryContentFrame; // ...this one could contain one, as well.
}

// The complete implementation of my UIToolbar class:
@implementation AnimatableToolbar

static CGFloat sThresholdSelectorMargin = 30.;

- (void)layoutSubviews
{
    [super layoutSubviews];

    // walk the subviews looking for the views that represent toolbar items 
    for (UIView *subview in self.subviews)
    {
        NSString *className = NSStringFromClass([subview class]);
        if (![className hasPrefix:@"UIToolbar"]) // not a toolbar item view
            continue;

        if (![subview isKindOfClass:[UIControl class]]) // some other private class we don't want to f**k around with…
            continue;

        CGRect frame = [subview frame];
        BOOL isLeftmostItem = frame.origin.x <= sThresholdSelectorMargin;
        if (isLeftmostItem)
        {
            subview.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
            continue;
        }

        BOOL isRightmostItem = (CGRectGetMaxX(self.bounds) - CGRectGetMaxX(frame)) <= sThresholdSelectorMargin;
        if (!isRightmostItem)
        {
            subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;

            continue;
        }

        subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
    }
}

@end

I’ve set the class of the toolbar in InterfaceBuilder and I know for a fact, that this code gets called and, like I said, on iOS 5.1 everything works just fine.

I have to support iOS starting version 4.2, though…

Any help/hints as to what I’m missing are greatly appreciated.


Solution

  • As far as I can see, your approach can only work on iOS SDK > 5. Indeed, iOS SDK 5 introduced the possibility of manipulating the UIToolbar background in an explicit way (see setBackgroundImage:forToolbarPosition:barMetrics and relative getter method).

    In iOS SDK 4, an UIToolbar object has no _UIToolbarBackground subview, so you cannot move it around in your layoutSubviews implementation. To verify this, add a trace like this:

    for (UIView *subview in self.subviews)
    {
        NSLog(@"FOUND SUBVIEW: %@", [subview description]);
    

    run the code on both iOS 4 and 5 and you will see what I mean.

    All in all, the solution to your problem lays in handling the background in two different ways under iOS 4 and iOS 5. Specifically, on iOS 4 you might give the following approach a try:

    1. add a subview to your custom UIToolbar that acts as a background view:

      [toolbar insertSubview:backgroundView atIndex:0];

    2. set:

      toolbar.backgroundColor = [UIColor clearColor];

      so that the UIToolbar background color does not interfere;

    3. in your layoutSubviews method animate around this background subview together with the others, like you are doing;

    Of course, nothing prevents you from using this same background subview also for iOS 5, only thing you should beware is that at step 1, the subview should be inserted at index 1 (i.e, on top of the existing background).

    Hope that this helps.