Objective-C 2.0, iPhone, iPod, iPad, iOS, Core Motion, CMAcceleration, CMAccelerometerData, UIAccelerometer
В этой статье я приведу пример работы с акселерометром в Xcode в режимах Portrait и Landscape.
Акселерометр есть на всех устройствах с iOS.
Начиная с iPhone 4 и iPod Touch 4 в устройствах появился гироскоп.
С появлением гироскопа, для разработчиков стал доступен новый способ работы с движением устройства.
На данный момент предыдущий способ хоть и работает на iOS 4 и 5, но является устаревшим. Использовать его имеет смысл только если вы создаёте приложение для версии iOS 3 и ранее.
Актуальный способ работает начиная с iOS 4.
В этой статье я приведу пример работы с акселерометром разными способами.
Сначала мы создадим приложение, которое поможет разобраться с тем как работать с акселерометром, затем добавим в приложение мяч, который будет двигаться с помощью акселерометра.
Обработка данных акселерометра в портретном и ландшафтном режимах немного отличаются. В этой статье будут рассмотрены оба варианта ориентации экрана.
Использование акселерометра
1. Создаем новый проект Single View Application
Называем его Ball.
2. Скрываем Status Bar
В Ball-Info.plist добавляем
Status bar is initially hidden: YES
Более подробно о скрытии Status Bar можно прочитать в статье Как скрыть Status Bar (iOS, Xcode 4.2).
3. Открываем ViewController.xib
Выбираем View, в его свойствах выбираем Status Bar: None.
Добавляем Label.
В лейбл будем выводить координаты, чтобы нагляднее понимать как работает акселерометр.
В свойствах лейбла можно увеличить количество линий до 7. За этот параметр отвечает свойство Lines.
4. Пишем код
Для начала надо связать Label с кодом. Я делаю это перетаскиванием линии с зажатой правой кнопкой мыши в код.
Получаю такой код в ViewController.h
Код: Выделить всё
#import <UIKit/UIKit.h> @interface ViewController : UIViewController { IBOutlet UILabel *label;} @property (retain, nonatomic) IBOutlet UILabel *label; @end
Для того чтобы интерфейс приложения не менял ориентацию, в настройках приложения Targets - Supported Device Orientations оставляем только Portrait и пишем в ViewController.m
Код: Выделить всё
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait);}
На следующем этапе есть разные варианты дальнейшей работы с акселерометром.
Вариант 1
Устаревший в iOS 5 способ работы с акселерометром.
Не рекомендуется использовать в iOS 5.0, хоть он там и работает.
Для устройств с iOS 3 и ранних версий, для работы с акселерометром нужно использовать метод accelerometer:didAccelerate:
Почитать в официальной документации https://developer.apple.com/library/ios ... egate.html
Пример использования этого метода:
ViewController.h
Код: Выделить всё
#import <UIKit/UIKit.h> @interface ViewController : UIViewController <UIAccelerometerDelegate> { IBOutlet UILabel *label;}@property (retain, nonatomic) IBOutlet UILabel *label; @end
ViewController.m
Код: Выделить всё
- (void)viewDidLoad{ [super viewDidLoad]; UIAccelerometer *accel = [UIAccelerometer sharedAccelerometer]; accel.delegate = self; accel.updateInterval = 1.0f / 60.0f;} -(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { label.text = [NSString stringWithFormat: @"Accelerometer \nx:%f, y:%f, z:%f", acceleration.x, acceleration.y, acceleration.z];}
Вариант 2
Использование Core Motion.
Начиная с iOS 4 стало возможно использовать фреймворк Core Motion.
Core Motion позволяет приложению получать данные о движениях устройства с помощью акселерометра и гироскопа.
Подробнее о Core Motion можно прочитать в документации http://developer.apple.com/library/ios/ ... index.html
https://developer.apple.com/library/ios ... vents.html
Для работы с Core Motion framework необходимо сначала добавить этот фреймворк в проект.
Это можно сделать зайдя в свойства проекта Build Phases - Link Binary With Libraries
Нажать на значек плюса и добавить CoreMotion.framework
С Core Motion можно работать двумя способами:
- С запуском акселерометра с заданным обработчиком (startAccelerometerUpdatesToQueue).
- Периодически опрашивая акселерометр.
Способ 1
Запуск акселерометра с заданным обработчиком.
В этом способе используются блоки.
Блоки в Objective-C - это функции, описанные специальным образом.
Подробнее о Блоках в Objective-C можно прочитать в документации https://developer.apple.com/library/mac ... ction.html
В ViewController.h добавляем недостающие строки
Код: Выделить всё
#import <UIKit/UIKit.h>#import <CoreMotion/CoreMotion.h> @interface ViewController : UIViewController { UILabel *label; CMMotionManager *motionManager;} @property (retain, nonatomic) IBOutlet UILabel *label;@property (retain, nonatomic) CMMotionManager *motionManager; @end
Код: Выделить всё
@synthesize motionManager;// ...- (void)viewDidLoad{ [super viewDidLoad]; motionManager = [[CMMotionManager alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //очередь if(motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0f / 30.0f; // обновление 30 раз в секунду [motionManager startAccelerometerUpdatesToQueue:queue withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) { NSString *str; if(error) { [motionManager stopAccelerometerUpdates]; str = [NSString stringWithFormat:@"Accelerometer error: %@", error]; } else { str = [NSString stringWithFormat: @"Accelerometer \nx:%f \ny:%f \nz:%f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; } [label performSelectorOnMainThread:@selector(setText:) withObject:str waitUntilDone:YES]; }]; } else { label.text = @"No accelerometer"; } }
UIlabel доступен для изменения только в главном потоке, поэтому используем performSelectorOnMainThread. Более подробно в документации http://developer.apple.com/library/mac/ ... rence.html
Способ 2
Опрашиваем акселерометр по таймеру.
Код ViewController.h
Код: Выделить всё
#import <UIKit/UIKit.h>#import <CoreMotion/CoreMotion.h> @interface ViewController : UIViewController { IBOutlet UILabel *label; CMMotionManager *motionManager; NSTimer *updateTimer;} @property (retain, nonatomic) IBOutlet UILabel *label;@property (retain, nonatomic) CMMotionManager *motionManager;@property (retain, nonatomic) NSTimer *updateTimer; @end
Код: Выделить всё
@synthesize motionManager;@synthesize updateTimer;// ...- (void)viewDidLoad{ [super viewDidLoad]; motionManager = [[CMMotionManager alloc]init]; if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0f / 30.0f; [motionManager startAccelerometerUpdates]; } else { label.text = @"No accelerometer"; } updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 30.0f target:self selector:@selector(accelUpdate) userInfo:nil repeats:YES];} -(void)accelUpdate{ if(motionManager.accelerometerAvailable) { CMAccelerometerData *accelerometerData = motionManager.accelerometerData; label.text = [NSString stringWithFormat: @"Accelerometer \nx:%f \ny:%f \nz:%f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; } }
В лейбле вы должны будете увидеть значения акселерометра по трем осям.
Добавление мяча
Для оформления приложения я создал 2 изображения для Retina дисплея (640x960).
grass.jpg
soccer-ball.png
Можете использовать эту графику, а можете создать свою.
Для обычных дисплеев (320x480) можно использовать изображения в 2 раза меньше.
Чтобы для обычных и retina дисплеев использовались разные изображения, нужно добавлять в проект изображения 2х форматов с разными именами ImageName.png и ImageName@2x.png. Для retina нужно добавить в имя @2x.
Подходящее под экран устройства изображение будет использовано автоматически.
Если использовать изображения для Retina дисплея, то на устройствах с обычными экранами не будет заметно разницы, но нагрузка на процессор будет сильнее. В этой статье я буду использовать только изображения для Retina дисплеев.
Использование акселерометра в режиме Portrait
Мы будем изменять уже выше созданный проект.
Я выбрал вариант с использованием Core Motion с периодическим опрашиванием акселерометра (Способ 2).
1. Добавляем изображения в проект
Для этого перетаскиваем их в Xcode, в папку Supporting Files.
Ставим галочку Copy items into destination group's folder (if needed).
2. Открываем ViewController.xib
Добавляем Image View, растягиваем его на весь View.
В поле Image выбираем grass.jpg.
Устанавливаем Mode: Aspect Fit.
Добавляем еще один Image View, но уже не на весь экран, а размером 64x64.
Image: soccer-ball.png
Mode: Aspect Fit.
Можно сделать лейбл поверх других элементов. Для этого выберите лейбл и в меню Editor - Arrange - Send Forward.
Должно получиться примерно такое:
Трава специально сделана не на весь экран, чтобы мы могли отличить приложение в портретном режиме от приложения в ландшафтном режиме.
3. Связываем мяч в Image View с кодом
Я буду использовать способ с Core Motion с опросом акселерометра по таймеру
ViewController.h
Код: Выделить всё
#import <UIKit/UIKit.h>#import <CoreMotion/CoreMotion.h> @interface ViewController : UIViewController { IBOutlet UILabel *label; CMMotionManager *motionManager; NSTimer *updateTimer; IBOutlet UIImageView *ball;} @property (retain, nonatomic) IBOutlet UILabel *label;@property (retain, nonatomic) CMMotionManager *motionManager;@property (retain, nonatomic) NSTimer *updateTimer;@property (retain, nonatomic) IBOutlet UIImageView *ball; @end
ViewController.m
Код: Выделить всё
#import "ViewController.h" @implementation ViewController@synthesize label;@synthesize motionManager;@synthesize updateTimer;@synthesize ball; //... - (void)viewDidLoad{ [super viewDidLoad]; motionManager = [[CMMotionManager alloc]init]; if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0f / 60.0f; [motionManager startAccelerometerUpdates]; } else { label.text = @"No accelerometer"; } updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 60.0f target:self selector:@selector(accelUpdate) userInfo:nil repeats:YES];} -(void)accelUpdate{ if(motionManager.accelerometerAvailable) { CMAccelerometerData *accelerometerData = motionManager.accelerometerData; label.text = [NSString stringWithFormat: @"Accelerometer \nx:%f \ny:%f \nz:%f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; CGPoint delta; // координаты положение мяча // умножаем значении акселерометра на 10, чтобы мяч двигался быстрее delta.x = accelerometerData.acceleration.x * 10; delta.y = accelerometerData.acceleration.y * 10; // задаем мячу новые координаты ball.center = CGPointMake(ball.center.x + delta.x, ball.center.y - delta.y); // делаем границы, чтобы мяч не скрылся за пределы экрана if (ball.center.x < 0) { // слева ball.center = CGPointMake(0, ball.center.y); } else if (ball.center.x > 320) { // справа ball.center = CGPointMake(320, ball.center.y); } if (ball.center.y < 0) { // сверху ball.center = CGPointMake(ball.center.x, 0); } else if (ball.center.y > 480) { // снизу ball.center = CGPointMake(ball.center.x, 480); } } }
Можете запустить и проверить работу приложения.
Расчет идет от цента мяча, поэтому он на половину скрывается. Чтобы это исправить, нужно сделать дополнительные вычисления.
ball.image.size.width - позволяет узнать оригинальный размер изображения в Image View
ball.bounds.size.width - позволяет узнать размер самого Image View.
Изменяем код:
Код: Выделить всё
// делаем границы, чтобы мяч не скрылся за пределы экрана // половина ширины изображения с мячом int ballHalfWidth = ball.bounds.size.width/2; if (ball.center.x < 0+ballHalfWidth) { // слева ball.center = CGPointMake(0+ballHalfWidth, ball.center.y); } else if (ball.center.x > 320-ballHalfWidth) { // справа ball.center = CGPointMake(320-ballHalfWidth, ball.center.y); } if (ball.center.y < 0+ballHalfWidth) { // сверху ball.center = CGPointMake(ball.center.x, 0+ballHalfWidth); } else if (ball.center.y > 480-ballHalfWidth) { // снизу ball.center = CGPointMake(ball.center.x, 480-ballHalfWidth); }
Мяч двигается не реалистично, но это не было целью.
Использование акселерометра в режиме Landscape
Переделываем приложение из портретной ориентации в ландшафтную.
1. Открываем свойства проекта
Targets - Summary
Выбираем только ландшафтную ориентацию Landscape Right.
2. Открываем Ball-Info.plist
Добавляем строку
Key: Initial interface orientation
Value: Landscape (right home button)
3. Открываем ViewController.xib
Выбираем View. В свойствах выбираем
Size: iPhone/iPod touch Full screen
Orientation: Landscape
Можно поменять расположение объектов.
4. Открываем ViewController.m
Изменяем код на
Код: Выделить всё
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);}
Меняем часть кода, отвечающую за передвижение мяча и определяющую границы и добавим вывод текущих координат положения центра мяча
Код: Выделить всё
label.text = [NSString stringWithFormat: @"Accelerometer \nx:%f \ny:%f \nz:%f \nBall \nx:%f \ny:%f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z, ball.center.x, ball.center.y]; CGPoint delta; // координаты положение мяча // умножаем значении акселерометра на 10, чтобы мяч двигался быстрее delta.x = accelerometerData.acceleration.x * 10; delta.y = accelerometerData.acceleration.y * 10; ball.center = CGPointMake(ball.center.x - delta.y, ball.center.y - delta.x); // делаем границы, чтобы мяч не скрылся за пределы экрана // половина ширины изображения с мячом int ballHalfWidth = ball.bounds.size.width/2; if (ball.center.x < 0+ballHalfWidth) { // слева ball.center = CGPointMake(0+ballHalfWidth, ball.center.y); } else if (ball.center.x > 480-ballHalfWidth) { // справа ball.center = CGPointMake(480-ballHalfWidth, ball.center.y); } if (ball.center.y < 0+ballHalfWidth) { // сверху ball.center = CGPointMake(ball.center.x, 0+ballHalfWidth); } else if (ball.center.y > 320-ballHalfWidth) { // снизу ball.center = CGPointMake(ball.center.x, 320-ballHalfWidth); }
Теперь акселерометр работает корректно.