Главная iPhone Mac OS X Форум О себе

Скроллинг при помощи акселерометра

Одной из самых “вкусных” функциональностей, которые предоставляет iPhone/iPod Touch, безусловно является акселерометр. Его реализация в устройствах от Apple не первая в мире — попытки внедрения модуля определения положения аппарата были и раньше, например, в телефонах от Sony Ericsson и Nokia, но действительно удачное применение удалось найти именно Apple, сделав акселерометр одной из главных отличительных черт своих телефонов и плееров.

В этот раз начну свою статью необычно — с видео, в котором продемонстрирую в действии приложение, созданием которого мы займемся в этом посте.

Как и большинство классов, с которыми нам довелось познакомиться, API акселерометра прост до безобразия. Для его использования в своих программах нужно быть знакомым всего с двумя классами и одним делегатом. Но прежде чем приступить к знакомству с ними, создайте новый проект на основе шаблона “Window based application” в XCode. Я назвал его “accelerometer”.

Хочу заранее предупредить, что без сертификата разработчика попробовать в действии это приложение вам не удастся, как и любые примеры с сайта developer.apple.com, работающие с информацией о положении аппарата в пространстве, так как в симуляторе нет возможности эмулировать вращение устройства. Но информации в статье достаточно, чтобы составить представление о функционировании акселерометра, знания о работе с которым пригодятся вам в будущем и позволят писать приложения, использующие его функциональность, уже сейчас.

Первым делом нам потребуется создать в нашей программе Table View и заполнить его буквами алфавита. Для этих целей воспользуемся готовым объектом UITableViewController, который немного упростит нам дальнейшую работу с таблицей. Для тех, кто еще не был знаком с UITableView, отправляю к статье Вездесущий UITableView, а остальные откройте MainWindow.xib и добавьте на форму объект UITableViewController.

UITableViewController в Interface Builder

dataSource и delegate для нашей таблицы автоматически установлены на объект UITableViewController. Чтобы описать методы, ответственные за наполнение таблицы данными, измените класс UITableViewController на tableViewController, сгенерируйте файлы класса и включите их в состав проекта.

Чтобы наша таблица отображалась в окне приложение, добавьте outlet переменную viewController класса tableViewController в класс accelerometerAppDelegate, и свяжите ее с объектом tableViewController на форме через Interface Builder. А в методе applicationDidFinishLaunching: добавьте следующую строчку:


[window addSubview:viewController.view];

Специально не описываю подробности всего процесса, так как уже неоднократно освещал эти вопросы в предыдущих статьях. В случае появления трудностей взгляните в исходный код проекта, приложенного в конце статьи.

В accelerometerAppDelegate.h объявите переменную list класса NSMutableArray, в которую мы поместим сгенерированный нами алфавит. В этом нам поможет метод stringWithFormat класса NSString, в котором мы будем формировать символы по кодам из нижней части ACSII таблицы. Код поместим во все тот же applicationDidFinishLaunching:.


list = [[NSMutableArray alloc] init];

for (int i = 65; i < 65+26; i++) {
    [list addObject:[NSString stringWithFormat:@"%c", i]];
}

Не забудьте освободить переменную list в dealloc.

Нам осталось сформировать код, ответственный за заполнение таблицы данными. Подробно останавливаться на нем не буду, лишь приведу исходный текст tableViewController.m.


#import "tableViewController.h"
#import "accelerometerAppDelegate.h"

@implementation tableViewController

— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    accelerometerAppDelegate *appDelegate = (accelerometerAppDelegate *)[[UIApplication sharedApplication] delegate];
    return [appDelegate.list count];
}

— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
   
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
    }
   
    accelerometerAppDelegate *appDelegate = (accelerometerAppDelegate *)[[UIApplication sharedApplication] delegate];
    cell.text = (NSString *)[appDelegate.list objectAtIndex:indexPath.row];
   
    return cell;
}

@end

Теперь, когда подготовительная фаза завершена, мы можем приступить к знакомству с акселерометром. Но прежде немного теории.

Акселерометр — устройство, измеряющее проекцию силы тяжести. С его помощью можно также установить величину ускорения аппарата под действием внешних сил. Для получения информации о величине и направлении ускорения используются значения проекций на оси координат.

Оси координат в iPhone
* изображение взято с сайта http://www.forestgumpsays.com/?p=42

