Использование камеры в приложениях на iPhone SDK

 

iPhone содержит множество полезных функций. Одна из них – это встроенная камера и системное приложение Camera для создания фотографий. Все это замечательно, но, а как же использование встроенных приложений? iPhone SDK позволяет использовать камеру посредством класса UIImagePickerController, что само по себе неплохо, но есть один небольшой недостаток – у вас нет возможности создавать постоянный развернутый «живой» экран захвата фотографии, в то время как приложение Camera позволяет это сделать. Вместо этого вам следует использовать UIImagePickerController только в модальном режиме – чтобы показать всплывающее изображение, когда вам нужна фотография, и закрыть его после того, как фото сделано. Для того, чтобы сделать следующее фото, вам придется заново открыть это изображение.

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

Решением может стать использование UIImagePickerController в качестве порядкового немодального контроллера изображения под контроллером навигации, также как мы используем другие контроллеры изображения. Попробуйте и увидите, что это работает! Экран захвата фотографии работает и выглядит так как и положено. Вы можете назначать делегата и обрабатывать события UIImagePickerControllerDelegate, чтобы получить и сохранить фото. Нажмите кнопку Shoot, затем кнопку Save – замечательно, вы сделали фотографию! Но взгляните на все это – кнопки, предназначенные для того, чтобы сделать фото еще раз и потом сохранить его, остаются поверх экрана и неактивны сейчас, когда они нажаты… Это происходит из-за того, что у вас нет возможности очистить экран, чтобы сделать еще одно фото, при нажатии кнопки Save изображение фиксируется и кнопки деактивируются. В данном случае, чтобы сделать еще одно фото, вам нужно заново запускать UIImagePickerController. Это не очень удобно и просто. И вам снова нужно использовать панели и кнопки, которые закрывают экран захвата фотографии…

У меня есть идея! Когда мы нажимаем на кнопку Shoot, изображение прекращает обновление и отображает одно изображение из камеры, затем нам нужно нажать на кнопки Retake или Save. Можем ли мы сделать фото и сохранить его без использования UIImagePickerControllerDelegate, затем программно нажать кнопку Retake, чтобы повторно запустить экран и получить еще одну фотографию? Конечно, можем! Если вы изучите иерархию окон камеры после нажатия кнопки Shoot, вы обнаружите скрытое окно типа ImageView. Данный класс не описан в SDK, но можно применить его методы на практике, используя возможности Objective-C. Этот класс содержит метод под названием imageRef. Опробовав его, видим, что он возвращает объект CGImage! И изображение размером 1200 x 1600 – это точно фотография с камеры!

Теперь мы знаем, что можно сделать фото без использования UIImagePickerControllerDelegate. Но в какой именно момент это можно сделать? Можем ли мы перехватить нажатия пользователя на кнопку Shoot для начала обработки? Это возможно, но не очень удобно. Вы помните нашу главную цель о создании постоянного развернутого экрана захвата фотографии – как это делает системное приложение Camera? Пришло время сделать это! Когда мы исследовали иерархию окон, мы обнаружили ряд окон поверх экрана камеры. Мы можем попробовать скрыть эти окна и создать нашу собственную кнопку под экраном захвата фотографии для того, чтобы сделать фото одним щелчком. Но можем ли мы заставить экран захвата фотографии сделать фото? Это очень просто – мы можем достать соответствующий переключатель из кнопки Shoot и вызвать его из нашего обработчика действий!

Итак, мы получили изображение. Но весь этот процесс занимает несколько секунд. В какой же момент мы можем определить, что изображение уже готово? Это происходит тогда, когда кнопки Cancel и Shoot заменяются кнопками Retake и Save. Этот процесс проще всего отследить, запуская таймер с короткими интервалами времени и проверяя кнопки. Затем мы можем получить и сохранить фотографию, используя соответствующий селектор из кнопки Retake и запуская его, чтобы переустановить экран захвата фотографии и подготовить его для еще одного фото. Ниже представлен код:

