iOS. Приемы программирования Нахавандипур Вандад
— (void) viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear: paramAnimated];
[self startTopLeftImageViewAnimation];
[self startBottomRightViewAnimationAfterDelay:2.0f];
}
17.15. Анимирование и масштабирование видов
Постановка задачи
Требуется возможность анимировать виды и масштабировать их в сторону увеличения или уменьшения.
Решение
Создайте для вида аффинное преобразование и используйте анимационные методы UIView для сопровождения масштабирования анимацией.
Обсуждение
Перед дальнейшей работой настоятельно рекомендую перечитать раздел 17.14.
Чтобы масштабировать вид, анимируя его при этом, можно либо применить к виду преобразование масштабирования в анимационном блоке (см. раздел 17.12), либо просто увеличить высоту и/или ширину вида.
Рассмотрим, как изменять масштаб вида, применяя к нему преобразование масштабирования:
— (void) viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear: paramAnimated];
/* Помещаем вид с изображением в центре основного вида данного
контроллера вида. */
self.xcodeImageView.center = self.view.center;
/* Убеждаемся, что к этому виду с изображением не применяется никакого
преобразования сдвига. */
self.xcodeImageView.transform = CGAffineTransformIdentity;
/* Начинаем анимацию. */
[UIView beginAnimations: nil
context: NULL];
/* Анимация продлится 5 секунд. */
[UIView setAnimationDuration:5.0f];
/* Вдвое увеличиваем вид с изображением в ширину и в длину. */
self.xcodeImageView.transform = CGAffineTransformMakeScale(2.0f,
2.0f);
/* Выполняем анимацию. */
[UIView commitAnimations];
}
В этом коде используется аффинное преобразование масштабирования, в результате которого вид с изображением становится в два раза больше по сравнению с исходными размерами. Самое большое достоинство такой операции заключается в том, что в ходе масштабирования начало координат (центр) при увеличении или уменьшении совпадает с началом координат (центром) самого вида. Предположим, что центр вашего вида расположен на экране в точке с координатами (100; 100), а вы хотите масштабировать вид, вдвое увеличив его ширину и высоту. В результате центр вида так и останется в точке (100; 100), в то время как сам вид увеличится в два раза. Если бы мы увеличивали вид, сначала специально добавив ему ширины, а потом высоты, то вид, который получился бы в итоге, находился бы немного не в той точке экрана, где был исходный вид. Это объясняется тем, что, изменяя высоту и ширину рамок вида, вы одновременно изменяете значения x и y контура вида, хотите вы того или нет. Поэтому вид с изображением не будет масштабироваться относительно своего центра. Исправление такой проблемы выходит за рамки этой книги, но вы можете самостоятельно разобраться с этой задачей — может быть, вам удастся найти решение. Дам одну подсказку: можно параллельно запустить две анимации. Одна из них будет изменять длину и ширину вида, а другая — перемещать центр вида.
См. также
Разделы 17.12 и 17.14.
17.16. Анимирование и вращение видов
Постановка задачи
Требуется анимировать виды на экране при вращении.
Решение
Создайте аффинное преобразование вращения, для анимирования вращения пользуйтесь методами класса UIView.
Перед дальнейшей работой настоятельно рекомендую перечитать раздел 17.14.
Чтобы вращать вид, анимируя его при этом, нужно применить к нему преобразование вращения в то время, как в коде выполняется анимационный блок (см. раздел 17.12). Рассмотрим пример кода, который прояснит это. Допустим, у нас есть рисунок Xcode.png (см. рис. 17.9) и мы хотим отобразить его в центре экрана. После того как картинка появится на экране, мы повернем ее на 90° за 5 секунд, а потом повернем обратно, поставив в исходное положение. Итак, когда вид с изображением появится на экране, повернем этот вид на 90° по часовой стрелке:
— (void) viewDidAppear:(BOOL)paramAnimated{
[super viewDidAppear: paramAnimated];
self.xcodeImageView.center = self.view.center;
/* Начинаем анимацию. */
[UIView beginAnimations:@"clockwiseAnimation"
context: NULL];
/* Анимация будет длиться 5 секунд. */
[UIView setAnimationDuration:5.0f];
[UIView setAnimationDelegate: self];
[UIView setAnimationDidStopSelector:
@selector(clockwiseRotationStopped: finished: context:)];
/* Поворачиваем вид с изображением на 90°. */
self.xcodeImageView.transform =
CGAffineTransformMakeRotation((90.0f * M_PI) / 180.0f);
/* Выполняем анимацию. */
[UIView commitAnimations];
}
Мы решили, что селектор clockwiseRotationStopped: finished: context: должен вызываться в тот момент, когда заканчивается анимация вращения по часовой стрелке. В этом методе мы будем вращать вид с изображением против часовой стрелки, обратно в положение, соответствующее 0° (то есть исходное). На это тоже уйдет 5 секунд.
— (void)clockwiseRotationStopped:(NSString *)paramAnimationID
finished:(NSNumber *)paramFinished
context:(void *)paramContext{
[UIView beginAnimations:@"counterclockwiseAnimation"
context: NULL];
/* 5 секунд */
[UIView setAnimationDuration:5.0f];
/* Возврат в исходное положение */
self.xcodeImageView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
Как было показано в разделах 17.14 и 17.15, а также в этом разделе, существует много способов анимировать виды (прямые или непрямые подклассы UIView). При выполнении анимации можно изменять немало свойств. Будьте креативны и экспериментируйте с другими свойствами UIView, о которых раньше, возможно, не знали. Не помешает также еще раз пересмотреть документацию по UIView в органайзере Xcode.
См. также
Разделы 17.13–17.15.
17.17. Получение изображения со скриншотом вида
Постановка задачи
Требуется сохранить содержимое объекта-вида, находящегося в вашем приложении, в виде изображения. Возможно, также потребуется сохранить это изображение на диске и выполнить с ним другое действие — например, позволить пользователю поделиться этой картинкой в любимой социальной сети (см. раздел 11.11).
Решение
Выполните следующие шаги.
1. Воспользуйтесь функцией UIGraphicsBeginImageContextWithOptions для создания нового контекста изображения. Этот контекст сразу станет текущим (актуальным), и именно в нем будет происходить все последующее рисование.
2. Вызовите метод drawViewHierarchyInRect: вашего класса UIView. В качестве параметра передайте этому методу границы вида, который вы хотите отрисовать в текущем контексте.
3. Вызовите метод UIGraphicsGetImageFromCurrentImageContext, возвращаемое значение которого — это представление текущего контекста в качестве изображения. Это изображение будет относиться к типу UIImage.
4. Преобразуйте ваш экземпляр изображения в данные, воспользовавшись функцией UIImagePNGRepresentation. Эта функция даст вам объект типа NSData.
5. Наконец, вызовите в вашем объекте данных метод экземпляра writeToUrl: atomically:, чтобы записать изображение на определенный адрес на диске — если хотите. Имея экземпляр UIImage, можете выполнить с этим изображением и любую другую операцию.
Обсуждение
Иногда разработчику требуется программно делать скриншот содержимого, которое отображается на экране устройства. В частности, это может понадобиться, если вы пишете приложение для рисования и хотите предоставить пользователю возможность сохранить сделанный рисунок в файле. Возможно, этот файл затем будет сохранен в облаке iCloud, откуда его можно будет впоследствии загрузить.
Перед тем как сохранить изображение или поделиться им таким образом, мы должны нарисовать его в контексте изображения. Контекст изображения остается для нас невидимым, так как мы даже не имеем его описателя. Тем не менее все рисовальные методы, которые вы вызываете, будут оказывать влияние на текущий контекст изображения. Контекст изображения можно сравнить с невидимым холстом для рисования. Чтобы получить представление вашего изображения, воспользуйтесь функцией UIGraphicsGetImageFromCurrentImageContext.
Когда вы начнете решать такую задачу с помощью нового SDK, вам всего лишь потребуется вызвать в виде метод drawViewHierarchyInRect: — и содержимое этого вида будет отрисовано в текущем контексте.
Итак, применим изученный материал на практике. В следующем фрагменте кода мы собираемся разместить в нашем виде ряд компонентов (при этом используются раскадровки, описанные в главе 6). Не важно, что именно вы поместите в раскадровке. Мы хотим снять содержимое нашего вида, сохранить эту информацию как изображение, а затем поместить это изображение в каталог Documents (Документы) на диске:
— (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear: animated];
/* Делаем скриншот */
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0f);
if ([self.view drawViewHierarchyInRect: self.view.bounds]){
NSLog(@"Successfully draw the screenshot.");
} else {
NSLog(@"Failed to draw the screenshot.");
}
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Сохраняем его на диске */
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *documentsFolder = [fileManager URLForDirectory: NSDocumentDirectory
inDomain: NSUserDomainMask
appropriateForURL: nil
create: YES
error: nil];
NSURL *screenshotUrl = [documentsFolder
URLByAppendingPathComponent:@"screenshot.png"];
NSData *screenshotData = UIImagePNGRepresentation(screenshot);
if ([screenshotData writeToURL: screenshotUrl atomically: YES]){
NSLog(@"Successfully saved screenshot to %@", screenshotUrl);
} else {
NSLog(@"Failed to save screenshot.");
}
}
В начале этого кода мы создаем новый контекст изображения и получаем его представление в виде изображения с помощью UIGraphicsGetImageFromCurrentImageContext. Имея это представление, мы воспользуемся NSFileManager, чтобы найти путь к каталогу Documents (Документы) нашего приложения, который находится на диске (см. раздел 12.1). Наконец, мы получаем представление скриншота в виде данных (с помощью функции UIImagePNGRepresentation) и после этого можем сохранить данное представление на диске. Мы должны получить представление изображения в формате PNG или JPEG, воспользовавшись для этого функцией UIImageJPEGRepresentation. Так мы получим данные, соответствующие изображению в этом формате (PNG/JPEG). Имея данные, мы можем сохранить их на диске или выполнить с ними другие операции.
См. также
Раздел 11.11, глава 6.
Глава 18. Фреймворк Core Motion
18.0. Введение
Устройства с операционной системой iOS, в частности iPhone и iPad, обычно оборудованы акселерометром. На некоторых устройствах, например новых iPhone и iPad, есть также гироскоп. Прежде чем пытаться использовать в ваших приложениях для iOS акселерометр или гироскоп, нужно проверить доступность (наличие) этих сенсоров на том устройстве, на котором работает ваша программа. В разделах 18.1 и 18.2 описаны приемы, которые можно использовать для обнаружения акселерометра или гироскопа. Устройства iOS, оснащенные гироскопом, могут регистрировать движение вдоль шести осей.
Рассмотрим ситуацию, которая позволяет оценить, насколько полезен гироскоп. Например, акселерометр не может обнаружить вращение устройства вокруг его вертикальной оси, если вы крепко держите устройство в руках, сидите в компьютерном кресле и крутитесь на нем по часовой стрелке или против часовой стрелки. Относительно пола в вашей комнате или относительно планеты Земля устройство вращается вокруг вертикальной оси, но оно при этом не вращается вокруг собственной оси Y, проходящей по вертикали через центр устройства, то есть акселерометр не обнаружит никакого движения.
Гироскоп, имеющийся в некоторых устройствах с iOS, может регистрировать такие движения. И мы можем писать более гладкие и безошибочные программы обнаружения движения. Обычно такие возможности полезны в играх, так как при их программировании разработчику зачастую требуется узнать не только о том, как устройство движется по осям X, Y и Z — эти данные можно получить от акселерометра, — но и о том, движется ли устройство по этим осям относительно Земли. А вот для этого уже нужен гироскоп.
Программист может пользоваться фреймворком Core Motion для доступа к информации, поступающей как от акселерометра, так и от гироскопа (при их наличии). Фреймворк Core Motion применяется во всех разделах этой главы. При работе с новым компилятором LLVM, чтобы связать новое приложение с системным фреймворком, вам всего лишь потребуется импортировать этот фреймворк в верхней части заголовочных файлов и файлов реализации, а компилятор сам выполнит все необходимые операции для импорта фреймворка в приложение.
Эмулятор iOS не может имитировать работу акселерометра и гироскопа. Правда, в эмуляторе iOS можно имитировать встряхивание, выбрав команду Hardware — Shake Gesture (Оборудование — Жест встряхивания) (рис. 18.1).
Рис. 18.1. Параметр Shake Gesture (Жест встряхивания) в эмуляторе iOS
18.1. Обнаружение доступности акселерометра
Постановка задачи
В вашей программе требуется определить, имеется ли в устройстве акселерометр.
Решение
Для обнаружения акселерометра пользуйтесь методом isAccelerometerAvailable класса CMMotionManager. Метод isAccelerometerActive также позволяет узнать, посылает ли акселерометр в данный момент уведомления вашей программе.
Сначала убедимся, что импортировали необходимые заголовочные файлы:
#import «AppDelegate.h»
#import <CoreMotion/CoreMotion.h>
@implementation AppDelegate
Далее проверим, что присутствие акселерометра указано в файле реализации делегата нашего приложения:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
if ([motionManager isAccelerometerAvailable]){
NSLog(@"Accelerometer is available.");
} else{
NSLog(@"Accelerometer is not available.");
}
if ([motionManager isAccelerometerActive]){
NSLog(@"Accelerometer is active.");
} else {
NSLog(@"Accelerometer is not active.");
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Итак, в устройстве с iOS, где работает ваша программа, может присутствовать акселерометр. Но это еще не означает, что акселерометр посылает уведомления вашему приложению. Если акселерометр или гироскоп посылает такие уведомления, мы говорим, что он активен (а в таком случае нам потребуется определить объект делегата, о чем поговорим чуть позже).
Если запустить этот код в эмуляторе iOS, то в окне консоли появятся примерно такие сообщения:
Accelerometer is not available. // акселерометр недоступен
Accelerometer is not active. // акселерометр неактивен
При запуске такого же кода на новом iPhone получим такие значения:
Accelerometer is available. // акселерометр доступен
Accelerometer is not active. // акселерометр неактивен
Обсуждение
В устройстве с операционной системой iOS может быть встроенный акселерометр. Поскольку мы не можем с уверенностью сказать, в каких устройствах с iOS имеется такое оборудование, а в каких — нет, перед использованием акселерометра целесообразно проверить, доступен ли он.
Чтобы проверить наличие этого оборудования, нужно инстанцировать объект типа CMMotionManager и получить доступ к его методу isAccelerometerAvailable. Это метод логического типа, он возвращает значение YES, если акселерометр доступен, и NO, если он отсутствует.
Кроме того, можно узнать о том, посылает ли акселерометр обновления вашей программе в настоящий момент (соответственно, активен ли он), воспользовавшись методом isAccelerometerActive класса CMMotionManager. О том, как получать данные от акселерометра, мы поговорим в разделе 18.3.
См. также
Раздел 18.3.
18.2. Обнаружение доступности гироскопа
Постановка задачи
В вашей программе требуется определить, имеется ли в устройстве гироскоп.
Решение
Пользуйтесь методом isGyroAvailable, относящимся к классу CMMotionManager, чтобы проверить наличие гироскопа. Кроме того, метод isGyroActive позволяет узнать, посылает ли в данный момент гироскоп обновления вашему приложению, то есть активен ли он:
#import «AppDelegate.h»
#import <CoreMotion/CoreMotion.h>
@implementation AppDelegate
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
if ([motionManager isGyroAvailable]){
NSLog(@"Gryro is available.");
} else {
NSLog(@"Gyro is not available.");
}
if ([motionManager isGyroActive]){
NSLog(@"Gryo is active.");
} else {
NSLog(@"Gryo is not active.");
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Эмулятор iOS не позволяет имитировать работу гироскопа. Запустив этот код в эмуляторе, вы увидите в окне консоли примерно такой текст:
Gyro is not available. // гироскоп недоступен
Gyro is not active. // гироскоп неактивен