Customize the Airplay button's appearance
After accepting @Erik B's answer and awarding the bounty to him, I found that there was more tweaking necessary to get it to work. I am posting here for the benefit of future SO searchers.
The problem I was seeing was that the internal mechanisms of the buttons would assign the image based on the current airplay state. Thus any customizations I made during init would not stick if the Airplay receiver went away, or the state was changed somehow. To solve this, I setup a KVO observation on the button's alpha
key. I noticed that the button is always faded in/out which is an animation on alpha
.
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectZero];
[volumeView setShowsVolumeSlider:NO];
for (UIButton *button in volumeView.subviews) {
if ([button isKindOfClass:[UIButton class]]) {
self.airplayButton = button; // @property retain
[self.airplayButton setImage:[UIImage imageNamed:@"airplay.png"] forState:UIControlStateNormal];
[self.airplayButton setBounds:CGRectMake(0, 0, kDefaultIconSize, kDefaultIconSize)];
[self.airplayButton addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew context:nil];
}
}
[volumeView sizeToFit];
Then I observe the changed value of the buttons alpha
.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[UIButton class]] && [[change valueForKey:NSKeyValueChangeNewKey] intValue] == 1) {
[(UIButton *)object setImage:[UIImage imageNamed:@"airplay.png"] forState:UIControlStateNormal];
[(UIButton *)object setBounds:CGRectMake(0, 0, kDefaultIconSize, kDefaultIconSize)];
}
}
Don't forget to remove the observer if you destroy the button
- (void)dealloc {
[self.airplayButton removeObserver:self forKeyPath:@"alpha"];
…
}
Based on code observation, the button will break if Apple changes the internal view hierarchy of the MPVolumeView
to add/remove/alter the views such that a different button comes up. This makes it kind of fragile, so use at your own risk, or come up with a plan b in case this happens. I have been using it for over a year in production with no issues. If you want to see it in action, check out the main player screen in Ambiance
I finally found a bluetooth headset so that I could test the button. Changing its appearance was very simple. Here's the code:
for (UIButton *button in volumeView.subviews) {
if ([button isKindOfClass:[UIButton class]]) {
[button setImage:[UIImage imageNamed:@"custom-route-button.png"] forState:UIControlStateNormal];
[button sizeToFit];
}
}
That's all there's to it.
Update: As of iOS 13, this method is deprecated!
I think there's a better solution to this since iOS 6: Use
- (void)setRouteButtonImage:(UIImage *)image forState:(UIControlState)state;
on MPVolumeView.
See https://developer.apple.com/Library/ios/documentation/MediaPlayer/Reference/MPVolumeView_Class/index.html#//apple_ref/occ/instm/MPVolumeView/setRouteButtonImage:forState: