Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MPVolumeView doesn't redraw correctly in iOS 7

So I have an iOS7 app that I'm using a MPVolumeView in so the user can control the volume level. I have the route button hidden on this particular MPVolumeView and use another MPVolumeView with the slider disabled as my AirPlay icon.

My app supports both Portrait and landscape orientations, and the volume slider is a different width in these two different modes.

If the view gets initialized in Landscape mode first, then the MPVolumeView will resize itself correctly.

However, when the view gets initialized in Portrait mode and then I rotate to Landscape mode, EVERYTHING else in the app gets resized/moved except the MPVolumeView only gets moved, and it doesn't get shorter like it should.

I am using custom images on the MPVolumeView, and if I remove the custom images for the track, this problem goes away.

Here is the code used to initialize the MPVolumeView

    self.volumeView = [[MPVolumeView alloc] initWithFrame:CGRectZero];
    self.volumeView.showsRouteButton = NO;
    self.volumeView.layer.borderColor = [[UIColor redColor] CGColor];
    self.volumeView.layer.borderWidth = 1.0f;
    if (AT_LEAST_IOS7) {
        // TODO: BUGBUG iOS7 doesn't redraw this MPVolumeView correctly when it's frame changes (i.e. we rotate to the portrait view).
        // These images simply make the bar a little thicker than the standard thickness (to match the iOS7 music app) but it's not redrawing
        // correctly so we are going to have to live with a slightly thinner bar.
        [self.volumeView setMaximumVolumeSliderImage:[UIImage imageNamed:@"volume_bar_max"] forState:UIControlStateNormal];
        [self.volumeView setMinimumVolumeSliderImage:[UIImage imageNamed:@"volume_bar_min"] forState:UIControlStateNormal];
        [self.volumeView setVolumeThumbImage:[UIImage imageNamed:@"volume_scrubber"] forState:UIControlStateNormal];
    }
    [self addSubview:self.volumeView];

And in layoutSubviews I am repositioning/scaling it's frame:

self.volumeView.frame = CGRectIntegral(CGRectMake(controlsLeft + kEdgeToSliderSideWidth,
                                                  volumeTop,
                                                  controlsWidth - (2 * kEdgeToSliderSideWidth),
                                                  volumeSize.height));

Here is what it looks like when the view starts out in Portrait Mode: (overall width of 640 pixels)

Portrait Mode

And when it gets rotated to Landscape it ends up looking like this: (overall width of 568 pixels)

Landscape Mode

Does anybody have any ideas?

like image 214
jrwagz Avatar asked Oct 07 '13 06:10

jrwagz


2 Answers

I'm experiencing the same issue. My current workaround is to destroy and re-add the slider after a screen rotation:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    //remove the old view
    for (UIView *subview in self.volumeViewContainer.subviews)
        [subview removeFromSuperview];

    //recreate it
    UIImage *slider = [[UIImage imageNamed:@"slider"] resizableImageWithCapInsets:UIEdgeInsetsMake(0.0, 15.0, 0.0, 15.0)];
    UIImage *knobImage = [UIImage imageNamed:@"slider_knob"];
    MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:self.volumeViewContainer.bounds] autorelease];
    [volumeView setMinimumVolumeSliderImage:slider forState:UIControlStateNormal];
    [volumeView setMaximumVolumeSliderImage:slider forState:UIControlStateNormal];
    [volumeView setVolumeThumbImage:knobImage forState:UIControlStateNormal];
    [self.volumeViewContainer addSubview:volumeView];
}

Doing that there is still the issue that the slider is still displayed with glitches DURING rotation. That's why I render the slider to an image and swap the slider with said image before a rotation takes place:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    //render to UIImage
    UIGraphicsBeginImageContextWithOptions(self.volumeViewContainer.frame.size, NO, 0.0);
    [self.volumeViewContainer.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //remove old slider
    for (UIView *subview in self.volumeViewContainer.subviews)
        [subview removeFromSuperview];

    //add UIImageView which resizes nicely with the container view
    UIImageView *imageView = [[[UIImageView alloc] initWithFrame:self.volumeViewContainer.bounds] autorelease];
    imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    imageView.image = img;
    [self.volumeViewContainer addSubview:imageView];
}

I know that this is not a real solution, but I think it's better than nothing and I hope it helps you.

like image 162
Daniel S Avatar answered Nov 15 '22 19:11

Daniel S


After doing a lot of experimentation, it seems that MPVolumeView is very buggy in iOS 7.0.x.

I was having an issue where having custom images for min/max sliders, volume thumb and route would sometimes cause the slider to expand to cover the route button.

When I changed the order in which these are invoked, it fixed the issue. The trick is to call setVolumeThumbImage and setRouteButtonImage first, then setMinimumVolumeSliderImage and setMaximumVolumeSliderImage.

That took care of the overlapping track over the route button.

HOWEVER, this caused a new issue, wherein the maximumSliderImage began overlapping the volumeThumbImage!

The final solution was to bring the thumb layer to front as follows, in a subclass of MPVolumeView:

- (void)_initialize {
    UIImage *scrubber = [UIImage imageNamed:@"volume_scrubber_grip"];
    scrubberSize = scrubber.size;
    [self setVolumeThumbImage:scrubber forState:UIControlStateNormal];
    [self setRouteButtonImage:[UIImage imageNamed:@"airplay_button"] forState:UIControlStateNormal];
    [self setMinimumVolumeSliderImage:[UIImage imageNamed:@"min_slider"] forState:UIControlStateNormal];
    [self setMaximumVolumeSliderImage:[UIImage imageNamed:@"max_slider"] forState:UIControlStateNormal];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.subviews enumerateObjectsUsingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
        if([obj isKindOfClass:[UISlider class]]) {
            UISlider *slider = (UISlider *)obj;
            [slider.subviews enumerateObjectsUsingBlock:^(UIView *obj2, NSUInteger idx, BOOL *stop2) {
                if(CGSizeEqualToSize(obj2.frame.size, scrubberSize)) {
                    [obj bringSubviewToFront:obj2];
                    *stop2 = YES;
                }
            }];
            *stop = YES;
        }
    }];
}
like image 23
igz Avatar answered Nov 15 '22 17:11

igz