ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C Часть 1

Работа с камерой (CameraRoll)

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

  • сделать снимок и добавить его в таблицу
  • сохранить созданный снимок в галерее
  • снять видео и добавить ссылку на файл в таблицу
  • сохранить видео в галерею

Вы наверное уже догадались, что наш проект будет основан на табличном представлении (я назвал его CameraRoll). Прежде чем приступить к организации работе таблицы предлагаю изменить метод с которого начнает работу наше приложение (application:didFinishLaunchingWithOptions: он находится в AppDelegate.m). Обернем наш контроллер представления контроллером навигации. Нет, мы не будем переходить с главного контроллера на детальные или осуществлять любую другую навигацию. От контроллера навигации нам нужен лишь его Top Bar.

<code data-result="[object Object]">- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController];
    self.window.rootViewController = nav;
    [self.window makeKeyAndVisible];
    return YES;
}</code>

Теперь перейдем в ViewController.h и подготовим его к работе с выборщиком картинок и таблице:

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;

@interface ViewController : UITableViewController
&lt;UIImagePickerControllerDelegate, UINavigationControllerDelegate&gt;

@property (strong, nonatomic) NSMutableArray *images;

@end</code>

Теперь у нас все готово чтобы приступить к реализации нашей цели. Для начала я предлагаю разобраться в работе с фотографиями. Саму камеру (или список фотографий устройства) мы будем вызывать по нажатию на кнопку. Надеюсь, никто не забыл как добавить кнопку в TopBar?

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    self.images = [NSMutableArray array];

    UIBarButtonItem *photoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
                                                                               target:self
                                                                               action:@selector(takePhoto)];
    self.navigationItem.rightBarButtonItem = photoItem;
}</code>

 

А обработка нажатия на только что созданную кнопку будет выглядеть так:

<code data-result="[object Object]">- (void)takePhoto {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    [self presentModalViewController:picker animated:YES];
}</code>

Этот метод модально вызовет стандартный контроллер для создания фотографий. После того как пользователь сделает снимок — будет вызван метод imagePickerController:didFinishPickingMediaWithInfo:. Реализуем его:

<code data-result="[object Object]">- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *pickedImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    [self.images addObject:pickedImage];

    [picker dismissModalViewControllerAnimated:YES];

    [self.tableView reloadData];
}</code>

 

Здесь в нашем распоряжении два параметра, UIImagePickerController *picker, который мы прячем и словарь info. Именно в последнем и возвращаются данные фотографии, которые храняться под ключем UIImagePickerControllerOriginalImage. Мы просто получаем картинку с ключа и помещаем ее в массим. Следовательно, нужно реализовать отображения картинок в таблице:

 

<code data-result="[object Object]">- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.images.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }

    cell.imageView.image = [self.images objectAtIndex:indexPath.row];
    cell.textLabel.text = [NSString stringWithFormat:@"Image %i", indexPath.row+1];

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    UIImage *image = [self.images objectAtIndex:indexPath.row];
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}</code>

 

При заполнении таблицы я решил не только подставлять картинку с массива, но и создавать текст (давать какое-то название для удобства). А при нажатии на ячейку я сохраняю выбранную картинку в фотогалерею устройства с помощью метода UIImageWriteToSavedPhotosAlbum.

 

Первая часть задания выполнена, с изображениями разобрались, пришла очередь научиться работать с видеофайлами. Отдельной кнопки для них добавлять мы не будем и хранить видеофайлы будем в той же таблице, просто на TopBar добавим еще один элемент интерфейса — сегмент:

 

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    self.images = [NSMutableArray array];

    UIBarButtonItem *photoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
                                                                               target:self
                                                                               action:@selector(takePhoto)];
    self.navigationItem.rightBarButtonItem = photoItem;

    UISegmentedControl *contentPiacker = [[UISegmentedControl alloc] initWithItems:@[@"Image",@"Video"]];
    contentPiacker.selectedSegmentIndex = 0;
    contentPiacker.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    contentPiacker.segmentedControlStyle = UISegmentedControlStyleBar;
    contentPiacker.frame = CGRectMake(0.0f, 0.0f, 180.0f, 30.0f);

    self.navigationItem.titleView = contentPiacker;
}</code>