// Shot button on the toolbar touched. Make the photo.  - (void)shotAction:(id)sender {  [self enableInterface:NO];  // Simulate touch on the Image Picker's Shot button  UIControl *camBtn = [self getCamShutButton];  [camBtn sendActionsForControlEvents:UIControlEventTouchUpInside];    // Set up timer to check the camera controls to detect when the image  // from the camera will be prepared.  // Image Picker's Shot button is passed as userInfo to compare with current button.  [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(savePhotoTimerFireMethod:) userInfo:camBtn repeats:NO];  }    // Return Image Picker's Shoot button (the button that makes the photo).  - (UIControl*) getCamShutButton {    UIView *topView = [self findCamControlsLayerView:self.view];  UIView *buttonsBar = [topView.subviews objectAtIndex:2];  UIControl *btn = [buttonsBar.subviews objectAtIndex:1];    return btn;  }    // Return Image Picker's Retake button that appears after the user pressed Shoot.  - (UIControl*) getCamRetakeButton {    UIView *topView = [self findCamControlsLayerView:self.view];  UIView *buttonsBar = [topView.subviews objectAtIndex:2];  UIControl *btn = [buttonsBar.subviews objectAtIndex:0];    return btn;  }    // Find the view that contains the camera controls (buttons)  - (UIView*)findCamControlsLayerView:(UIView*)view {    Class cl = [view class];  NSString *desc = [cl description];  if ([desc compare:@"PLCropOverlay"] == NSOrderedSame)  return view;    for (NSUInteger i = 0; i < [view.subviews count]; i++)  {  UIView *subView = [view.subviews objectAtIndex:i];  subView = [self findCamControlsLayerView:subView];  if (subView)  return subView;  }    return nil;  }    // Called by the timer. Check the camera controls to detect that the image is ready.  - (void)savePhotoTimerFireMethod:(NSTimer*)theTimer {    // Compare current Image Picker's Shot button with passed.  UIControl *camBtn = [self getCamShutButton];  if (camBtn != [theTimer userInfo])  {  // The button replaced by Save button - the image is ready.  [self saveImageFromImageView];    // Simulate touch on Retake button to continue working; the camera is ready to take new photo.  camBtn = [self getCamRetakeButton];  [camBtn sendActionsForControlEvents:UIControlEventTouchUpInside];    [self enableInterface:YES];  }  else  {  NSTimeInterval interval = [theTimer timeInterval];  [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(savePhotoTimerFireMethod:) userInfo:camBtn repeats:NO];  }  }    // Save taken image from hidden image view.  - (BOOL)saveImageFromImageView {    UIView *cameraView = [self.view.subviews objectAtIndex:0];  if ([self enumSubviewsToFindImageViewAndSavePhoto:cameraView])  return YES;    return NO;  }    // Recursive enumerate subviews to find hidden image view and save photo  - (BOOL)enumSubviewsToFindImageViewAndSavePhoto:(UIView*)view {    Class cl = [view class];  NSString *desc = [cl description];  if ([desc compare:@"ImageView"] == NSOrderedSame)  return [self grabPictureFromImageView:view];    for (int i = 0; i < [view.subviews count]; i++)  {  if ([self enumSubviewsToFindImageViewAndSavePhoto:[view.subviews objectAtIndex:i]])  return YES;  }    return NO;  }    // Grab the image from hidden image view and save the photo  - (BOOL)grabPictureFromImageView:(UIView*)view {    CGImageRef img = (CGImageRef)[view imageRef];  if (img)  {  // Taken image is in UIImageOrientationRight orientation  UIImage *photo = [self correctImageOrientation:img];  UIImageWriteToSavedPhotosAlbum(photo, nil, nil, nil);    return YES;  }    return NO;  }    // Correct image orientation from UIImageOrientationRight (rotate on 90 degrees)  - (UIImage*)correctImageOrientation:(CGImageRef)image {    CGFloat width = CGImageGetWidth(image);  CGFloat height = CGImageGetHeight(image);  CGRect bounds = CGRectMake(0.0f, 0.0f, width, height);    CGFloat boundHeight = bounds.size.height;  bounds.size.height = bounds.size.width;  bounds.size.width = boundHeight;    CGAffineTransform transform = CGAffineTransformMakeTranslation(height, 0.0f);  transform = CGAffineTransformRotate(transform, M_PI / 2.0f);    UIGraphicsBeginImageContext(bounds.size);    CGContextRef context = UIGraphicsGetCurrentContext();    CGContextScaleCTM(context, - 1.0f, 1.0f);  CGContextTranslateCTM(context, -height, 0.0f);  CGContextConcatCTM(context, transform);    CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), image);    UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    return imageCopy;  }