В состоянии покоя, когда устройство не двигается, величина замеряемого ускорения равна силе тяжести и принята за единицу. Соотношения величин проекций этой силы на оси координат дают нам углы поворота устройства в пространстве. Если iPhone находится в движении, то величину ускорения, с которым разгоняется аппарат, можно посредством дополнительных преобразований вычислить на основе значений проекций.

Заметьте, это именно ускорение, а не скорость движения устройства. То есть, если ваш iPhone начнет падать на землю, то проекции величины ускорения примут значение 0 по всем осям — ваш iPhone будет в невесомости :) А если вы поднимаетесь с должным ускорением на лифте вверх, то значение силы тяжести на время ускорения увеличится.

Чтобы было понятнее, пользуясь значением по оси Z можно определить в каком положении находится аппарат: лицом вверх (-1, при нулевых значениях по другим осям), в вертикальном положении (0) или лицом вниз (+1).

Проекция вектора силы тяжести на ось Z

Ось Y дает нам следующую информацию: iPhone находится в вертикальном положении (-1), лежит в горизонтальной плоскости (0) или находится в вертикальном положении, только вверх ногами (+1).

Проекция вектора силы тяжести на ось Y

И, наконец, ось X дает информацию о повороте аппарата влево (-1) или вправо (+1).

Проекция вектора силы тяжести на ось X

Для случая произвольной ориентации iPhone данные о величине ускорения распределяются по осям согласно проекции вектора ускорения.

Проекция вектора силы тяжести на оси координат в произвольном положении iPhone

Всю эту информацию аккумулирует объект класса UIAcceleration, возвращающий данные по всем осям, а также временной маркер, позволяющий определить относительное время замера указанных величин. Напрямую подступиться к данным этого класса нельзя, эту информацию можно получить только через делегат UIAccelerometerDelegate, предоставляющего для реализации один единственный метод accelerometer:didAccelerate:, в который возвращается объект класса UIAcceleration. Назначение делегата и инициализация вызовов метода accelerometer:didAccelerate: происходит при помощи класса UIAccelerometer. Пришло время написать код, реализующий описанный алгоритм.

В метод applicationDidFinishLaunching: добавьте следующие строки.


[[UIAccelerometer sharedAccelerometer] setUpdateInterval:0.5];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];

Первая назначает интервал в секундах, с которым будут посылаться данные от акселерометра в класс-делегат, назначенный в следующей строке. В заголовочном файле класса accelerometerAppDelegate укажите протокол UIAccelerometerDelegate.


@interface accelerometerAppDelegate : NSObject <UIApplicationDelegate, UIAccelerometerDelegate>

Нам осталось только добавить реализацию метода в класс accelerometerAppDelegate.


— (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
}

Но что же мы должны в нем написать, чтобы реализовать прокрутку списка? Возьмем за точку отсчета положение нашего устройства в пространстве, при котором угол между задней стенкой и землей составляет 45 градусов. В этом случае проекции силы тяжести на ось Y будет составлять -0.7. Если мы отклоняем аппарат чуть ближе к вертикальному положению, то примем, что при достижении угла в 30 градусов от вертикали мы должны перелистнуть список к концу. И наоборот, при достижении угла в 30 и менее градусов от горизонтального положения, мы должны перелистнуть список к началу.

В первом случае абсолютная величина проекции силы тяжести на ось Y, направленная вдоль аппарата, станет равна 0.86. Те, кто не понял откуда взялось это значение, вспоминаем геометрию и вычисление проекции на ось координат вектора единичной длины. Во втором случае это же значение равно 0.5. Для реализации прокрутки мы воспользуемся методом scrollToRowAtIndexPath:atScrollPosition:animated: класса UITableView.


— (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
    double absY = fabs(acceleration.y);
   
    if (absY <= 0.5) {
        // Прокрутка к началу списка
        [viewController.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
                                        atScrollPosition:UITableViewScrollPositionTop
                                                animated:YES];
    } else if (absY >= 0.86) {
        // Прокрутка к концу списка
        [viewController.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([list count] — 1) inSection:0]
                                        atScrollPosition:UITableViewScrollPositionBottom
                                                animated:YES];
    }
}