На основе этого изменим метод вызова контроллера камеры:

<code data-result="[object Object]">- (void)takePhoto {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;

    UISegmentedControl *contentPiacker = (UISegmentedControl*)self.navigationItem.titleView;
    if (contentPiacker.selectedSegmentIndex) {
        picker.mediaTypes = @[(NSString *)kUTTypeMovie];
    }

    [self presentModalViewController:picker animated:YES];
}</code>

Теперь, перед открытием каеры мы «смотри» какой тип данных хотим получить от камеры и в зависимости от этого «говорим» камере снимать видео или сделать фото.

Соответствующим образом следует изменить метод сохранения полученых данных с камеры:

<code data-result="[object Object]">- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *pickedImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    if (pickedImage) {
        [self.images addObject:pickedImage];
    }

    NSURL *videoURL = [info objectForKey:@"UIImagePickerControllerMediaURL"];
    if (videoURL) {
        [self.images addObject:videoURL];
    }

    [picker dismissModalViewControllerAnimated:YES];

    [self.tableView reloadData];
}</code>

В случае с видео в метод imagePickerController:didFinishPickingMediaWithInfo: возврашается ссылка на видео-файл, а не заснятые данные, а хранится эта ссылка под ключем UIImagePickerControllerMediaURL. В общем, я просто добавли проверку если мы получили фото — добавляем данные фото в массив, если ссылку — добавляем ссылку.

Пришла очередь таблицы. Изменим способ отображения данных массива:

<code data-result="[object Object]">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:CellIdentifier];
        cell.detailTextLabel.numberOfLines = 3;
    }

    id item = [self.images objectAtIndex:indexPath.row];
    if ([item isKindOfClass:[UIImage class]]) {
        cell.imageView.image = item;
        cell.textLabel.text = [NSString stringWithFormat:@"Image %i", indexPath.row+1];
    } else {
        cell.textLabel.text = [NSString stringWithFormat:@"Video %i", indexPath.row+1];
        cell.detailTextLabel.text = [item absoluteString];
    }

    return cell;
}</code>

Здесь мы изменили тип ячейки (чтобы выводить ссылку на видео). Перед заполнением — проверяем тип объекта, которых находится в массиве. Если это картинка — поступаем как раньше с картинкой, а для видео выводим текст и ссылку на файл.

При нажатии на ячейку с видео-файлом я решил продемонстрировать код не только сохранения файла, но и его открытием:

<code data-result="[object Object]">- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    id item = [self.images objectAtIndex:indexPath.row];
    if ([item isKindOfClass:[UIImage class]]) {
        UIImageWriteToSavedPhotosAlbum(item, nil, nil, nil);
    } else {
        MPMoviePlayerViewController *player = [[MPMoviePlayerViewController alloc] initWithContentURL:item];
        [self presentMoviePlayerViewControllerAnimated:player];

        ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
        [library writeVideoAtPathToSavedPhotosAlbum:item
                                    completionBlock:^(NSURL *assetURL, NSError *error) {
                                        NSLog(@"Video Saved");
                                    }];
    }
}</code>

 

То есть, точно такая же проверка, только в случае с видео — открываем его для просмотра и тут же сохраняем в устройстве.

 

Все это прекрасно, но у вас наверное масса ошибок в проекте. Чтобы их избежать нужно импортировать три фреймверка:

<code data-result="[object Object]">#import &lt;MobileCoreServices/MobileCoreServices.h&gt;
#import &lt;AssetsLibrary/AssetsLibrary.h&gt;
#import &lt;MediaPlayer/MediaPlayer.h&gt;</code>

А в проект добавить:

  • MobileCoreServices — добавляет доступ к kUTTypeMovie для записи видео с камеры
  • MediaPlayer — воспроизведение видео
  • AssetsLibrary — сохранение видео-файлов в галерею устройства