Другой важный вопрос заключается в следующем: в какой момент можно скрыть перекрывающие экран окна и контролы и создать нашу собственную кнопку? Пробуем viewDidLoad…упс…Экран захвата фотографии до сих пор не создан. Пробуем viewWillAppear…Аналогичный результат… Пробуем viewDidAppear…Да, окна создаются и их сейчас можно скрыть. Хорошо, мы их скроем и создадим панель инструментов с нашей кнопкой Shoot. Всё это работает, но экран сбрасывает – видим, как стандартные окна и кнопки сначала отображаются, а затем скрываются. Как предотвратить это? Я перепробовал несколько способов и нашел наиболее подходящий: нам нужно скрыть окна до того, как они добавляются к экрану захвата фотографии (когда запускается метод камеры addSubview). Этот вариант подходит, если используются возможности Objective-C для динамичного замещения метода во время выполнения программы. Ок, давайте будем использовать наш собственный метод вместо addSubview. Используя наш метод, мы можем проверить, что годное изображение – это одно из «подокон» экрана захвата фотографии и имеет скрытый статус «да». Итак, мы замещаем addSubview в viewWillAppear до того, как экран захвата фотографии будет создан. Мы также создаем нашу собственную панель инструментов и кнопку Shoot в viewDidAppear после того, как экран создан. Взгляните на код, представленный ниже:

// Replace "addSubview:" if called first time; hide camera controls otherwise.  - (void)viewWillAppear:(BOOL)animated {    [super viewWillAppear:animated];    if (toolBar != nil)  {  // The view was already appeared; we don't need to subclass UIView  // but need to hide extra camera controls.  UIView *cameraView = [self findCamControlsLayerView:self.view];  if (cameraView)  {  cameraView = cameraView.superview;  int cnt = [cameraView.subviews count];  if (cnt >= 4)  {  for (int i = 2; i < cnt - 1; i++)  {  UIView *v = [cameraView.subviews objectAtIndex:i];  v.hidden = YES;  }  }  }  }  else  {  // Subclass UIView and replace addSubview to hide the camera view controls on fly.  [RootViewController exchangeAddSubViewFor:self.view];  }  }    // Exchange addSubview: of UIView class; set our own myAddSubview instead  + (void)exchangeAddSubViewFor:(UIView*)view {    SEL addSubviewSel = @selector(addSubview:);  Method originalAddSubviewMethod = class_getInstanceMethod([view class], addSubviewSel);    SEL myAddSubviewSel = @selector(myAddSubview:);  Method replacedAddSubviewMethod = class_getInstanceMethod([self class], myAddSubviewSel);    method_exchangeImplementations(originalAddSubviewMethod, replacedAddSubviewMethod);  }    // Add the subview to view; "self" points to the parent view.  // Set "hidden" to YES if the subview is the camera controls view.  - (void) myAddSubview:(UIView*)view {    UIView *parent = (UIView*)self;    BOOL done = NO;  Class cl = [view class];  NSString *desc = [cl description];    if ([desc compare:@"PLCropOverlay"] == NSOrderedSame)  {  for (NSUInteger i = 0; i < [view.subviews count]; i++)  {  UIView *v = [view.subviews objectAtIndex:i];  v.hidden = YES;  }    done = YES;  }    [RootViewController exchangeAddSubViewFor:parent];    [parent addSubview:view];    if (!done)  [RootViewController exchangeAddSubViewFor:parent];  }

Полная демо версия проекта здесь. Проект был реализован как приложение, основанное на навигации. Обратите, пожалуйста, внимание на то, что все файлы реализации были переименованы из *.m в *.mm, для конвертации из Objective-C в Objective-C++, т.к. я предпочитаю C++.

Описанная выше методика была использована в приложении iUniqable доступном в AppStore (раздел Social Networking). Вы можете свободно им пользоваться.

Вам понравилась статья, и вы бы хотели отдать свой проект на разработку? Вы можете связаться с нами здесь!