Мы будем работать с абсолютными значениями проекции силы тяжести на ось Y, чтобы не запутаться в операциях сравнения отрицательных величин. scrollToRowAtIndexPath:atScrollPosition:animated: использует для указания координат конечной точки прокрутки объект NSIndexPath, точнее категорию класса NSIndexPath, приведенную для использования в UITableView. Мы указываем строку (row) и номер секции (section), куда нужно переместиться. Не забываем, что нумерация начинается с нуля. Параметр atScrollPosition позволяет указать где должна отображаться строка с указанной координатой после прокрутки к ее положению: UITableViewScrollPositionTop — в верхней части таблицы, UITableViewScrollPositionBottom — в нижней части таблицы. И, конечно же, прокрутка должна быть анимирована, чтобы мы увидели весь процесс.

Можно немного улучшить наше приложение. Как вы помните, описанный выше метод вызывается каждые 0.5 секунды. А это значит, что при нахождении аппарата в горизонтальном или вертикальном положении прокрутка списка будет вызываться постоянно, не смотря на то, что мы уже находимся в крайнем положении. Предлагаю сравнивать текущее положение с требуемым, и только в случае разницы осуществлять прокрутку.

В определении положения в таблице нам поможет метод indexPathsForVisibleRows класса UITableView, возвращающий список координат видимых ячеек. Нам потребуется лишь сравнить координаты первой и последней ячейки.


— (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
    double absY = fabs(acceleration.y);
   
    if (absY <= 0.5) {
        if ([[[viewController.tableView indexPathsForVisibleRows] objectAtIndex:0] row] > 0) {
            // Прокрутка к началу списка
            [viewController.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
             atScrollPosition:UITableViewScrollPositionTop
             animated:YES];
        }
    } else if (absY >= 0.86) {
        if ([[[viewController.tableView indexPathsForVisibleRows] lastObject] row] < ([list count] — 1)) {
            // Прокрутка к концу списка
            [viewController.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([list count] — 1) inSection:0]
             atScrollPosition:UITableViewScrollPositionBottom
             animated:YES];
        }
    }
}

Как вы поняли, по показаниям акселерометра можно определить направление силы тяжести, и, как следствие, ориентацию устройства. Если вам нужно всего лишь определить положение аппарата, то есть более простой путь — воспользоваться информацией, которую предоставляет класс UIDevice.


UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

Метод orientation возвращает одно из нескольких возможных значений:

  • UIDeviceOrientationPortrait и UIDeviceOrientationPortraitUpsideDown — вертикальное и вертикально-перевернутое положение
  • UIDeviceOrientationLandscapeLeft и UIDeviceOrientationLandscapeRight — горизонтальное положение в двух вариантах
  • UIDeviceOrientationFaceUp и UIDeviceOrientationFaceDown — лицом вверх и лицом вниз

Если же вы хотите, как и выше, получать информацию о смене положения аппарата, то следует воспользоваться следующим кодом.


— (void)applicationDidFinishLaunching:(UIApplication *)application {
	// ...
	// Инициализация
	
	[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];	
	[[NSNotificationCenter defaultCenter] addObserver:self
					  selector:@selector(didRotate:)
					  name:@"UIDeviceOrientationDidChangeNotification" 
					  object:nil];	
    }
}

— (void) didRotate:(NSNotification *)notification
{	
	UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
	// Здесь ваш код
}

Метод beginGeneratingDeviceOrientationNotifications своим запуском начинает генерировать уведомления UIDeviceOrientationDidChangeNotification, которые информируют о смене положения iPhone. Эти уведомления мы можем перехватывать и обрабатывать самостоятельно, что мы и делаем в методе didRotate. Я немного залез вперед в своих объяснениях, так как еще не поднимал тему обработки уведомлений в своих постах, и дабы не отклоняться от темы статьи, предлагаю закончить сегодняшнее повествование и поставить здесь жирную точку.

Скачать архив проекта (8Кб)

Дополнительная информация:

Примеры кода:

Комментарий

Пингбэки

Ссылки по программированию для iPhone &laquo; KasBlog @kasblog.ru 2.11.2020 22:15
Раз: http://pyobjc.ru/2008/09/30/skrolling-pri-pomoshi-akselerometra/

Комментарии

Val 1.10.2020 12:54

Огромное спасибо!

ответить
vjaako 19.05.2020 20:37

man, you have a balls! really!

ответить

Форма комментирования для «Скроллинг при помощи акселерометра»

Обязательное поле. Не больше 30 символов.

Обязательное поле

captcha image Пожалуйста, введите символы, которые вы видите на изображении

Карта Сайта