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

13.11.2008
51
 

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). Вы можете свободно им пользоваться.