Pull to refresh

Как заставить внешние кнопки iPhone работать на себя

Reading time 4 min
Views 15K
Здравствуйте, дорогие читатели Хабра!

Уже довольно давно работаю фрилансером и иногда беру пару-тройку простеньких проектов за $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];
}

Разберем код по-порядку:

  1. Получаем текущую громкость приложения
  2. Проверяем, увеличилась ли громкость, или уменьшилась
  3. Возвращаем громкость к исходному значению

Что за исходное значение? Объясняю: если пользователь зашел в наше приложение, а громкость на нуле? Тогда нажатия клавиши уменьшения громкости не будут иметь смысла — ничего работать не будет. Поэтому с самого начала нам нужно установить громкость на определенном уровне, от которого и будем плясать. Параметр 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 внешних клавиш.

Спасибо за то, что дочитали до конца! Исходники доступны на гитхабе.
Если вдруг найдете какие-либо неточности или опечатки, милости прошу в мой уютный хабракабинет.
Tags:
Hubs:
+14
Comments 17
Comments Comments 17

Articles