mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-30 00:02:47 +00:00
1307 lines
49 KiB
Objective-C
1307 lines
49 KiB
Objective-C
/*
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#import "MediaPickerViewController.h"
|
|
|
|
#import <Photos/Photos.h>
|
|
|
|
#import <MediaPlayer/MediaPlayer.h>
|
|
|
|
static void *CapturingStillImageContext = &CapturingStillImageContext;
|
|
static void *RecordingContext = &RecordingContext;
|
|
|
|
NSString* const recentItemCollectionViewCellId = @"recentItemCollectionViewCellId";
|
|
|
|
@interface MediaPickerViewController ()
|
|
{
|
|
BOOL isVideoCaptureMode;
|
|
|
|
AVCaptureSession *captureSession;
|
|
AVCaptureDeviceInput *frontCameraInput;
|
|
AVCaptureDeviceInput *backCameraInput;
|
|
AVCaptureDeviceInput *currentCameraInput;
|
|
|
|
AVCaptureMovieFileOutput *movieFileOutput;
|
|
AVCaptureStillImageOutput *stillImageOutput;
|
|
|
|
AVCaptureVideoPreviewLayer *cameraPreviewLayer;
|
|
Boolean canToggleCamera;
|
|
|
|
NSURL *outputVideoFileURL;
|
|
MPMoviePlayerController *videoPlayer;
|
|
|
|
dispatch_queue_t cameraQueue;
|
|
|
|
BOOL lockInterfaceRotation;
|
|
|
|
MXKAlert *alert;
|
|
|
|
PHFetchResult *assetsFetchResult;
|
|
|
|
NSInteger currentSelectedAsset;
|
|
NSMutableArray *selectedAssets;
|
|
|
|
UIImagePickerController *fullLibraryPicker;
|
|
MXKImageView* imageValidationView;
|
|
}
|
|
|
|
@property (nonatomic) AVCaptureVideoOrientation previewOrientation;
|
|
@property (nonatomic) UIBackgroundTaskIdentifier backgroundRecordingID;
|
|
|
|
@end
|
|
|
|
@implementation MediaPickerViewController
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (UINib *)nib
|
|
{
|
|
return [UINib nibWithNibName:NSStringFromClass([MediaPickerViewController class])
|
|
bundle:[NSBundle bundleForClass:[MediaPickerViewController class]]];
|
|
}
|
|
|
|
+ (instancetype)mediaPickerViewController
|
|
{
|
|
return [[[self class] alloc] initWithNibName:NSStringFromClass([MediaPickerViewController class])
|
|
bundle:[NSBundle bundleForClass:[MediaPickerViewController class]]];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
// Do any additional setup after loading the view, typically from a nib.
|
|
|
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)];
|
|
|
|
cameraQueue = dispatch_queue_create("media.picker.vc.camera", NULL);
|
|
canToggleCamera = YES;
|
|
|
|
currentSelectedAsset = -1;
|
|
|
|
// Register collection view cell class
|
|
[self.recentPicturesCollectionView registerClass:MXKMediaCollectionViewCell.class forCellWithReuseIdentifier:recentItemCollectionViewCellId];
|
|
|
|
// Adjust layout according to screen size
|
|
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
|
|
CGFloat maxSize = (screenSize.height > screenSize.width) ? screenSize.height : screenSize.width;
|
|
self.captureViewContainerHeightConstraint.constant = maxSize / 2;
|
|
[self.view layoutIfNeeded];
|
|
|
|
// Adjust camera preview ratio
|
|
self.previewOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];
|
|
|
|
// Set default media type
|
|
self.mediaTypes = _mediaTypes ? _mediaTypes : @[(NSString *)kUTTypeImage];
|
|
|
|
// Check camera access before set up AV capture
|
|
[self checkDeviceAuthorizationStatus];
|
|
[self setupAVCapture];
|
|
|
|
// Set button status
|
|
self.cameraRetakeButton.enabled = NO;
|
|
self.cameraChooseButton.enabled = NO;
|
|
self.libraryChooseButton.enabled = NO;
|
|
|
|
// Set camera preview background
|
|
self.cameraPreviewContainerView.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
|
|
|
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
|
|
|
|
// Set label and button text
|
|
self.libraryLabel.text = NSLocalizedStringFromTable(@"media_picker_choose_from_library", @"Vector", nil);
|
|
[self.cameraRetakeButton setTitle:NSLocalizedStringFromTable(@"media_picker_retake", @"Vector", nil) forState:UIControlStateNormal];
|
|
[self.cameraRetakeButton setTitle:NSLocalizedStringFromTable(@"media_picker_retake", @"Vector", nil) forState:UIControlStateHighlighted];
|
|
[self.libraryOpenButton setTitle:NSLocalizedStringFromTable(@"media_picker_library", @"Vector", nil) forState:UIControlStateNormal];
|
|
[self.libraryOpenButton setTitle:NSLocalizedStringFromTable(@"media_picker_library", @"Vector", nil) forState:UIControlStateHighlighted];
|
|
self.selectionButtonCustomLabel = _selectionButtonCustomLabel ? _selectionButtonCustomLabel : NSLocalizedStringFromTable(@"media_picker_choose", @"Vector", nil);
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
cameraQueue = nil;
|
|
}
|
|
|
|
- (void)didReceiveMemoryWarning
|
|
{
|
|
[super didReceiveMemoryWarning];
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
{
|
|
[super viewDidAppear:animated];
|
|
}
|
|
|
|
- (BOOL)shouldAutorotate
|
|
{
|
|
// Disable autorotation of the interface when recording is in progress.
|
|
return !lockInterfaceRotation;
|
|
}
|
|
|
|
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
|
{
|
|
return UIInterfaceOrientationMaskAll;
|
|
}
|
|
|
|
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
|
|
{
|
|
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
|
|
// Hide camera preview during transition
|
|
cameraPreviewLayer.hidden = YES;
|
|
[self.cameraActivityIndicator startAnimating];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
self.previewOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];
|
|
|
|
// Show camera preview with delay to hide awful animation
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
cameraPreviewLayer.hidden = NO;
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
// The following methods are deprecated since iOS 8
|
|
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
|
{
|
|
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
|
|
|
|
[[cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)toInterfaceOrientation];
|
|
}
|
|
|
|
- (void)checkDeviceAuthorizationStatus
|
|
{
|
|
NSString *mediaType = AVMediaTypeVideo;
|
|
|
|
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
|
|
if (!granted)
|
|
{
|
|
// Not granted access to mediaType
|
|
__weak typeof(self) weakSelf = self;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
alert = [[MXKAlert alloc] initWithTitle:nil message:NSLocalizedStringFromTable(@"camera_access_not_granted", @"Vector", nil) style:MXKAlertStyleAlert];
|
|
alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
|
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
|
strongSelf->alert = nil;
|
|
}];
|
|
[alert showInViewController:self];
|
|
});
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setMediaTypes:(NSArray *)mediaTypes
|
|
{
|
|
_mediaTypes = mediaTypes;
|
|
|
|
// Retrieve recents snapshot for the selected media types
|
|
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumRecentlyAdded options:nil];
|
|
|
|
// Set up fetch options.
|
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
|
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
|
if ([mediaTypes indexOfObject:(NSString *)kUTTypeImage] != NSNotFound)
|
|
{
|
|
if ([mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
|
|
{
|
|
options.predicate = [NSPredicate predicateWithFormat:@"(mediaType = %d) || (mediaType = %d)", PHAssetMediaTypeImage, PHAssetMediaTypeVideo];
|
|
self.cameraModeButton.hidden = NO;
|
|
isVideoCaptureMode = NO;
|
|
|
|
self.captureLabel.text = NSLocalizedStringFromTable(@"media_picker_both_capture_title", @"Vector", nil);
|
|
}
|
|
else
|
|
{
|
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %d",PHAssetMediaTypeImage];
|
|
self.cameraModeButton.hidden = YES;
|
|
isVideoCaptureMode = NO;
|
|
|
|
self.captureLabel.text = NSLocalizedStringFromTable(@"media_picker_picture_capture_title", @"Vector", nil);
|
|
}
|
|
}
|
|
else if ([mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound)
|
|
{
|
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %d",PHAssetMediaTypeVideo];
|
|
self.cameraModeButton.hidden = YES;
|
|
isVideoCaptureMode = YES;
|
|
|
|
self.captureLabel.text = NSLocalizedStringFromTable(@"media_picker_video_capture_title", @"Vector", nil);
|
|
}
|
|
|
|
// Only one album is expected
|
|
if (smartAlbums.count)
|
|
{
|
|
PHAssetCollection *assetCollection = smartAlbums[0];
|
|
assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
|
|
|
NSLog(@"[MediaPickerVC] lists %tu assets that were recently added to the photo library", assetsFetchResult.count);
|
|
}
|
|
|
|
if (assetsFetchResult.count)
|
|
{
|
|
self.recentPicturesCollectionView.hidden = NO;
|
|
self.recentPictureCollectionViewHeightConstraint.constant = 130;
|
|
self.libraryChooseButton.hidden = NO;
|
|
|
|
selectedAssets = [NSMutableArray arrayWithCapacity:assetsFetchResult.count];
|
|
for (NSUInteger index = 0; index < assetsFetchResult.count; index++)
|
|
{
|
|
[selectedAssets addObject:@NO];
|
|
}
|
|
|
|
[self.recentPicturesCollectionView reloadData];
|
|
}
|
|
else
|
|
{
|
|
self.recentPicturesCollectionView.hidden = YES;
|
|
self.recentPictureCollectionViewHeightConstraint.constant = 0;
|
|
self.libraryChooseButton.hidden = YES;
|
|
selectedAssets = nil;
|
|
}
|
|
currentSelectedAsset = -1;
|
|
}
|
|
|
|
- (void)setMultipleSelections:(BOOL)multipleSelections
|
|
{
|
|
_multipleSelections = multipleSelections;
|
|
|
|
if (multipleSelections == NO)
|
|
{
|
|
// Flush current selection if any
|
|
currentSelectedAsset = -1;
|
|
|
|
for (NSUInteger index = 0; index < selectedAssets.count; index++)
|
|
{
|
|
selectedAssets[index] = @NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setSelectionButtonCustomLabel:(NSString *)selectionButtonCustomLabel
|
|
{
|
|
_selectionButtonCustomLabel = selectionButtonCustomLabel;
|
|
|
|
[self.cameraChooseButton setTitle:selectionButtonCustomLabel forState:UIControlStateNormal];
|
|
[self.cameraChooseButton setTitle:selectionButtonCustomLabel forState:UIControlStateHighlighted];
|
|
[self.libraryChooseButton setTitle:selectionButtonCustomLabel forState:UIControlStateNormal];
|
|
[self.libraryChooseButton setTitle:selectionButtonCustomLabel forState:UIControlStateHighlighted];
|
|
}
|
|
|
|
- (void)reset
|
|
{
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
|
|
if (videoPlayer)
|
|
{
|
|
[videoPlayer stop];
|
|
|
|
[videoPlayer.view removeFromSuperview];
|
|
videoPlayer = nil;
|
|
}
|
|
|
|
if (outputVideoFileURL)
|
|
{
|
|
[[NSFileManager defaultManager] removeItemAtURL:outputVideoFileURL error:nil];
|
|
outputVideoFileURL = nil;
|
|
}
|
|
|
|
self.cameraCaptureImageView.image = nil;
|
|
|
|
self.cameraCaptureContainerView.hidden = YES;
|
|
|
|
self.cameraModeButton.enabled = YES;
|
|
self.cameraSwitchButton.enabled = YES;
|
|
|
|
self.cameraChooseButton.enabled = NO;
|
|
self.cameraRetakeButton.enabled = NO;
|
|
|
|
if (isVideoCaptureMode)
|
|
{
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_record"] forState:UIControlStateNormal];
|
|
}
|
|
self.cameraCaptureButton.enabled = YES;
|
|
}
|
|
|
|
#pragma mark - Override MXKViewController
|
|
|
|
- (void)destroy
|
|
{
|
|
[self stopAVCapture];
|
|
|
|
[self reset];
|
|
|
|
[self dismissImageValidationView];
|
|
[self dismissMediaPicker];
|
|
|
|
cameraQueue = nil;
|
|
|
|
[super destroy];
|
|
}
|
|
|
|
#pragma mark - Action
|
|
|
|
- (IBAction)onButtonPressed:(id)sender
|
|
{
|
|
if (sender == self.navigationItem.leftBarButtonItem)
|
|
{
|
|
// Cancel has been pressed
|
|
[self withdrawViewControllerAnimated:YES completion:nil];
|
|
}
|
|
else if (sender == self.cameraModeButton)
|
|
{
|
|
[self toggleCaptureMode];
|
|
}
|
|
else if (sender == self.cameraSwitchButton)
|
|
{
|
|
[self toggleCamera];
|
|
}
|
|
else if (sender == self.cameraCaptureButton)
|
|
{
|
|
if (isVideoCaptureMode)
|
|
{
|
|
if (outputVideoFileURL)
|
|
{
|
|
// Play/Stop captured video
|
|
[self controlVideoPlayer];
|
|
}
|
|
else
|
|
{
|
|
// Record a new video
|
|
[self toggleMovieRecording];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self snapStillImage];
|
|
}
|
|
}
|
|
else if (sender == self.cameraRetakeButton)
|
|
{
|
|
[self reset];
|
|
}
|
|
else if (sender == self.cameraChooseButton)
|
|
{
|
|
self.cameraChooseButton.enabled = NO;
|
|
[self.cameraActivityIndicator startAnimating];
|
|
|
|
if (outputVideoFileURL)
|
|
{
|
|
// Reset here output video url attribute to not remove the temporary file while the delegate uses it.
|
|
// We let the delegate remove the temporary file after use...
|
|
NSURL *selectedVideoURL = outputVideoFileURL;
|
|
outputVideoFileURL = nil;
|
|
[self.delegate mediaPickerController:self didSelectVideo:selectedVideoURL isCameraRecording:YES];
|
|
}
|
|
else if (self.cameraCaptureImageView.image)
|
|
{
|
|
// Send the original image
|
|
[self.delegate mediaPickerController:self didSelectImage:self.cameraCaptureImageView.image withURL:nil];
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"[MediaPickerVC] Selection is empty");
|
|
self.cameraChooseButton.enabled = YES;
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
}
|
|
}
|
|
else if (sender == self.libraryOpenButton)
|
|
{
|
|
// Open media gallery
|
|
fullLibraryPicker = [[UIImagePickerController alloc] init];
|
|
fullLibraryPicker.delegate = self;
|
|
fullLibraryPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
|
fullLibraryPicker.allowsEditing = NO;
|
|
fullLibraryPicker.mediaTypes = self.mediaTypes;
|
|
[self presentViewController:fullLibraryPicker animated:YES completion:nil];
|
|
}
|
|
else if (sender == self.libraryChooseButton && selectedAssets)
|
|
{
|
|
self.libraryChooseButton.enabled = NO;
|
|
|
|
if ([self.delegate respondsToSelector:@selector(mediaPickerController:didSelectAssets:)])
|
|
{
|
|
NSMutableArray *array = [NSMutableArray array];
|
|
for (NSUInteger index = 0; index < selectedAssets.count; index++)
|
|
{
|
|
if ([selectedAssets[index] boolValue])
|
|
{
|
|
PHAsset *asset = assetsFetchResult[index];
|
|
[array addObject:asset];
|
|
|
|
// Reset selection
|
|
selectedAssets[index] = @NO;
|
|
}
|
|
}
|
|
|
|
[self.delegate mediaPickerController:self didSelectAssets:array];
|
|
}
|
|
else
|
|
{
|
|
PHContentEditingInputRequestOptions *editOptions = [[PHContentEditingInputRequestOptions alloc] init];
|
|
for (NSUInteger index = 0; index < selectedAssets.count; index++)
|
|
{
|
|
if ([selectedAssets[index] boolValue])
|
|
{
|
|
PHAsset *asset = assetsFetchResult[index];
|
|
[asset requestContentEditingInputWithOptions:editOptions
|
|
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
|
|
|
|
if (contentEditingInput.mediaType == PHAssetMediaTypeImage)
|
|
{
|
|
// Here the fullSizeImageURL is related to a local file path
|
|
NSData *data = [NSData dataWithContentsOfURL:contentEditingInput.fullSizeImageURL];
|
|
UIImage *image = [UIImage imageWithData:data];
|
|
|
|
[self.delegate mediaPickerController:self didSelectImage:image withURL:contentEditingInput.fullSizeImageURL];
|
|
}
|
|
else if (contentEditingInput.mediaType == PHAssetMediaTypeVideo)
|
|
{
|
|
if ([contentEditingInput.avAsset isKindOfClass:[AVURLAsset class]])
|
|
{
|
|
AVURLAsset *avURLAsset = (AVURLAsset*)contentEditingInput.avAsset;
|
|
[self.delegate mediaPickerController:self didSelectVideo:[avURLAsset URL] isCameraRecording:NO];
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"[MediaPickerVC] Selected video asset is not initialized from an URL!");
|
|
}
|
|
}
|
|
}];
|
|
|
|
// Reset selection
|
|
selectedAssets[index] = @NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset
|
|
currentSelectedAsset = -1;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Capture handling methods
|
|
|
|
- (void)setPreviewOrientation:(AVCaptureVideoOrientation)previewOrientation
|
|
{
|
|
// Check whether the preview ratio must be inverted
|
|
CGFloat ratio = 0.0;
|
|
switch (previewOrientation)
|
|
{
|
|
case AVCaptureVideoOrientationPortrait:
|
|
case AVCaptureVideoOrientationPortraitUpsideDown:
|
|
{
|
|
if (self.cameraPreviewContainerAspectRatio.multiplier > 1)
|
|
{
|
|
ratio = (1 / self.cameraPreviewContainerAspectRatio.multiplier);
|
|
}
|
|
break;
|
|
}
|
|
case AVCaptureVideoOrientationLandscapeRight:
|
|
case AVCaptureVideoOrientationLandscapeLeft:
|
|
{
|
|
if (self.cameraPreviewContainerAspectRatio.multiplier < 1)
|
|
{
|
|
ratio = (1 / self.cameraPreviewContainerAspectRatio.multiplier);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ratio)
|
|
{
|
|
// Replace the current ratio constraint by a new one
|
|
if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)])
|
|
{
|
|
[NSLayoutConstraint deactivateConstraints:@[self.cameraPreviewContainerAspectRatio]];
|
|
}
|
|
else
|
|
{
|
|
[self.view removeConstraint:self.cameraPreviewContainerAspectRatio];
|
|
}
|
|
|
|
self.cameraPreviewContainerAspectRatio = [NSLayoutConstraint constraintWithItem:self.cameraPreviewContainerView
|
|
attribute:NSLayoutAttributeWidth
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.cameraPreviewContainerView
|
|
attribute:NSLayoutAttributeHeight
|
|
multiplier:ratio
|
|
constant:0.0f];
|
|
|
|
if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)])
|
|
{
|
|
[NSLayoutConstraint activateConstraints:@[self.cameraPreviewContainerAspectRatio]];
|
|
}
|
|
else
|
|
{
|
|
[self.view addConstraint:self.cameraPreviewContainerAspectRatio];
|
|
}
|
|
|
|
// Force layout refresh
|
|
[self.view layoutIfNeeded];
|
|
}
|
|
|
|
// Refresh camera preview layer
|
|
if (cameraPreviewLayer)
|
|
{
|
|
[[cameraPreviewLayer connection] setVideoOrientation:previewOrientation];
|
|
cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds;
|
|
}
|
|
|
|
_previewOrientation = previewOrientation;
|
|
}
|
|
|
|
- (void)setupAVCapture
|
|
{
|
|
if (captureSession)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Attemping to setup AVCapture when it is already started!");
|
|
return;
|
|
}
|
|
|
|
[self.cameraActivityIndicator startAnimating];
|
|
|
|
dispatch_async(cameraQueue, ^{
|
|
|
|
// Get the Camera Device
|
|
AVCaptureDevice *frontCamera = nil;
|
|
AVCaptureDevice *backCamera = nil;
|
|
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
|
for (AVCaptureDevice *thisCamera in cameras)
|
|
{
|
|
if (thisCamera.position == AVCaptureDevicePositionFront)
|
|
{
|
|
frontCamera = thisCamera;
|
|
}
|
|
else if (thisCamera.position == AVCaptureDevicePositionBack)
|
|
{
|
|
backCamera = thisCamera;
|
|
}
|
|
|
|
NSError *lockError = nil;
|
|
[thisCamera lockForConfiguration:&lockError];
|
|
if (!lockError)
|
|
{
|
|
if ([thisCamera isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus])
|
|
{
|
|
[thisCamera setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
|
|
}
|
|
else if ([thisCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus])
|
|
{
|
|
[thisCamera setFocusMode:AVCaptureFocusModeAutoFocus];
|
|
}
|
|
|
|
if ([thisCamera isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance])
|
|
{
|
|
[thisCamera setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
|
|
}
|
|
if ([thisCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
|
|
{
|
|
[thisCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
|
|
}
|
|
|
|
[thisCamera unlockForConfiguration];
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"[MediaPickerVC] Failed to take out lock on camera. Device not setup properly.");
|
|
}
|
|
}
|
|
|
|
currentCameraInput = nil;
|
|
NSError *error = nil;
|
|
if (frontCamera)
|
|
{
|
|
frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:frontCamera error:&error];
|
|
if (error)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Error: %@", error);
|
|
}
|
|
|
|
if (frontCameraInput == nil)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Error creating front camera capture input");
|
|
}
|
|
else
|
|
{
|
|
currentCameraInput = frontCameraInput;
|
|
}
|
|
}
|
|
|
|
if (backCamera)
|
|
{
|
|
error = nil;
|
|
backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:backCamera error:&error];
|
|
if (error)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Error: %@", error);
|
|
}
|
|
|
|
if (backCameraInput == nil)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Error creating back camera capture input");
|
|
}
|
|
else
|
|
{
|
|
currentCameraInput = backCameraInput;
|
|
}
|
|
}
|
|
|
|
self.cameraSwitchButton.hidden = (!frontCamera || !backCamera);
|
|
|
|
if (currentCameraInput)
|
|
{
|
|
// Create the AVCapture Session
|
|
captureSession = [[AVCaptureSession alloc] init];
|
|
|
|
[captureSession setSessionPreset:AVCaptureSessionPreset640x480];
|
|
|
|
cameraPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
|
|
cameraPreviewLayer.masksToBounds = NO;
|
|
cameraPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
|
cameraPreviewLayer.backgroundColor = [[UIColor blackColor] CGColor];
|
|
cameraPreviewLayer.borderWidth = 2;
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[[cameraPreviewLayer connection] setVideoOrientation:self.previewOrientation];
|
|
cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds;
|
|
cameraPreviewLayer.hidden = YES;
|
|
|
|
[self.cameraPreviewContainerView.layer addSublayer:cameraPreviewLayer];
|
|
|
|
});
|
|
|
|
[captureSession addInput:currentCameraInput];
|
|
|
|
AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
|
|
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
|
|
|
|
if (error)
|
|
{
|
|
NSLog(@"[MediaPickerVC] Error: %@", error);
|
|
}
|
|
|
|
if ([captureSession canAddInput:audioDeviceInput])
|
|
{
|
|
[captureSession addInput:audioDeviceInput];
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(caughtAVRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVCaptureSessionDidStartRunning:) name:AVCaptureSessionDidStartRunningNotification object:nil];
|
|
|
|
[captureSession startRunning];
|
|
|
|
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
|
|
if ([captureSession canAddOutput:movieFileOutput])
|
|
{
|
|
[captureSession addOutput:movieFileOutput];
|
|
AVCaptureConnection *connection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
|
|
if ([connection isVideoStabilizationSupported])
|
|
{
|
|
if ([connection respondsToSelector:@selector(setPreferredVideoStabilizationMode:)])
|
|
{
|
|
[connection setPreferredVideoStabilizationMode:YES];
|
|
}
|
|
else
|
|
{
|
|
[connection setEnablesVideoStabilizationWhenAvailable:YES];
|
|
}
|
|
}
|
|
}
|
|
[movieFileOutput addObserver:self forKeyPath:@"recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
|
|
|
|
stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
|
|
if ([captureSession canAddOutput:stillImageOutput])
|
|
{
|
|
[stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
|
|
[captureSession addOutput:stillImageOutput];
|
|
}
|
|
[stillImageOutput addObserver:self forKeyPath:@"capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext];
|
|
}
|
|
else
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
- (void)stopAVCapture
|
|
{
|
|
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
|
|
|
if (captureSession)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVCaptureSessionDidStopRunning:) name:AVCaptureSessionDidStopRunningNotification object:nil];
|
|
|
|
[captureSession stopRunning];
|
|
}
|
|
}
|
|
|
|
- (void)tearDownAVCapture
|
|
{
|
|
frontCameraInput = nil;
|
|
backCameraInput = nil;
|
|
captureSession = nil;
|
|
|
|
if (movieFileOutput)
|
|
{
|
|
[movieFileOutput removeObserver:self forKeyPath:@"recording" context:RecordingContext];
|
|
movieFileOutput = nil;
|
|
}
|
|
|
|
if (stillImageOutput)
|
|
{
|
|
[stillImageOutput removeObserver:self forKeyPath:@"capturingStillImage" context:CapturingStillImageContext];
|
|
stillImageOutput = nil;
|
|
}
|
|
|
|
currentCameraInput = nil;
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionRuntimeErrorNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionDidStartRunningNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionDidStopRunningNotification object:nil];
|
|
}
|
|
|
|
- (void)caughtAVRuntimeError:(NSNotification*)note
|
|
{
|
|
NSError *error = [[note userInfo] objectForKey:AVCaptureSessionErrorKey];
|
|
NSLog(@"[MediaPickerVC] AV Session Error: %@", error);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self tearDownAVCapture];
|
|
// Retry
|
|
[self performSelector:@selector(setupAVCapture) withObject:nil afterDelay:1.0];
|
|
});
|
|
}
|
|
|
|
- (void)AVCaptureSessionDidStartRunning:(NSNotification*)note
|
|
{
|
|
// Show camera preview with delay to hide camera settlement
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
cameraPreviewLayer.hidden = NO;
|
|
|
|
});
|
|
}
|
|
|
|
- (void)AVCaptureSessionDidStopRunning:(NSNotification*)note
|
|
{
|
|
[self tearDownAVCapture];
|
|
}
|
|
|
|
- (void)toggleCaptureMode
|
|
{
|
|
if (isVideoCaptureMode)
|
|
{
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_capture"] forState:UIControlStateNormal];
|
|
[self.cameraModeButton setImage:[UIImage imageNamed:@"camera_video"] forState:UIControlStateNormal];
|
|
isVideoCaptureMode = NO;
|
|
}
|
|
else
|
|
{
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_record"] forState:UIControlStateNormal];
|
|
[self.cameraModeButton setImage:[UIImage imageNamed:@"camera_picture"] forState:UIControlStateNormal];
|
|
isVideoCaptureMode = YES;
|
|
}
|
|
}
|
|
|
|
- (void)toggleCamera
|
|
{
|
|
if (frontCameraInput && backCameraInput)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (!canToggleCamera)
|
|
{
|
|
return;
|
|
}
|
|
canToggleCamera = NO;
|
|
|
|
AVCaptureDeviceInput *newInput = nil;
|
|
AVCaptureDeviceInput *oldInput = nil;
|
|
if (currentCameraInput == frontCameraInput)
|
|
{
|
|
newInput = backCameraInput;
|
|
oldInput = frontCameraInput;
|
|
}
|
|
else
|
|
{
|
|
newInput = frontCameraInput;
|
|
oldInput = backCameraInput;
|
|
}
|
|
|
|
dispatch_async(cameraQueue, ^{
|
|
|
|
[captureSession beginConfiguration];
|
|
[captureSession removeInput:oldInput];
|
|
if ([captureSession canAddInput:newInput]) {
|
|
[captureSession addInput:newInput];
|
|
currentCameraInput = newInput;
|
|
}
|
|
[captureSession commitConfiguration];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self.cameraActivityIndicator stopAnimating];
|
|
cameraPreviewLayer.hidden = NO;
|
|
canToggleCamera = YES;
|
|
});
|
|
});
|
|
|
|
[self.cameraActivityIndicator startAnimating];
|
|
cameraPreviewLayer.hidden = YES;
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)toggleMovieRecording
|
|
{
|
|
self.cameraCaptureButton.enabled = NO;
|
|
|
|
dispatch_async(cameraQueue, ^{
|
|
if (![movieFileOutput isRecording])
|
|
{
|
|
lockInterfaceRotation = YES;
|
|
|
|
if ([[UIDevice currentDevice] isMultitaskingSupported])
|
|
{
|
|
// Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until the App returns to the foreground unless you request background execution time.
|
|
[self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
|
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundRecordingID];
|
|
self.backgroundRecordingID = UIBackgroundTaskInvalid;
|
|
|
|
NSLog(@"[MediaPickerVC] pauseInBackgroundTask : %08lX expired", (unsigned long)self.backgroundRecordingID);
|
|
}]];
|
|
|
|
NSLog(@"[MediaPickerVC] pauseInBackgroundTask : %08lX starts", (unsigned long)self.backgroundRecordingID);
|
|
}
|
|
|
|
// Update the orientation on the movie file output video connection before starting recording.
|
|
[[movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[cameraPreviewLayer connection] videoOrientation]];
|
|
|
|
// Turning OFF flash for video recording
|
|
[MediaPickerViewController setFlashMode:AVCaptureFlashModeOff forDevice:[currentCameraInput device]];
|
|
|
|
// Start recording to a temporary file.
|
|
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[@"movie" stringByAppendingPathExtension:@"mov"]];
|
|
[movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
|
|
}
|
|
else
|
|
{
|
|
[movieFileOutput stopRecording];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)controlVideoPlayer
|
|
{
|
|
// Check whether the video player is already playing
|
|
if (videoPlayer.view.superview)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
|
|
|
|
[videoPlayer stop];
|
|
[videoPlayer.view removeFromSuperview];
|
|
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_play"] forState:UIControlStateNormal];
|
|
}
|
|
else
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(moviePlayerPlaybackDidFinishNotification:)
|
|
name:MPMoviePlayerPlaybackDidFinishNotification
|
|
object:nil];
|
|
|
|
CGRect frame = self.cameraCaptureImageView.frame;
|
|
frame.origin = CGPointZero;
|
|
videoPlayer.view.frame = frame;
|
|
videoPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
|
|
[self.cameraCaptureImageView addSubview:videoPlayer.view];
|
|
|
|
[videoPlayer play];
|
|
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_stop"] forState:UIControlStateNormal];
|
|
}
|
|
}
|
|
|
|
- (void)snapStillImage
|
|
{
|
|
dispatch_async(cameraQueue, ^{
|
|
// Update the orientation on the still image output video connection before capturing.
|
|
[[stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[cameraPreviewLayer connection] videoOrientation]];
|
|
|
|
// Flash set to Auto for Still Capture
|
|
[MediaPickerViewController setFlashMode:AVCaptureFlashModeAuto forDevice:[currentCameraInput device]];
|
|
|
|
// Capture a still image.
|
|
[stillImageOutput captureStillImageAsynchronouslyFromConnection:[stillImageOutput connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
|
|
|
|
if (imageDataSampleBuffer)
|
|
{
|
|
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
|
|
|
|
self.cameraCaptureImageView.image = [[UIImage alloc] initWithData:imageData];
|
|
|
|
self.cameraCaptureContainerView.hidden = NO;
|
|
|
|
self.cameraRetakeButton.enabled = YES;
|
|
self.cameraChooseButton.enabled = YES;
|
|
|
|
self.cameraCaptureButton.enabled = NO;
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
+ (void)setFlashMode:(AVCaptureFlashMode)flashMode forDevice:(AVCaptureDevice *)device
|
|
{
|
|
if ([device hasFlash] && [device isFlashModeSupported:flashMode])
|
|
{
|
|
NSError *error = nil;
|
|
if ([device lockForConfiguration:&error])
|
|
{
|
|
[device setFlashMode:flashMode];
|
|
[device unlockForConfiguration];
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"[MediaPickerVC] %@", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)runStillImageCaptureAnimation
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[cameraPreviewLayer setOpacity:0.0];
|
|
|
|
[UIView animateWithDuration:.25 animations:^{
|
|
[cameraPreviewLayer setOpacity:1.0];
|
|
}];
|
|
});
|
|
}
|
|
|
|
#pragma mark - KVO
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
|
{
|
|
if (context == CapturingStillImageContext)
|
|
{
|
|
BOOL isCapturingStillImage = [change[NSKeyValueChangeNewKey] boolValue];
|
|
|
|
if (isCapturingStillImage)
|
|
{
|
|
[self runStillImageCaptureAnimation];
|
|
}
|
|
}
|
|
else if (context == RecordingContext)
|
|
{
|
|
BOOL isRecording = [change[NSKeyValueChangeNewKey] boolValue];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (isRecording)
|
|
{
|
|
self.cameraModeButton.enabled = NO;
|
|
self.cameraSwitchButton.enabled = NO;
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_stop"] forState:UIControlStateNormal];
|
|
self.cameraCaptureButton.enabled = YES;
|
|
}
|
|
else
|
|
{
|
|
[self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_play"] forState:UIControlStateNormal];
|
|
self.cameraCaptureButton.enabled = YES;
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
}
|
|
}
|
|
|
|
#pragma mark - File Output Delegate
|
|
|
|
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
|
|
{
|
|
if (error)
|
|
{
|
|
NSLog(@"[MediaPickerVC] %@", error);
|
|
}
|
|
|
|
self.cameraCaptureButton.enabled = NO;
|
|
|
|
lockInterfaceRotation = NO;
|
|
|
|
outputVideoFileURL = outputFileURL;
|
|
|
|
// Display first video frame
|
|
videoPlayer = [[MPMoviePlayerController alloc] initWithContentURL:outputVideoFileURL];
|
|
if (videoPlayer)
|
|
{
|
|
[videoPlayer setShouldAutoplay:NO];
|
|
videoPlayer.scalingMode = MPMovieScalingModeAspectFit;
|
|
videoPlayer.controlStyle = MPMovieControlStyleNone;
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(moviePlayerThumbnailImageRequestDidFinishNotification:)
|
|
name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
|
|
object:nil];
|
|
[videoPlayer requestThumbnailImagesAtTimes:@[@0.0f] timeOption:MPMovieTimeOptionNearestKeyFrame];
|
|
}
|
|
|
|
UIBackgroundTaskIdentifier backgroundRecordingID = [self backgroundRecordingID];
|
|
if (backgroundRecordingID != UIBackgroundTaskInvalid)
|
|
{
|
|
[[UIApplication sharedApplication] endBackgroundTask:backgroundRecordingID];
|
|
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
|
|
NSLog(@"[MediaPickerVC] >>>>> background pause task finished");
|
|
}
|
|
}
|
|
|
|
#pragma mark - Movie player observer
|
|
|
|
- (void)moviePlayerThumbnailImageRequestDidFinishNotification:(NSNotification *)notification
|
|
{
|
|
self.cameraCaptureImageView.image = [[notification userInfo] objectForKey:MPMoviePlayerThumbnailImageKey];
|
|
self.cameraCaptureContainerView.hidden = NO;
|
|
self.cameraRetakeButton.enabled = YES;
|
|
self.cameraChooseButton.enabled = YES;
|
|
|
|
self.cameraCaptureButton.enabled = YES;
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:nil];
|
|
}
|
|
|
|
- (void)moviePlayerPlaybackDidFinishNotification:(NSNotification *)notification
|
|
{
|
|
// Remove player view from superview
|
|
[self controlVideoPlayer];
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDataSource
|
|
|
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
{
|
|
return assetsFetchResult.count;
|
|
}
|
|
|
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
MXKMediaCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:recentItemCollectionViewCellId forIndexPath:indexPath];
|
|
|
|
if (indexPath.row < assetsFetchResult.count)
|
|
{
|
|
PHAsset *asset = assetsFetchResult[indexPath.row];
|
|
|
|
// Assets are display in full height in collection view
|
|
CGFloat collectionCellHeight = self.recentPictureCollectionViewHeightConstraint.constant;
|
|
// Request an image with the collection cell height by keeping ratio
|
|
CGSize cellSize = CGSizeMake((asset.pixelWidth * collectionCellHeight) / asset.pixelHeight, collectionCellHeight);
|
|
|
|
PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
|
|
option.synchronous = YES;
|
|
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:cellSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
cell.mxkImageView.image = result;
|
|
}];
|
|
|
|
cell.bottomLeftIcon.image = [UIImage imageNamed:@"selection_tick"];
|
|
cell.bottomLeftIcon.hidden = ![selectedAssets[indexPath.row] boolValue];
|
|
|
|
cell.topRightIcon.image = [UIImage imageNamed:@"icon_video"];
|
|
cell.topRightIcon.hidden = (asset.mediaType == PHAssetMediaTypeImage);
|
|
|
|
// Disable user interaction in mxkImageView, in order to let collection handle user selection
|
|
cell.mxkImageView.userInteractionEnabled = NO;
|
|
}
|
|
|
|
return cell;
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDelegate
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if (indexPath.row < assetsFetchResult.count)
|
|
{
|
|
if ([selectedAssets[indexPath.row] boolValue])
|
|
{
|
|
selectedAssets[indexPath.row] = @NO;
|
|
currentSelectedAsset = -1;
|
|
|
|
// Update attach button status by checking selected assets array
|
|
self.libraryChooseButton.enabled = NO;
|
|
for (NSNumber *number in selectedAssets)
|
|
{
|
|
if (number.boolValue)
|
|
{
|
|
self.libraryChooseButton.enabled = YES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selectedAssets[indexPath.row] = @YES;
|
|
if (!self.isMultipleSelections)
|
|
{
|
|
// Only one item can be selected at once
|
|
if (currentSelectedAsset != -1 && currentSelectedAsset < selectedAssets.count)
|
|
{
|
|
selectedAssets[currentSelectedAsset] = @NO;
|
|
// Refresh locally the table
|
|
[collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForRow:currentSelectedAsset inSection:0]]];
|
|
}
|
|
currentSelectedAsset = indexPath.row;
|
|
}
|
|
|
|
self.libraryChooseButton.enabled = YES;
|
|
}
|
|
|
|
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
|
|
|
|
// Refresh locally the table
|
|
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
|
|
}
|
|
}
|
|
|
|
#pragma mark - UICollectionViewDelegateFlowLayout
|
|
|
|
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if (indexPath.row < assetsFetchResult.count)
|
|
{
|
|
PHAsset *asset = assetsFetchResult[indexPath.row];
|
|
|
|
// Assets are display in full height in collection view
|
|
CGFloat collectionCellHeight = self.recentPictureCollectionViewHeightConstraint.constant;
|
|
CGSize cellSize = CGSizeMake((asset.pixelWidth * collectionCellHeight) / asset.pixelHeight, collectionCellHeight);
|
|
|
|
return cellSize;
|
|
}
|
|
return CGSizeZero;
|
|
}
|
|
|
|
#pragma mark - UIImagePickerControllerDelegate
|
|
|
|
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
|
|
{
|
|
[self dismissMediaPicker];
|
|
|
|
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
|
|
if ([mediaType isEqualToString:(NSString *)kUTTypeImage])
|
|
{
|
|
UIImage *selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
|
|
if (selectedImage)
|
|
{
|
|
// media picker does not offer a preview
|
|
// so add a preview to let the user validates his selection
|
|
__weak typeof(self) weakSelf = self;
|
|
|
|
imageValidationView = [[MXKImageView alloc] initWithFrame:CGRectZero];
|
|
imageValidationView.stretchable = YES;
|
|
|
|
// the user validates the image
|
|
[imageValidationView setRightButtonTitle:[NSBundle mxk_localizedStringForKey:@"ok"] handler:^(MXKImageView* imageView, NSString* buttonTitle) {
|
|
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
|
|
|
// Dismiss the image view
|
|
[strongSelf dismissImageValidationView];
|
|
|
|
// Send the original image by considering its asset url
|
|
NSURL *assetURL = [info valueForKey:UIImagePickerControllerReferenceURL];
|
|
[strongSelf.delegate mediaPickerController:strongSelf didSelectImage:selectedImage withURL:assetURL];
|
|
}];
|
|
|
|
// the user wants to use an other image
|
|
[imageValidationView setLeftButtonTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] handler:^(MXKImageView* imageView, NSString* buttonTitle) {
|
|
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
|
|
|
// dismiss the image view
|
|
[strongSelf dismissImageValidationView];
|
|
|
|
// Open again media gallery
|
|
strongSelf->fullLibraryPicker = [[UIImagePickerController alloc] init];
|
|
strongSelf->fullLibraryPicker.delegate = strongSelf;
|
|
strongSelf->fullLibraryPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
|
strongSelf->fullLibraryPicker.allowsEditing = NO;
|
|
strongSelf->fullLibraryPicker.mediaTypes = picker.mediaTypes;
|
|
|
|
[strongSelf presentViewController:strongSelf->fullLibraryPicker animated:YES completion:nil];
|
|
}];
|
|
|
|
imageValidationView.image = selectedImage;
|
|
[imageValidationView showFullScreen];
|
|
}
|
|
}
|
|
else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie])
|
|
{
|
|
NSURL* selectedVideo = [info objectForKey:UIImagePickerControllerMediaURL];
|
|
[self.delegate mediaPickerController:self didSelectVideo:selectedVideo isCameraRecording:NO];
|
|
}
|
|
}
|
|
|
|
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
|
{
|
|
[self dismissMediaPicker];
|
|
}
|
|
|
|
- (void)dismissMediaPicker
|
|
{
|
|
if (fullLibraryPicker)
|
|
{
|
|
fullLibraryPicker.delegate = nil;
|
|
|
|
[self dismissViewControllerAnimated:YES completion:^{
|
|
fullLibraryPicker = nil;
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)dismissImageValidationView
|
|
{
|
|
if (imageValidationView)
|
|
{
|
|
[imageValidationView dismissSelection];
|
|
[imageValidationView removeFromSuperview];
|
|
imageValidationView = nil;
|
|
}
|
|
}
|
|
|
|
@end
|