
Уже довольно давно работаю фрилансером и иногда беру пару-тройку простеньких проектов за $100-200 для разгрузки мозга. В этот раз клиент попросил использовать внешние кнопки регулировки громкости в iPhone. Проблема состояла в том, что встроенного API для внешних кнопок в iOS не существует: до недавних пор использование хардверных элементов устройства, отличное от системного поведения, было запрещено. Поэтому различные приложения типа «Camera+» и «Camera Pro» никак не могли донести подобный функционал до пользователя. Однако, по счастливой случайности, в iOS 5 разработчики Apple сами начали использовать подобный подход к интерфейсу: сделать фотографию в системном приложении камеры теперь можно, нажав на клавишу увеличения громкости.
Как реализовать подобное поведение внешних клавиш в своем приложении, смотрите под катом. Исходники прилагаются в конце статьи.
Немного погуглив о задаче, можно наткнуться на открытое решение RBVolumeButtons, которое открывает аудио сессию и начинает слушать изменение громкости. В моем случае, этот класс неприятно влиял на работу камеры: активировав новую аудио сессию, мы прерывали аудиосессию камеры. Я решил собрать свой велосипед, подойдя к процессу со слегка иной стороны; написать отдельный класс NKVolumeButtons, скрывающий в себе весь необходимый функционал.
После короткого разбора полетов я решил использовать класс MPMusicPlayer из встроенного фреймворка MediaPlayer. В этом класе есть два сиглтона музыкального плеера: один для приложения и второй системный, общий для всего телефона. Мы будем слушать изменения громкости музыкального плеера для нашего приложения. Для этого добавим в метод инициализации объекта NKVolumeButtons немного кода:
Жми меня!
[[MPMusicPlayerController applicationMusicPlayer] addObserver:self forKeyPath:@"volume" options:NSKeyValueObservingOptionNew context:nil];
Все по канонам KVO: вместо собственного велосипеда мы используем старую добрую категорию, унаследованную от NSObject. Соответственно, нам нужен и обработчик события — когда параметр, который мы слушаем, изменится, нам нужно как-то принять эту информацию. Смело добавляем метод в NKVolumeButtons!
Жми меня!
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// Здесь мы можем проверить, тот ли keyPath мы получаем; но зачем? Ведь единственный параметр, который мы слушаем - это volume
[self checkVolumeButtons];
}
- (void)checkVolumeButtons {
// 1
float currentVolume = [[MPMusicPlayerController applicationMusicPlayer] volume];
// 2
if (currentVolume > 0.5) {
[self volumeUp];
} else if (currentVolume < 0.5) {
[self volumeDown];
}
// 3
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.5];
}
Разберем код по-порядку:
- Получаем текущую громкость приложения
- Проверяем, увеличилась ли громкость, или уменьшилась
- Возвращаем громкость к исходному значению
Что за исходное значение? Объясняю: если пользователь зашел в наше приложение, а громкость на нуле? Тогда нажатия клавиши уменьшения громкости не будут иметь смысла — ничего работать не будет. Поэтому с самого начала нам нужно установить громкость на определенном уровне, от которого и будем плясать. Параметр volume может принимать значения от 0 до 1, так что мы выберем середину — 0.5. Добавим следующий код в инициализацию объекта NKVolumeButtons:
Жми меня!
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.5];
А теперь приступим к реализации методов volumeUp и volumeDown. Для удобства добавим два параметра классу NKVolumeButtons, доступных извне — upBlock и downBlock. Приведем файл NKVolumeButtons.h к следующему виду:
Жми меня!
typedef void (^ButtonBlock)();
#import <Foundation/Foundation.h>
@interface NKVolumeButtons : NSObject
@property (nonatomic, copy) ButtonBlock upBlock;
@property (nonatomic, copy) ButtonBlock downBlock;
@end
Здесь мы просто в удобном ключе добавили возможность устанавливать извне блоки кода, которые будут вызываться при нажатии клавиш регулировки громкости. Не стоит забывать и о синтезации наших параметров. Добавьте следующее в имплементацию NKVolumeButtons:
Жми меня!
@synthesize upBlock = _upBlock;
@synthesize downBlock = _downBlock;
И, конечно же, вызов наших блоков в нужное время:
Жми меня!
- (void)volumeUp {
// Всегда нужно проверять, получилось ли задать нужные параметры
if(self.upBlock) {
self.upBlock();
}
}
- (void)volumeDown {
// Всегда нужно проверять, получилось ли задать нужные параметры
if(self.downBlock) {
self.downBlock();
}
}
А вот и еще одна проблемка! Каждый раз, когда мы изменяем громкость, на экране появляется индикатор громкости. Что же, воспользуемся готовым решением, которое уже было в RBVolumeButtons. На то он и opensource, чтобы помогать друг другу, не так ли? Добавьте следующий код в NKVolumeButtons.m:
Жми меня!
// Прячем индикатор громкости
CGRect frame = CGRectMake(0, -100, 10, 0);
UIView *volumeView = [[MPVolumeView alloc] initWithFrame:frame];
[volumeView sizeToFit];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] addSubview:volumeView];
Вот и все! Нам остается только добавить этот класс в проект, инициализировать объект NKVolumeButtons и задать нужные блоки кода. Вот таким простым костылем решается проблема недостатка API внешних клавиш.
Спасибо за то, что дочитали до конца! Исходники доступны на гитхабе.
Если вдруг найдете какие-либо неточности или опечатки, милости прошу в мой уютный хабракабинет.