Merge pull request #10 from Lax/update

This commit is contained in:
Liu Lantao 2017-10-16 23:15:42 +08:00 committed by GitHub
commit 3f69e9478b
419 changed files with 31510 additions and 1575 deletions

View File

@ -0,0 +1,11 @@
//
// SampleCode.xcconfig
//
// The `SAMPLE_CODE_DISAMBIGUATOR` configuration is to make it easier to build
// and run a sample code project. Once you set your project's development team,
// you'll have a unique bundle identifier. This is because the bundle identifier
// is derived based on the 'SAMPLE_CODE_DISAMBIGUATOR' value. Do not use this
// approach in your own projects—it's only useful for sample code projects because
// they are frequently downloaded and don't have a development team set.
SAMPLE_CODE_DISAMBIGUATOR=${DEVELOPMENT_TEAM}

View File

@ -1,5 +1,5 @@
Sample code project: AVCam-iOS: Using AVFoundation to Capture Images and Movies
Version: 6.1
Version: 7.1
IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
@ -39,4 +39,4 @@ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2016 Apple Inc. All Rights Reserved.
Copyright (C) 2017 Apple Inc. All Rights Reserved.

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>

View File

@ -29,10 +29,12 @@
220626871A1E345E00A45150 /* AVCamCameraViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AVCamCameraViewController.m; sourceTree = "<group>"; };
22CA31B71B022D1300D2DE70 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = AVCam/Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
22CA31B91B0250C300D2DE70 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
3E25DCAD1F7EA483007EF37E /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../../LICENSE.txt; sourceTree = "<group>"; };
7A7444781CEE6B0F00C70C83 /* AVCamPhotoCaptureDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVCamPhotoCaptureDelegate.h; sourceTree = "<group>"; };
7A7444791CEE6B0F00C70C83 /* AVCamPhotoCaptureDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AVCamPhotoCaptureDelegate.m; sourceTree = "<group>"; };
7A74447B1CEE6B4B00C70C83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
7A74447D1CEE6B5900C70C83 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = SampleCode.xcconfig; path = ../Configuration/SampleCode.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -52,6 +54,8 @@
22CA31B91B0250C300D2DE70 /* README.md */,
2206265B1A1E330400A45150 /* AVCam */,
2206265A1A1E330400A45150 /* Products */,
DDF88DBAE19DC8C1451D62E5 /* Configuration */,
3E25DCAC1F7EA45D007EF37E /* LICENSE */,
);
sourceTree = "<group>";
};
@ -83,6 +87,23 @@
path = AVCam;
sourceTree = "<group>";
};
3E25DCAC1F7EA45D007EF37E /* LICENSE */ = {
isa = PBXGroup;
children = (
3E25DCAD1F7EA483007EF37E /* LICENSE.txt */,
);
name = LICENSE;
path = AVCam;
sourceTree = "<group>";
};
DDF88DBAE19DC8C1451D62E5 /* Configuration */ = {
isa = PBXGroup;
children = (
95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */,
);
name = Configuration;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -109,7 +130,8 @@
220626511A1E330400A45150 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Apple;
TargetAttributes = {
220626581A1E330400A45150 = {
CreatedOnToolsVersion = 6.1;
@ -178,21 +200,31 @@
/* Begin XCBuildConfiguration section */
2206267A1A1E330400A45150 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -212,7 +244,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -222,21 +254,31 @@
};
2206267B1A1E330400A45150 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
@ -249,7 +291,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -259,26 +301,32 @@
};
2206267D1A1E330400A45150 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AVCam/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam${SAMPLE_CODE_DISAMBIGUATOR}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
};
name = Debug;
};
2206267E1A1E330400A45150 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 95F1FD6060E3CAB64438CE51 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AVCam/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam${SAMPLE_CODE_DISAMBIGUATOR}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
};
name = Release;

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "220626581A1E330400A45150"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Objective-C.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "220626581A1E330400A45150"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Objective-C.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "220626581A1E330400A45150"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Objective-C.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "220626581A1E330400A45150"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Objective-C.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application delegate.
Abstract:
Application delegate.
*/
@import UIKit;

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application delegate.
Abstract:
Application delegate.
*/
#import "AVCamAppDelegate.h"

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
View controller for camera interface.
Abstract:
View controller for camera interface.
*/
@import UIKit;

View File

@ -1,11 +1,9 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
View controller for camera interface.
Abstract:
View controller for camera interface.
*/
@import AVFoundation;
@import Photos;
@ -31,6 +29,11 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
AVCamLivePhotoModeOff
};
typedef NS_ENUM( NSInteger, AVCamDepthDataDeliveryMode ) {
AVCamDepthDataDeliveryModeOn,
AVCamDepthDataDeliveryModeOff
};
@interface AVCaptureDeviceDiscoverySession (Utilities)
- (NSInteger)uniqueDevicePositionsCount;
@ -76,6 +79,8 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
@property (nonatomic, weak) IBOutlet UIButton *livePhotoModeButton;
@property (nonatomic) AVCamLivePhotoMode livePhotoMode;
@property (nonatomic, weak) IBOutlet UILabel *capturingLivePhotoLabel;
@property (nonatomic, weak) IBOutlet UIButton *depthDataDeliveryButton;
@property (nonatomic) AVCamDepthDataDeliveryMode depthDataDeliveryMode;
@property (nonatomic) AVCapturePhotoOutput *photoOutput;
@property (nonatomic) NSMutableDictionary<NSNumber *, AVCamPhotoCaptureDelegate *> *inProgressPhotoCaptureDelegates;
@ -104,12 +109,13 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.photoButton.enabled = NO;
self.livePhotoModeButton.enabled = NO;
self.captureModeControl.enabled = NO;
self.depthDataDeliveryButton.enabled = NO;
// Create the AVCaptureSession.
self.session = [[AVCaptureSession alloc] init];
// Create a device discovery session.
NSArray<AVCaptureDeviceType> *deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInDuoCamera];
NSArray<AVCaptureDeviceType> *deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInDualCamera];
self.videoDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
// Set up the preview view.
@ -276,7 +282,7 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
// Add video input.
// Choose the back dual camera if available, otherwise default to a wide angle camera.
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInDuoCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInDualCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
if ( ! videoDevice ) {
// If the back dual camera is not available, default to the back wide angle camera.
videoDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
@ -345,7 +351,11 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.photoOutput.highResolutionCaptureEnabled = YES;
self.photoOutput.livePhotoCaptureEnabled = self.photoOutput.livePhotoCaptureSupported;
self.photoOutput.depthDataDeliveryEnabled = self.photoOutput.depthDataDeliverySupported;
self.livePhotoMode = self.photoOutput.livePhotoCaptureSupported ? AVCamLivePhotoModeOn : AVCamLivePhotoModeOff;
self.depthDataDeliveryMode = self.photoOutput.depthDataDeliverySupported ? AVCamDepthDataDeliveryModeOn : AVCamDepthDataDeliveryModeOff;
self.inProgressPhotoCaptureDelegates = [NSMutableDictionary dictionary];
self.inProgressLivePhotoCapturesCount = 0;
@ -405,7 +415,6 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
[self.session beginConfiguration];
[self.session removeOutput:self.movieFileOutput];
self.session.sessionPreset = AVCaptureSessionPresetPhoto;
[self.session commitConfiguration];
self.movieFileOutput = nil;
@ -417,10 +426,22 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.livePhotoModeButton.hidden = NO;
} );
}
if ( self.photoOutput.depthDataDeliverySupported ) {
self.photoOutput.depthDataDeliveryEnabled = YES;
dispatch_async( dispatch_get_main_queue(), ^{
self.depthDataDeliveryButton.hidden = NO;
self.depthDataDeliveryButton.enabled = YES;
} );
}
[self.session commitConfiguration];
} );
}
else if ( captureModeControl.selectedSegmentIndex == AVCamCaptureModeMovie ) {
self.livePhotoModeButton.hidden = YES;
self.depthDataDeliveryButton.hidden = YES;
dispatch_async( self.sessionQueue, ^{
AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
@ -468,7 +489,7 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
case AVCaptureDevicePositionUnspecified:
case AVCaptureDevicePositionFront:
preferredPosition = AVCaptureDevicePositionBack;
preferredDeviceType = AVCaptureDeviceTypeBuiltInDuoCamera;
preferredDeviceType = AVCaptureDeviceTypeBuiltInDualCamera;
break;
case AVCaptureDevicePositionBack:
preferredPosition = AVCaptureDevicePositionFront;
@ -523,12 +544,13 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
}
/*
Set Live Photo capture enabled if it is supported. When changing cameras, the
`livePhotoCaptureEnabled` property of the AVCapturePhotoOutput gets set to NO when
Set Live Photo capture and depth data delivery if it is supported. When changing cameras, the
`livePhotoCaptureEnabled` and `depthDataDeliveryEnabled` properties of the AVCapturePhotoOutput gets set to NO when
a video device is disconnected from the session. After the new video device is
added to the session, re-enable Live Photo capture on the AVCapturePhotoOutput if it is supported.
added to the session, re-enable Live Photo capture and depth data delivery if they are supported.
*/
self.photoOutput.livePhotoCaptureEnabled = self.photoOutput.livePhotoCaptureSupported;
self.photoOutput.depthDataDeliveryEnabled = self.photoOutput.depthDataDeliverySupported;
[self.session commitConfiguration];
}
@ -539,6 +561,8 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.photoButton.enabled = YES;
self.livePhotoModeButton.enabled = YES;
self.captureModeControl.enabled = YES;
self.depthDataDeliveryButton.enabled = self.photoOutput.isDepthDataDeliveryEnabled;
self.depthDataDeliveryButton.hidden = !self.photoOutput.depthDataDeliverySupported;
} );
} );
}
@ -595,9 +619,18 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
AVCaptureConnection *photoOutputConnection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
photoOutputConnection.videoOrientation = videoPreviewLayerVideoOrientation;
// Capture a JPEG photo with flash set to auto and high resolution photo enabled.
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
AVCapturePhotoSettings *photoSettings;
// Capture HEIF photo when supported, with flash set to auto and high resolution photo enabled.
if ( [self.photoOutput.availablePhotoCodecTypes containsObject:AVVideoCodecTypeHEVC] ) {
photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:@{ AVVideoCodecKey : AVVideoCodecTypeHEVC }];
}
else {
photoSettings = [AVCapturePhotoSettings photoSettings];
}
if ( self.videoDeviceInput.device.isFlashAvailable ) {
photoSettings.flashMode = AVCaptureFlashModeAuto;
}
photoSettings.highResolutionPhotoEnabled = YES;
if ( photoSettings.availablePreviewPhotoPixelFormatTypes.count > 0 ) {
photoSettings.previewPhotoFormat = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : photoSettings.availablePreviewPhotoPixelFormatTypes.firstObject };
@ -608,6 +641,12 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
photoSettings.livePhotoMovieFileURL = [NSURL fileURLWithPath:livePhotoMovieFilePath];
}
if ( self.depthDataDeliveryMode == AVCamDepthDataDeliveryModeOn && self.photoOutput.isDepthDataDeliverySupported ) {
photoSettings.depthDataDeliveryEnabled = YES;
} else {
photoSettings.depthDataDeliveryEnabled = NO;
}
// Use a separate object for the photo capture delegate to isolate each capture life cycle.
AVCamPhotoCaptureDelegate *photoCaptureDelegate = [[AVCamPhotoCaptureDelegate alloc] initWithRequestedPhotoSettings:photoSettings willCapturePhotoAnimation:^{
dispatch_async( dispatch_get_main_queue(), ^{
@ -616,7 +655,7 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.previewView.videoPreviewLayer.opacity = 1.0;
}];
} );
} capturingLivePhoto:^( BOOL capturing ) {
} livePhotoCaptureHandler:^( BOOL capturing ) {
/*
Because Live Photo captures can overlap, we need to keep track of the
number of in progress Live Photo captures to ensure that the
@ -643,7 +682,7 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
}
} );
} );
} completed:^( AVCamPhotoCaptureDelegate *photoCaptureDelegate ) {
} completionHandler:^( AVCamPhotoCaptureDelegate *photoCaptureDelegate ) {
// When the capture is complete, remove a reference to the photo capture delegate so it can be deallocated.
dispatch_async( self.sessionQueue, ^{
self.inProgressPhotoCaptureDelegates[@(photoCaptureDelegate.requestedPhotoSettings.uniqueID)] = nil;
@ -677,6 +716,23 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
} );
}
- (IBAction)toggleDepthDataDeliveryMode:(UIButton *)depthDataDeliveryButton
{
dispatch_async( self.sessionQueue, ^{
self.depthDataDeliveryMode = ( self.depthDataDeliveryMode == AVCamDepthDataDeliveryModeOn ) ? AVCamDepthDataDeliveryModeOff : AVCamDepthDataDeliveryModeOn;
AVCamDepthDataDeliveryMode depthDataDeliveryMode = self.depthDataDeliveryMode;
dispatch_async( dispatch_get_main_queue(), ^{
if ( depthDataDeliveryMode == AVCamDepthDataDeliveryModeOn ) {
[self.depthDataDeliveryButton setTitle:NSLocalizedString( @"Depth Data Delivery: On", @"Depth Data mode button on title" ) forState:UIControlStateNormal];
}
else {
[self.depthDataDeliveryButton setTitle:NSLocalizedString( @"Depth Data Delivery: Off", @"Depth Data mode button off title" ) forState:UIControlStateNormal];
}
} );
} );
}
#pragma mark Recording Movies
- (IBAction)toggleMovieRecording:(id)sender
@ -716,6 +772,11 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
AVCaptureConnection *movieFileOutputConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
movieFileOutputConnection.videoOrientation = videoPreviewLayerVideoOrientation;
// Use HEVC codec if supported
if ( [self.movieFileOutput.availableVideoCodecTypes containsObject:AVVideoCodecTypeHEVC] ) {
[self.movieFileOutput setOutputSettings:@{ AVVideoCodecKey : AVVideoCodecTypeHEVC } forConnection:movieFileOutputConnection];
}
// Start recording to a temporary file.
NSString *outputFileName = [NSUUID UUID].UUIDString;
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[outputFileName stringByAppendingPathExtension:@"mov"]];
@ -751,7 +812,7 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
UIBackgroundTaskIdentifier currentBackgroundRecordingID = self.backgroundRecordingID;
self.backgroundRecordingID = UIBackgroundTaskInvalid;
dispatch_block_t cleanup = ^{
dispatch_block_t cleanUp = ^{
if ( [[NSFileManager defaultManager] fileExistsAtPath:outputFileURL.path] ) {
[[NSFileManager defaultManager] removeItemAtPath:outputFileURL.path error:NULL];
}
@ -781,16 +842,16 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
if ( ! success ) {
NSLog( @"Could not save movie to photo library: %@", error );
}
cleanup();
cleanUp();
}];
}
else {
cleanup();
cleanUp();
}
}];
}
else {
cleanup();
cleanUp();
}
// Enable the Camera and Record buttons to let the user switch camera and start another recording.
@ -836,6 +897,8 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
BOOL isSessionRunning = [change[NSKeyValueChangeNewKey] boolValue];
BOOL livePhotoCaptureSupported = self.photoOutput.livePhotoCaptureSupported;
BOOL livePhotoCaptureEnabled = self.photoOutput.livePhotoCaptureEnabled;
BOOL depthDataDeliverySupported = self.photoOutput.depthDataDeliverySupported;
BOOL depthDataDeliveryEnabled = self.photoOutput.depthDataDeliveryEnabled;
dispatch_async( dispatch_get_main_queue(), ^{
// Only enable the ability to change camera if the device has more than one camera.
@ -845,6 +908,8 @@ typedef NS_ENUM( NSInteger, AVCamLivePhotoMode ) {
self.captureModeControl.enabled = isSessionRunning;
self.livePhotoModeButton.enabled = isSessionRunning && livePhotoCaptureEnabled;
self.livePhotoModeButton.hidden = ! ( isSessionRunning && livePhotoCaptureSupported );
self.depthDataDeliveryButton.enabled = isSessionRunning && depthDataDeliveryEnabled ;
self.depthDataDeliveryButton.hidden = ! ( isSessionRunning && depthDataDeliverySupported );
} );
}
else {

View File

@ -1,16 +1,15 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Photo capture delegate.
Abstract:
Photo capture delegate.
*/
@import AVFoundation;
@interface AVCamPhotoCaptureDelegate : NSObject<AVCapturePhotoCaptureDelegate>
- (instancetype)initWithRequestedPhotoSettings:(AVCapturePhotoSettings *)requestedPhotoSettings willCapturePhotoAnimation:(void (^)())willCapturePhotoAnimation capturingLivePhoto:(void (^)( BOOL capturing ))capturingLivePhoto completed:(void (^)( AVCamPhotoCaptureDelegate *photoCaptureDelegate ))completed;
- (instancetype)initWithRequestedPhotoSettings:(AVCapturePhotoSettings *)requestedPhotoSettings willCapturePhotoAnimation:(void (^)(void))willCapturePhotoAnimation livePhotoCaptureHandler:(void (^)( BOOL capturing ))livePhotoCaptureHandler completionHandler:(void (^)( AVCamPhotoCaptureDelegate *photoCaptureDelegate ))completionHandler;
@property (nonatomic, readonly) AVCapturePhotoSettings *requestedPhotoSettings;

View File

@ -1,11 +1,11 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Photo capture delegate.
Abstract:
Photo capture delegate.
*/
#import "AVCamPhotoCaptureDelegate.h"
@import Photos;
@ -13,9 +13,9 @@
@interface AVCamPhotoCaptureDelegate ()
@property (nonatomic, readwrite) AVCapturePhotoSettings *requestedPhotoSettings;
@property (nonatomic) void (^willCapturePhotoAnimation)();
@property (nonatomic) void (^capturingLivePhoto)(BOOL capturing);
@property (nonatomic) void (^completed)(AVCamPhotoCaptureDelegate *photoCaptureDelegate);
@property (nonatomic) void (^willCapturePhotoAnimation)(void);
@property (nonatomic) void (^livePhotoCaptureHandler)(BOOL capturing);
@property (nonatomic) void (^completionHandler)(AVCamPhotoCaptureDelegate *photoCaptureDelegate);
@property (nonatomic) NSData *photoData;
@property (nonatomic) NSURL *livePhotoCompanionMovieURL;
@ -24,14 +24,14 @@
@implementation AVCamPhotoCaptureDelegate
- (instancetype)initWithRequestedPhotoSettings:(AVCapturePhotoSettings *)requestedPhotoSettings willCapturePhotoAnimation:(void (^)())willCapturePhotoAnimation capturingLivePhoto:(void (^)(BOOL))capturingLivePhoto completed:(void (^)(AVCamPhotoCaptureDelegate *))completed
- (instancetype)initWithRequestedPhotoSettings:(AVCapturePhotoSettings *)requestedPhotoSettings willCapturePhotoAnimation:(void (^)(void))willCapturePhotoAnimation livePhotoCaptureHandler:(void (^)(BOOL))livePhotoCaptureHandler completionHandler:(void (^)(AVCamPhotoCaptureDelegate *))completionHandler
{
self = [super init];
if ( self ) {
self.requestedPhotoSettings = requestedPhotoSettings;
self.willCapturePhotoAnimation = willCapturePhotoAnimation;
self.capturingLivePhoto = capturingLivePhoto;
self.completed = completed;
self.livePhotoCaptureHandler = livePhotoCaptureHandler;
self.completionHandler = completionHandler;
}
return self;
}
@ -47,13 +47,13 @@
}
}
self.completed( self );
self.completionHandler( self );
}
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput willBeginCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
if ( ( resolvedSettings.livePhotoMovieDimensions.width > 0 ) && ( resolvedSettings.livePhotoMovieDimensions.height > 0 ) ) {
self.capturingLivePhoto( YES );
self.livePhotoCaptureHandler( YES );
}
}
@ -62,19 +62,18 @@
self.willCapturePhotoAnimation();
}
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings error:(NSError *)error
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error
{
if ( error != nil ) {
NSLog( @"Error capturing photo: %@", error );
return;
}
self.photoData = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer:previewPhotoSampleBuffer];
self.photoData = [photo fileDataRepresentation];
}
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishRecordingLivePhotoMovieForEventualFileAtURL:(NSURL *)outputFileURL resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
self.capturingLivePhoto(NO);
self.livePhotoCaptureHandler(NO);
}
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingLivePhotoToMovieFileAtURL:(NSURL *)outputFileURL duration:(CMTime)duration photoDisplayTime:(CMTime)photoDisplayTime resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings error:(NSError *)error
@ -104,8 +103,10 @@
[PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
if ( status == PHAuthorizationStatusAuthorized ) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
options.uniformTypeIdentifier = self.requestedPhotoSettings.processedFileType;
PHAssetCreationRequest *creationRequest = [PHAssetCreationRequest creationRequestForAsset];
[creationRequest addResourceWithType:PHAssetResourceTypePhoto data:self.photoData options:nil];
[creationRequest addResourceWithType:PHAssetResourceTypePhoto data:self.photoData options:options];
if ( self.livePhotoCompanionMovieURL ) {
PHAssetResourceCreationOptions *livePhotoCompanionMovieResourceOptions = [[PHAssetResourceCreationOptions alloc] init];

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application preview view.
Abstract:
Application preview view.
*/
@import UIKit;

View File

@ -1,11 +1,9 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application preview view.
Abstract:
Application preview view.
*/
@import AVFoundation;
#import "AVCamPreviewView.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,124 +1,149 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small.png",
"filename" : "AVCam_Icon_29x29-1.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@2x.png",
"filename" : "AVCam_Icon_29x29@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@3x.png",
"filename" : "AVCam_Icon_29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@2x.png",
"filename" : "AVCam_Icon_40x40@2x-1.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@3x.png",
"filename" : "AVCam_Icon_40x40@3x.png",
"scale" : "3x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon.png",
"filename" : "AVCam_Icon_57x57.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon@2x.png",
"filename" : "AVCam_Icon_57x57@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"filename" : "AVCam_Icon_60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x.png",
"filename" : "AVCam_Icon_60x60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small.png",
"filename" : "AVCam_Icon_29x29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small@2x.png",
"filename" : "AVCam_Icon_29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40.png",
"filename" : "AVCam_Icon_40x40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40@2x.png",
"filename" : "AVCam_Icon_40x40@2x.png",
"scale" : "2x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50.png",
"filename" : "AVCam_Icon_50x50.png",
"scale" : "1x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50@2x.png",
"filename" : "AVCam_Icon_50x50@2x.png",
"scale" : "2x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-72.png",
"filename" : "AVCam_Icon_72x72.png",
"scale" : "1x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-72@2x.png",
"filename" : "AVCam_Icon_72x72@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"filename" : "AVCam_Icon_76x76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"filename" : "AVCam_Icon_76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-83.5@2x.png",
"filename" : "AVCam_Icon_83.5x83.5.png",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.12" systemVersion="15F24" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Cam Camera View Controller-->
<!--Camera View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="AVCamCameraViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<viewController id="BYZ-38-t0r" userLabel="Camera View Controller" customClass="AVCamCameraViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3eR-Rn-XpZ" userLabel="Preview" customClass="AVCamPreviewView">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<connections>
@ -26,12 +26,14 @@
</connections>
</view>
<label hidden="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Camera Unavailable" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zf0-db-esM" userLabel="Camera Unavailable">
<rect key="frame" x="103" y="353.66666666666669" width="208" height="29"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FZr-Ip-7WL" userLabel="Resume">
<rect key="frame" x="124.66666666666669" y="348.66666666666669" width="165" height="39"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<inset key="contentEdgeInsets" minX="10" minY="5" maxX="10" maxY="5"/>
@ -48,6 +50,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eRT-dK-6dM" userLabel="Record">
<rect key="frame" x="67" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Record">
@ -62,11 +65,12 @@
<action selector="toggleMovieRecording:" destination="BYZ-38-t0r" eventType="touchUpInside" id="9R7-Ok-FpB"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uCj-6P-mHF" userLabel="Still">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uCj-6P-mHF" userLabel="Photo">
<rect key="frame" x="167" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="NtC-UN-gTs"/>
<constraint firstAttribute="width" constant="80" id="dxU-UP-4Ae"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="80" id="dxU-UP-4Ae"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Photo">
@ -82,6 +86,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rUJ-G6-RPv" userLabel="Camera">
<rect key="frame" x="267" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Camera">
@ -96,7 +101,8 @@
<action selector="changeCamera:" destination="BYZ-38-t0r" eventType="touchUpInside" id="3W0-h3-6fc"/>
</connections>
</button>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="FAC-co-10c">
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="FAC-co-10c" userLabel="Capture Mode">
<rect key="frame" x="155.66666666666669" y="638" width="103.00000000000006" height="29"/>
<segments>
<segment title="Photo"/>
<segment title="Movie"/>
@ -105,10 +111,28 @@
<action selector="toggleCaptureMode:" destination="BYZ-38-t0r" eventType="valueChanged" id="SKd-67-ZHh"/>
</connections>
</segmentedControl>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eI6-gV-W7d">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9i1-NX-Qxg" userLabel="Depth Data Delivery">
<rect key="frame" x="102" y="28" width="210" height="25"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="DBZ-an-hCH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Depth Data Delivery: On"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="4"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="toggleDepthDataDeliveryMode:" destination="BYZ-38-t0r" eventType="touchUpInside" id="iSb-YO-MyR"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eI6-gV-W7d" userLabel="Live Photo Mode">
<rect key="frame" x="116" y="61" width="182" height="25"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="heR-zX-F6K"/>
<constraint firstAttribute="height" constant="25" id="om7-Gh-HVl"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Live Photo Mode: On"/>
@ -121,12 +145,9 @@
<action selector="toggleLivePhotoMode:" destination="BYZ-38-t0r" eventType="touchUpInside" id="JqX-wJ-Xf1"/>
</connections>
</button>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pii-2r-R2l">
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pii-2r-R2l" userLabel="Capturing Live Photo">
<rect key="frame" x="191.66666666666666" y="94" width="31" height="20.666666666666671"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="Kxo-zf-Fe1"/>
<constraint firstAttribute="width" constant="40" id="eRd-mj-8Du"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -139,31 +160,36 @@
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="125-kC-WZF"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="trailing" secondItem="nyU-fN-aJh" secondAttribute="trailing" id="2SX-Lc-5HP"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="top" secondItem="9i1-NX-Qxg" secondAttribute="bottom" constant="8" id="6iA-0j-auu"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="ACB-oH-2jU"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="height" secondItem="eRT-dK-6dM" secondAttribute="height" id="AEV-ew-H4g"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="8" id="Aao-6b-vLN"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" id="Ice-47-M9N"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="top" secondItem="eI6-gV-W7d" secondAttribute="bottom" constant="8" id="B43-ME-uK5"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="rUJ-G6-RPv" secondAttribute="trailing" constant="20" symbolic="YES" id="J70-ky-10v"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="rUJ-G6-RPv" secondAttribute="top" id="NFm-e8-abT"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="OaZ-uO-vXY"/>
<constraint firstItem="FAC-co-10c" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Oow-A6-mDp"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="OaZ-uO-vXY"/>
<constraint firstItem="FAC-co-10c" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="Oow-A6-mDp"/>
<constraint firstItem="9i1-NX-Qxg" firstAttribute="top" secondItem="nyU-fN-aJh" secondAttribute="top" constant="8" id="PNv-qh-VmU"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="Pik-0A-HnE"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="bottom" secondItem="8bC-Xf-vdC" secondAttribute="bottom" id="RNu-d4-nQ2"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="Ris-mI-8lA"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Upd-h8-1dL"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="W6q-xJ-jfF"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="SXi-MU-H9D"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="W6q-xJ-jfF"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="height" secondItem="rUJ-G6-RPv" secondAttribute="height" id="aQi-F7-E2b"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="FAC-co-10c" secondAttribute="bottom" constant="20" id="aSR-Je-0lW"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="eRT-dK-6dM" secondAttribute="top" id="bQd-ro-0Hw"/>
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="uCj-6P-mHF" secondAttribute="bottom" constant="20" id="eWs-co-Aaz"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="igk-MQ-CGt"/>
<constraint firstItem="rUJ-G6-RPv" firstAttribute="leading" secondItem="uCj-6P-mHF" secondAttribute="trailing" constant="20" id="lsk-Hm-rTd"/>
<constraint firstAttribute="centerX" secondItem="uCj-6P-mHF" secondAttribute="centerX" id="m8a-cF-Rf0"/>
<constraint firstItem="eRT-dK-6dM" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="20" symbolic="YES" id="cCX-ki-9gT"/>
<constraint firstItem="nyU-fN-aJh" firstAttribute="bottom" secondItem="uCj-6P-mHF" secondAttribute="bottom" constant="20" id="eWs-co-Aaz"/>
<constraint firstItem="rUJ-G6-RPv" firstAttribute="leading" secondItem="eRT-dK-6dM" secondAttribute="trailing" constant="120" id="lsk-Hm-rTd"/>
<constraint firstItem="nyU-fN-aJh" firstAttribute="centerX" secondItem="uCj-6P-mHF" secondAttribute="centerX" id="m8a-cF-Rf0"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="width" secondItem="rUJ-G6-RPv" secondAttribute="width" id="o8j-gw-35B"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="top" secondItem="eI6-gV-W7d" secondAttribute="bottom" constant="8" id="oDE-jY-ryC"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" id="pSC-xP-dl0"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="rqt-bn-mSt"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="width" secondItem="eRT-dK-6dM" secondAttribute="width" id="s8u-Y8-n27"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="sTY-i6-czN"/>
<constraint firstItem="9i1-NX-Qxg" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="wWj-VD-34F"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="leading" secondItem="nyU-fN-aJh" secondAttribute="leading" id="x70-kJ-WPk"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="leading" secondItem="eRT-dK-6dM" secondAttribute="trailing" constant="20" id="zwj-TX-t6O"/>
</constraints>
<viewLayoutGuide key="safeArea" id="nyU-fN-aJh"/>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
<nil key="simulatedStatusBarMetrics"/>
@ -172,6 +198,7 @@
<outlet property="cameraUnavailableLabel" destination="zf0-db-esM" id="P9W-lb-Pb8"/>
<outlet property="captureModeControl" destination="FAC-co-10c" id="KXj-wg-BvS"/>
<outlet property="capturingLivePhotoLabel" destination="Pii-2r-R2l" id="JAa-4l-5SD"/>
<outlet property="depthDataDeliveryButton" destination="9i1-NX-Qxg" id="The-P3-y3W"/>
<outlet property="livePhotoModeButton" destination="eI6-gV-W7d" id="r9f-cN-YSH"/>
<outlet property="photoButton" destination="uCj-6P-mHF" id="Ha8-ua-hxy"/>
<outlet property="previewView" destination="3eR-Rn-XpZ" id="e7I-nu-L6j"/>

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Main application entry point.
Abstract:
Main application entry point.
*/
@import UIKit;

View File

@ -1,24 +1,25 @@
# AVCam-iOS: Using AVFoundation to Capture Photos and Movies
# AVCam-iOS
AVCam demonstrates how to use the AVFoundation capture API to record movies and capture photos. The sample has a record button for recording movies, a photo button for capturing photos, a Live Photo mode button for enabling Live Photo capture, a capture mode control for toggling between photo and movie capture modes, and a camera button for switching between front and back cameras (on supported devices). AVCam runs only on an actual device, either an iPad or iPhone, and cannot be run in Simulator.
AVCam demonstrates how to use the AVFoundation capture API to record movies and capture photos.
## Overview
The sample has a record button for recording movies, a photo button for capturing photos, a Live Photo mode button for enabling Live Photo capture, a Depth Data delivery button for enabling depth data delivery in capture, a capture mode control for toggling between photo and movie capture modes, and a camera button for switching between front and back cameras (on supported devices). AVCam runs only on an actual device, either an iPad or iPhone, and cannot be run in Simulator.
## Requirements
### Build
Xcode 8.0, iOS 10.0 SDK
Xcode 9.0, iOS 11.0 SDK
### Runtime
iOS 10.0 or later
iOS 11.0 or later
## Changes from Previous Version
- Adopt AVCapturePhotoOutput
- Capture Live Photos
- Add privacy keys to Info.plist
- Add a version of AVCam in Swift 3
- Remove support for AVCaptureStillImageOutput
- Adopt AVCapturePhoto
- Add HEIF and HEVC support
- Include Depth Data Delivery in photo capture
- Upgrade from Swift 3 to Swift 4
- Bug fixes
Copyright (C) 2016 Apple Inc. All rights reserved.

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>

View File

@ -17,6 +17,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
3E25DCAF1F7EA506007EF37E /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../../LICENSE.txt; sourceTree = "<group>"; };
7AA677111CFF765600B353FB /* AVCam.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AVCam.app; sourceTree = BUILT_PRODUCTS_DIR; };
7AA677141CFF765600B353FB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AA677161CFF765600B353FB /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = "<group>"; };
@ -27,6 +28,7 @@
7AA677261CFF774800B353FB /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = "<group>"; };
7AA677281CFF7B5C00B353FB /* PreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = "<group>"; };
7AE4754E1D00FFA900C2CB9E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = SampleCode.xcconfig; path = ../Configuration/SampleCode.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -40,12 +42,23 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
3E25DCAE1F7EA4F7007EF37E /* LICENSE */ = {
isa = PBXGroup;
children = (
3E25DCAF1F7EA506007EF37E /* LICENSE.txt */,
);
name = LICENSE;
path = AVCam;
sourceTree = "<group>";
};
7AA677081CFF765500B353FB = {
isa = PBXGroup;
children = (
7AE4754E1D00FFA900C2CB9E /* README.md */,
7AA677131CFF765600B353FB /* AVCam */,
7AA677121CFF765600B353FB /* Products */,
DFC195FC1DBE566F0F3F6735 /* Configuration */,
3E25DCAE1F7EA4F7007EF37E /* LICENSE */,
);
sourceTree = "<group>";
};
@ -72,6 +85,14 @@
path = AVCam;
sourceTree = "<group>";
};
DFC195FC1DBE566F0F3F6735 /* Configuration */ = {
isa = PBXGroup;
children = (
C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */,
);
name = Configuration;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -99,8 +120,8 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0800;
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Apple, Inc.";
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Apple;
TargetAttributes = {
7AA677101CFF765500B353FB = {
CreatedOnToolsVersion = 8.0;
@ -175,6 +196,7 @@
/* Begin XCBuildConfiguration section */
7AA677211CFF765600B353FB /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -188,8 +210,11 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -211,7 +236,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -223,6 +248,7 @@
};
7AA677221CFF765600B353FB /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -236,8 +262,11 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -253,7 +282,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@ -264,29 +293,35 @@
};
7AA677241CFF765600B353FB /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AVCam/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam${SAMPLE_CODE_DISAMBIGUATOR}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
7AA677251CFF765600B353FB /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C0E23044F2276005EA5958F2 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AVCam/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.AVCam${SAMPLE_CODE_DISAMBIGUATOR}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
};
name = Release;
};

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7AA677101CFF765500B353FB"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Swift.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7AA677101CFF765500B353FB"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Swift.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7AA677101CFF765500B353FB"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Swift.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7AA677101CFF765500B353FB"
BuildableName = "AVCam.app"
BlueprintName = "AVCam"
ReferencedContainer = "container:AVCam Swift.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application delegate.
Abstract:
Application delegate.
*/
import UIKit

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,124 +1,149 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small.png",
"filename" : "AVCam_Icon_29x29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@2x.png",
"filename" : "AVCam_Icon_29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-Small@3x.png",
"filename" : "AVCam_Icon_29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@2x.png",
"filename" : "AVCam_Icon_40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@3x.png",
"filename" : "AVCam_Icon_40x40@3x.png",
"scale" : "3x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon.png",
"filename" : "AVCam_Icon_57x57.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon@2x.png",
"filename" : "AVCam_Icon_57x57@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"filename" : "AVCam_Icon_60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x.png",
"filename" : "AVCam_Icon_60x60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small.png",
"filename" : "AVCam_Icon_29x29-1.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small@2x.png",
"filename" : "AVCam_Icon_29x29@2x-1.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40.png",
"filename" : "AVCam_Icon_40x40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40@2x.png",
"filename" : "AVCam_Icon_40x40@2x-1.png",
"scale" : "2x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50.png",
"filename" : "AVCam_Icon_50x50.png",
"scale" : "1x"
},
{
"size" : "50x50",
"idiom" : "ipad",
"filename" : "Icon-Small-50@2x.png",
"filename" : "AVCam_Icon_50x50@2x.png",
"scale" : "2x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-72.png",
"filename" : "AVCam_Icon_72x72.png",
"scale" : "1x"
},
{
"size" : "72x72",
"idiom" : "ipad",
"filename" : "Icon-72@2x.png",
"filename" : "AVCam_Icon_72x72@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"filename" : "AVCam_Icon_76x76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"filename" : "AVCam_Icon_76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-83.5@2x.png",
"filename" : "AVCam_Icon_83.5x83.5.png",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.12" systemVersion="15F24" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Camera View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CameraViewController" customModule="AVCam" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<viewController id="BYZ-38-t0r" userLabel="Camera View Controller" customClass="CameraViewController" customModule="AVCam" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3eR-Rn-XpZ" userLabel="Preview" customClass="PreviewView" customModule="AVCam" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<connections>
@ -26,12 +26,14 @@
</connections>
</view>
<label hidden="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Camera Unavailable" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zf0-db-esM" userLabel="Camera Unavailable">
<rect key="frame" x="103" y="353.66666666666669" width="208" height="29"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FZr-Ip-7WL" userLabel="Resume">
<rect key="frame" x="124.66666666666669" y="348.66666666666669" width="165" height="39"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<inset key="contentEdgeInsets" minX="10" minY="5" maxX="10" maxY="5"/>
@ -44,10 +46,11 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="resumeInterruptedSession:" destination="BYZ-38-t0r" eventType="touchUpInside" id="o7T-5Z-tfn"/>
<action selector="resumeInterruptedSession:" destination="BYZ-38-t0r" eventType="touchUpInside" id="42K-1B-qJd"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eRT-dK-6dM" userLabel="Record">
<rect key="frame" x="67" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Record">
@ -62,11 +65,12 @@
<action selector="toggleMovieRecording:" destination="BYZ-38-t0r" eventType="touchUpInside" id="9R7-Ok-FpB"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uCj-6P-mHF" userLabel="Still">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uCj-6P-mHF" userLabel="Photo">
<rect key="frame" x="167" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="NtC-UN-gTs"/>
<constraint firstAttribute="width" constant="80" id="dxU-UP-4Ae"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="80" id="dxU-UP-4Ae"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Photo">
@ -82,6 +86,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rUJ-G6-RPv" userLabel="Camera">
<rect key="frame" x="267" y="686" width="80" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Camera">
@ -96,7 +101,8 @@
<action selector="changeCamera:" destination="BYZ-38-t0r" eventType="touchUpInside" id="3W0-h3-6fc"/>
</connections>
</button>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="FAC-co-10c">
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="FAC-co-10c" userLabel="Capture Mode">
<rect key="frame" x="155.66666666666669" y="638" width="103.00000000000006" height="29"/>
<segments>
<segment title="Photo"/>
<segment title="Movie"/>
@ -105,10 +111,28 @@
<action selector="toggleCaptureMode:" destination="BYZ-38-t0r" eventType="valueChanged" id="SKd-67-ZHh"/>
</connections>
</segmentedControl>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eI6-gV-W7d">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9i1-NX-Qxg" userLabel="Depth Data Delivery">
<rect key="frame" x="102" y="28" width="210" height="25"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="DBZ-an-hCH"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Depth Data Delivery: On"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="4"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="toggleDepthDataDeliveryMode:" destination="BYZ-38-t0r" eventType="touchUpInside" id="iSb-YO-MyR"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eI6-gV-W7d" userLabel="Live Photo Mode">
<rect key="frame" x="116" y="61" width="182" height="25"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="heR-zX-F6K"/>
<constraint firstAttribute="height" constant="25" id="om7-Gh-HVl"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Live Photo Mode: On"/>
@ -121,12 +145,9 @@
<action selector="toggleLivePhotoMode:" destination="BYZ-38-t0r" eventType="touchUpInside" id="JqX-wJ-Xf1"/>
</connections>
</button>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pii-2r-R2l">
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pii-2r-R2l" userLabel="Capturing Live Photo">
<rect key="frame" x="191.66666666666666" y="94" width="31" height="20.666666666666671"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="Kxo-zf-Fe1"/>
<constraint firstAttribute="width" constant="40" id="eRd-mj-8Du"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -139,31 +160,36 @@
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="125-kC-WZF"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="top" secondItem="9i1-NX-Qxg" secondAttribute="bottom" constant="8" id="6iA-0j-auu"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="trailing" secondItem="nyU-fN-aJh" secondAttribute="trailing" id="8ud-UI-Sfr"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="ACB-oH-2jU"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="height" secondItem="eRT-dK-6dM" secondAttribute="height" id="AEV-ew-H4g"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="8" id="Aao-6b-vLN"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" id="Ice-47-M9N"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="top" secondItem="eI6-gV-W7d" secondAttribute="bottom" constant="8" id="B43-ME-uK5"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="rUJ-G6-RPv" secondAttribute="trailing" constant="20" symbolic="YES" id="J70-ky-10v"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="rUJ-G6-RPv" secondAttribute="top" id="NFm-e8-abT"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="OaZ-uO-vXY"/>
<constraint firstItem="FAC-co-10c" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Oow-A6-mDp"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="OaZ-uO-vXY"/>
<constraint firstItem="FAC-co-10c" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="Oow-A6-mDp"/>
<constraint firstItem="9i1-NX-Qxg" firstAttribute="top" secondItem="nyU-fN-aJh" secondAttribute="top" constant="8" id="PNv-qh-VmU"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="Ris-mI-8lA"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Upd-h8-1dL"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="W6q-xJ-jfF"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="SXi-MU-H9D"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="leading" secondItem="nyU-fN-aJh" secondAttribute="leading" id="Sdw-MH-42e"/>
<constraint firstItem="zf0-db-esM" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="W6q-xJ-jfF"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="height" secondItem="rUJ-G6-RPv" secondAttribute="height" id="aQi-F7-E2b"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="FAC-co-10c" secondAttribute="bottom" constant="20" id="aSR-Je-0lW"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="top" secondItem="eRT-dK-6dM" secondAttribute="top" id="bQd-ro-0Hw"/>
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="uCj-6P-mHF" secondAttribute="bottom" constant="20" id="eWs-co-Aaz"/>
<constraint firstItem="eRT-dK-6dM" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="20" symbolic="YES" id="cCX-ki-9gT"/>
<constraint firstItem="nyU-fN-aJh" firstAttribute="bottom" secondItem="uCj-6P-mHF" secondAttribute="bottom" constant="20" id="eWs-co-Aaz"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="bottom" secondItem="8bC-Xf-vdC" secondAttribute="bottom" id="ea0-4F-Kd7"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="igk-MQ-CGt"/>
<constraint firstItem="rUJ-G6-RPv" firstAttribute="leading" secondItem="uCj-6P-mHF" secondAttribute="trailing" constant="20" id="lsk-Hm-rTd"/>
<constraint firstAttribute="centerX" secondItem="uCj-6P-mHF" secondAttribute="centerX" id="m8a-cF-Rf0"/>
<constraint firstItem="rUJ-G6-RPv" firstAttribute="leading" secondItem="eRT-dK-6dM" secondAttribute="trailing" constant="120" id="lsk-Hm-rTd"/>
<constraint firstItem="nyU-fN-aJh" firstAttribute="centerX" secondItem="uCj-6P-mHF" secondAttribute="centerX" id="m8a-cF-Rf0"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="width" secondItem="rUJ-G6-RPv" secondAttribute="width" id="o8j-gw-35B"/>
<constraint firstItem="Pii-2r-R2l" firstAttribute="top" secondItem="eI6-gV-W7d" secondAttribute="bottom" constant="8" id="oDE-jY-ryC"/>
<constraint firstItem="3eR-Rn-XpZ" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" id="pSC-xP-dl0"/>
<constraint firstItem="eI6-gV-W7d" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="rqt-bn-mSt"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="width" secondItem="eRT-dK-6dM" secondAttribute="width" id="s8u-Y8-n27"/>
<constraint firstItem="FZr-Ip-7WL" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="sTY-i6-czN"/>
<constraint firstItem="9i1-NX-Qxg" firstAttribute="centerX" secondItem="nyU-fN-aJh" secondAttribute="centerX" id="wWj-VD-34F"/>
<constraint firstItem="uCj-6P-mHF" firstAttribute="leading" secondItem="eRT-dK-6dM" secondAttribute="trailing" constant="20" id="zwj-TX-t6O"/>
</constraints>
<viewLayoutGuide key="safeArea" id="nyU-fN-aJh"/>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
<nil key="simulatedStatusBarMetrics"/>
@ -172,6 +198,7 @@
<outlet property="cameraUnavailableLabel" destination="zf0-db-esM" id="P9W-lb-Pb8"/>
<outlet property="captureModeControl" destination="FAC-co-10c" id="KXj-wg-BvS"/>
<outlet property="capturingLivePhotoLabel" destination="Pii-2r-R2l" id="JAa-4l-5SD"/>
<outlet property="depthDataDeliveryButton" destination="9i1-NX-Qxg" id="The-P3-y3W"/>
<outlet property="livePhotoModeButton" destination="eI6-gV-W7d" id="r9f-cN-YSH"/>
<outlet property="photoButton" destination="uCj-6P-mHF" id="Ha8-ua-hxy"/>
<outlet property="previewView" destination="3eR-Rn-XpZ" id="e7I-nu-L6j"/>

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
View controller for camera interface.
Abstract:
View controller for camera interface.
*/
import UIKit
@ -21,6 +20,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
recordButton.isEnabled = false
photoButton.isEnabled = false
livePhotoModeButton.isEnabled = false
depthDataDeliveryButton.isEnabled = false
captureModeControl.isEnabled = false
// Set up the video preview view.
@ -31,7 +31,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
access is optional. If audio access is denied, audio is not recorded
during movie recording.
*/
switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
// The user has previously granted access to the camera.
break
@ -46,7 +46,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
create an AVCaptureDeviceInput for audio during session setup.
*/
sessionQueue.suspend()
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { [unowned self] granted in
AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
if !granted {
self.setupResult = .notAuthorized
}
@ -68,7 +68,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
take a long time. We dispatch session setup to the sessionQueue so
that the main queue isn't blocked, which keeps the UI responsive.
*/
sessionQueue.async { [unowned self] in
sessionQueue.async {
self.configureSession()
}
}
@ -85,11 +85,18 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
self.isSessionRunning = self.session.isRunning
case .notAuthorized:
DispatchQueue.main.async { [unowned self] in
let message = NSLocalizedString("AVCam doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera")
DispatchQueue.main.async {
let changePrivacySetting = "AVCam doesn't have permission to use the camera, please change privacy settings"
let message = NSLocalizedString(changePrivacySetting, comment: "Alert message when the user has denied access to the camera")
let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .`default`, handler: { action in
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"),
style: .cancel,
handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"),
style: .`default`,
handler: { _ in
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: nil)
}))
@ -97,10 +104,14 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
}
case .configurationFailed:
DispatchQueue.main.async { [unowned self] in
let message = NSLocalizedString("Unable to capture media", comment: "Alert message when something goes wrong during capture session configuration")
DispatchQueue.main.async {
let alertMsg = "Alert message when something goes wrong during capture session configuration"
let message = NSLocalizedString("Unable to capture media", comment: alertMsg)
let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"),
style: .cancel,
handler: nil))
self.present(alertController, animated: true, completion: nil)
}
@ -109,7 +120,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
}
override func viewWillDisappear(_ animated: Bool) {
sessionQueue.async { [unowned self] in
sessionQueue.async {
if self.setupResult == .success {
self.session.stopRunning()
self.isSessionRunning = self.session.isRunning
@ -137,7 +148,8 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
if let videoPreviewLayerConnection = previewView.videoPreviewLayer.connection {
let deviceOrientation = UIDevice.current.orientation
guard let newVideoOrientation = deviceOrientation.videoOrientation, deviceOrientation.isPortrait || deviceOrientation.isLandscape else {
guard let newVideoOrientation = AVCaptureVideoOrientation(deviceOrientation: deviceOrientation),
deviceOrientation.isPortrait || deviceOrientation.isLandscape else {
return
}
@ -157,7 +169,7 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
private var isSessionRunning = false
private let sessionQueue = DispatchQueue(label: "session queue", attributes: [], target: nil) // Communicate with the session and other session objects on this queue.
private let sessionQueue = DispatchQueue(label: "session queue") // Communicate with the session and other session objects on this queue.
private var setupResult: SessionSetupResult = .success
@ -175,28 +187,29 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
/*
We do not create an AVCaptureMovieFileOutput when setting up the session because the
AVCaptureMovieFileOutput does not support movie recording with AVCaptureSessionPresetPhoto.
AVCaptureMovieFileOutput does not support movie recording with AVCaptureSession.Preset.Photo.
*/
session.sessionPreset = AVCaptureSessionPresetPhoto
session.sessionPreset = .photo
// Add video input.
do {
var defaultVideoDevice: AVCaptureDevice?
// Choose the back dual camera if available, otherwise default to a wide angle camera.
if let dualCameraDevice = AVCaptureDevice.defaultDevice(withDeviceType: .builtInDuoCamera, mediaType: AVMediaTypeVideo, position: .back) {
if let dualCameraDevice = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {
defaultVideoDevice = dualCameraDevice
}
else if let backCameraDevice = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: .back) {
} else if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
// If the back dual camera is not available, default to the back wide angle camera.
defaultVideoDevice = backCameraDevice
}
else if let frontCameraDevice = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: .front) {
// In some cases where users break their phones, the back wide angle camera is not available. In this case, we should default to the front wide angle camera.
} else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
/*
In some cases where users break their phones, the back wide angle camera is not available.
In this case, we should default to the front wide angle camera.
*/
defaultVideoDevice = frontCameraDevice
}
let videoDeviceInput = try AVCaptureDeviceInput(device: defaultVideoDevice)
let videoDeviceInput = try AVCaptureDeviceInput(device: defaultVideoDevice!)
if session.canAddInput(videoDeviceInput) {
session.addInput(videoDeviceInput)
@ -216,22 +229,20 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
let statusBarOrientation = UIApplication.shared.statusBarOrientation
var initialVideoOrientation: AVCaptureVideoOrientation = .portrait
if statusBarOrientation != .unknown {
if let videoOrientation = statusBarOrientation.videoOrientation {
if let videoOrientation = AVCaptureVideoOrientation(interfaceOrientation: statusBarOrientation) {
initialVideoOrientation = videoOrientation
}
}
self.previewView.videoPreviewLayer.connection.videoOrientation = initialVideoOrientation
self.previewView.videoPreviewLayer.connection?.videoOrientation = initialVideoOrientation
}
}
else {
} else {
print("Could not add video device input to the session")
setupResult = .configurationFailed
session.commitConfiguration()
return
}
}
catch {
} catch {
print("Could not create video device input: \(error)")
setupResult = .configurationFailed
session.commitConfiguration()
@ -240,30 +251,29 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
// Add audio input.
do {
let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio)
let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
let audioDevice = AVCaptureDevice.default(for: .audio)
let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!)
if session.canAddInput(audioDeviceInput) {
session.addInput(audioDeviceInput)
}
else {
} else {
print("Could not add audio device input to the session")
}
}
catch {
} catch {
print("Could not create audio device input: \(error)")
}
// Add photo output.
if session.canAddOutput(photoOutput)
{
if session.canAddOutput(photoOutput) {
session.addOutput(photoOutput)
photoOutput.isHighResolutionCaptureEnabled = true
photoOutput.isLivePhotoCaptureEnabled = photoOutput.isLivePhotoCaptureSupported
photoOutput.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliverySupported
livePhotoMode = photoOutput.isLivePhotoCaptureSupported ? .on : .off
}
else {
depthDataDeliveryMode = photoOutput.isDepthDataDeliverySupported ? .on : .off
} else {
print("Could not add photo output to the session")
setupResult = .configurationFailed
session.commitConfiguration()
@ -273,9 +283,8 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
session.commitConfiguration()
}
@IBAction private func resumeInterruptedSession(_ resumeButton: UIButton)
{
sessionQueue.async { [unowned self] in
@IBAction private func resumeInterruptedSession(_ resumeButton: UIButton) {
sessionQueue.async {
/*
The session might fail to start running, e.g., if a phone or FaceTime call is still
using audio or video. A failure to start the session running will be communicated via
@ -286,16 +295,15 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
self.session.startRunning()
self.isSessionRunning = self.session.isRunning
if !self.session.isRunning {
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
let message = NSLocalizedString("Unable to resume", comment: "Alert message when unable to resume the session running")
let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
}
else {
DispatchQueue.main.async { [unowned self] in
} else {
DispatchQueue.main.async {
self.resumeButton.isHidden = true
}
}
@ -310,19 +318,24 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
@IBOutlet private weak var captureModeControl: UISegmentedControl!
@IBAction private func toggleCaptureMode(_ captureModeControl: UISegmentedControl) {
captureModeControl.isEnabled = false
if captureModeControl.selectedSegmentIndex == CaptureMode.photo.rawValue {
recordButton.isEnabled = false
sessionQueue.async { [unowned self] in
sessionQueue.async {
/*
Remove the AVCaptureMovieFileOutput from the session because movie recording is
not supported with AVCaptureSessionPresetPhoto. Additionally, Live Photo
not supported with AVCaptureSession.Preset.Photo. Additionally, Live Photo
capture is not supported when an AVCaptureMovieFileOutput is connected to the session.
*/
self.session.beginConfiguration()
self.session.removeOutput(self.movieFileOutput)
self.session.sessionPreset = AVCaptureSessionPresetPhoto
self.session.commitConfiguration()
self.session.removeOutput(self.movieFileOutput!)
self.session.sessionPreset = .photo
DispatchQueue.main.async {
captureModeControl.isEnabled = true
}
self.movieFileOutput = nil
@ -334,29 +347,43 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
self.livePhotoModeButton.isHidden = false
}
}
}
}
else if captureModeControl.selectedSegmentIndex == CaptureMode.movie.rawValue
{
livePhotoModeButton.isHidden = true
sessionQueue.async { [unowned self] in
if self.photoOutput.isDepthDataDeliverySupported {
self.photoOutput.isDepthDataDeliveryEnabled = true
DispatchQueue.main.async {
self.depthDataDeliveryButton.isHidden = false
self.depthDataDeliveryButton.isEnabled = true
}
}
self.session.commitConfiguration()
}
} else if captureModeControl.selectedSegmentIndex == CaptureMode.movie.rawValue {
livePhotoModeButton.isHidden = true
depthDataDeliveryButton.isHidden = true
sessionQueue.async {
let movieFileOutput = AVCaptureMovieFileOutput()
if self.session.canAddOutput(movieFileOutput) {
self.session.beginConfiguration()
self.session.addOutput(movieFileOutput)
self.session.sessionPreset = AVCaptureSessionPresetHigh
if let connection = movieFileOutput.connection(withMediaType: AVMediaTypeVideo) {
self.session.sessionPreset = .high
if let connection = movieFileOutput.connection(with: .video) {
if connection.isVideoStabilizationSupported {
connection.preferredVideoStabilizationMode = .auto
}
}
self.session.commitConfiguration()
DispatchQueue.main.async {
captureModeControl.isEnabled = true
}
self.movieFileOutput = movieFileOutput
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
self.recordButton.isEnabled = true
}
}
@ -369,8 +396,8 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
@IBOutlet private weak var cameraButton: UIButton!
@IBOutlet private weak var cameraUnavailableLabel: UILabel!
private let videoDeviceDiscoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDuoCamera], mediaType: AVMediaTypeVideo, position: .unspecified)!
private let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera],
mediaType: .video, position: .unspecified)
@IBAction private func changeCamera(_ cameraButton: UIButton) {
cameraButton.isEnabled = false
@ -379,31 +406,30 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
livePhotoModeButton.isEnabled = false
captureModeControl.isEnabled = false
sessionQueue.async { [unowned self] in
sessionQueue.async {
let currentVideoDevice = self.videoDeviceInput.device
let currentPosition = currentVideoDevice!.position
let currentPosition = currentVideoDevice.position
let preferredPosition: AVCaptureDevicePosition
let preferredDeviceType: AVCaptureDeviceType
let preferredPosition: AVCaptureDevice.Position
let preferredDeviceType: AVCaptureDevice.DeviceType
switch currentPosition {
case .unspecified, .front:
preferredPosition = .back
preferredDeviceType = .builtInDuoCamera
preferredDeviceType = .builtInDualCamera
case .back:
preferredPosition = .front
preferredDeviceType = .builtInWideAngleCamera
}
let devices = self.videoDeviceDiscoverySession.devices!
let devices = self.videoDeviceDiscoverySession.devices
var newVideoDevice: AVCaptureDevice? = nil
// First, look for a device with both the preferred position and device type. Otherwise, look for a device with only the preferred position.
if let device = devices.filter({ $0.position == preferredPosition && $0.deviceType == preferredDeviceType }).first {
if let device = devices.first(where: { $0.position == preferredPosition && $0.deviceType == preferredDeviceType }) {
newVideoDevice = device
}
else if let device = devices.filter({ $0.position == preferredPosition }).first {
} else if let device = devices.first(where: { $0.position == preferredPosition }) {
newVideoDevice = device
}
@ -417,56 +443,57 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
self.session.removeInput(self.videoDeviceInput)
if self.session.canAddInput(videoDeviceInput) {
NotificationCenter.default.removeObserver(self, name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: currentVideoDevice!)
NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: currentVideoDevice)
NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: videoDeviceInput.device)
NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)
self.session.addInput(videoDeviceInput)
self.videoDeviceInput = videoDeviceInput
}
else {
self.session.addInput(self.videoDeviceInput);
} else {
self.session.addInput(self.videoDeviceInput)
}
if let connection = self.movieFileOutput?.connection(withMediaType: AVMediaTypeVideo) {
if let connection = self.movieFileOutput?.connection(with: .video) {
if connection.isVideoStabilizationSupported {
connection.preferredVideoStabilizationMode = .auto
}
}
/*
Set Live Photo capture enabled if it is supported. When changing cameras, the
`isLivePhotoCaptureEnabled` property of the AVCapturePhotoOutput gets set to NO when
Set Live Photo capture and depth data delivery if it is supported. When changing cameras, the
`livePhotoCaptureEnabled and depthDataDeliveryEnabled` properties of the AVCapturePhotoOutput gets set to NO when
a video device is disconnected from the session. After the new video device is
added to the session, re-enable Live Photo capture on the AVCapturePhotoOutput if it is supported.
added to the session, re-enable them on the AVCapturePhotoOutput if it is supported.
*/
self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported;
self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported
self.photoOutput.isDepthDataDeliveryEnabled = self.photoOutput.isDepthDataDeliverySupported
self.session.commitConfiguration()
}
catch {
} catch {
print("Error occured while creating video device input: \(error)")
}
}
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
self.cameraButton.isEnabled = true
self.recordButton.isEnabled = self.movieFileOutput != nil
self.photoButton.isEnabled = true
self.livePhotoModeButton.isEnabled = true
self.captureModeControl.isEnabled = true
self.depthDataDeliveryButton.isEnabled = self.photoOutput.isDepthDataDeliveryEnabled
self.depthDataDeliveryButton.isHidden = !self.photoOutput.isDepthDataDeliverySupported
}
}
}
@IBAction private func focusAndExposeTap(_ gestureRecognizer: UITapGestureRecognizer) {
let devicePoint = self.previewView.videoPreviewLayer.captureDevicePointOfInterest(for: gestureRecognizer.location(in: gestureRecognizer.view))
let devicePoint = previewView.videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: gestureRecognizer.location(in: gestureRecognizer.view))
focus(with: .autoFocus, exposureMode: .autoExpose, at: devicePoint, monitorSubjectAreaChange: true)
}
private func focus(with focusMode: AVCaptureFocusMode, exposureMode: AVCaptureExposureMode, at devicePoint: CGPoint, monitorSubjectAreaChange: Bool) {
sessionQueue.async { [unowned self] in
if let device = self.videoDeviceInput.device {
private func focus(with focusMode: AVCaptureDevice.FocusMode, exposureMode: AVCaptureDevice.ExposureMode, at devicePoint: CGPoint, monitorSubjectAreaChange: Bool) {
sessionQueue.async {
let device = self.videoDeviceInput.device
do {
try device.lockForConfiguration()
@ -486,42 +513,48 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
device.isSubjectAreaChangeMonitoringEnabled = monitorSubjectAreaChange
device.unlockForConfiguration()
}
catch {
} catch {
print("Could not lock device for configuration: \(error)")
}
}
}
}
// MARK: Capturing Photos
private let photoOutput = AVCapturePhotoOutput()
private var inProgressPhotoCaptureDelegates = [Int64 : PhotoCaptureDelegate]()
private var inProgressPhotoCaptureDelegates = [Int64: PhotoCaptureProcessor]()
@IBOutlet private weak var photoButton: UIButton!
@IBAction private func capturePhoto(_ photoButton: UIButton) {
/*
Retrieve the video preview layer's video orientation on the main queue before
entering the session queue. We do this to ensure UI elements are accessed on
the main thread and session configuration is done on the session queue.
*/
let videoPreviewLayerOrientation = previewView.videoPreviewLayer.connection.videoOrientation
let videoPreviewLayerOrientation = previewView.videoPreviewLayer.connection?.videoOrientation
sessionQueue.async {
// Update the photo output's connection to match the video orientation of the video preview layer.
if let photoOutputConnection = self.photoOutput.connection(withMediaType: AVMediaTypeVideo) {
photoOutputConnection.videoOrientation = videoPreviewLayerOrientation
if let photoOutputConnection = self.photoOutput.connection(with: .video) {
photoOutputConnection.videoOrientation = videoPreviewLayerOrientation!
}
// Capture a JPEG photo with flash set to auto and high resolution photo enabled.
let photoSettings = AVCapturePhotoSettings()
var photoSettings = AVCapturePhotoSettings()
// Capture HEIF photo when supported, with flash set to auto and high resolution photo enabled.
if self.photoOutput.availablePhotoCodecTypes.contains(.hevc) {
photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
}
if self.videoDeviceInput.device.isFlashAvailable {
photoSettings.flashMode = .auto
}
photoSettings.isHighResolutionPhotoEnabled = true
if photoSettings.availablePreviewPhotoPixelFormatTypes.count > 0 {
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String : photoSettings.availablePreviewPhotoPixelFormatTypes.first!]
if !photoSettings.__availablePreviewPhotoPixelFormatTypes.isEmpty {
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.__availablePreviewPhotoPixelFormatTypes.first!]
}
if self.livePhotoMode == .on && self.photoOutput.isLivePhotoCaptureSupported { // Live Photo capture is not supported in movie mode.
let livePhotoMovieFileName = NSUUID().uuidString
@ -529,45 +562,48 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
photoSettings.livePhotoMovieFileURL = URL(fileURLWithPath: livePhotoMovieFilePath)
}
if self.depthDataDeliveryMode == .on && self.photoOutput.isDepthDataDeliverySupported {
photoSettings.isDepthDataDeliveryEnabled = true
} else {
photoSettings.isDepthDataDeliveryEnabled = false
}
// Use a separate object for the photo capture delegate to isolate each capture life cycle.
let photoCaptureDelegate = PhotoCaptureDelegate(with: photoSettings, willCapturePhotoAnimation: {
DispatchQueue.main.async { [unowned self] in
let photoCaptureProcessor = PhotoCaptureProcessor(with: photoSettings, willCapturePhotoAnimation: {
DispatchQueue.main.async {
self.previewView.videoPreviewLayer.opacity = 0
UIView.animate(withDuration: 0.25) { [unowned self] in
UIView.animate(withDuration: 0.25) {
self.previewView.videoPreviewLayer.opacity = 1
}
}
}, capturingLivePhoto: { capturing in
}, livePhotoCaptureHandler: { capturing in
/*
Because Live Photo captures can overlap, we need to keep track of the
number of in progress Live Photo captures to ensure that the
Live Photo label stays visible during these captures.
*/
self.sessionQueue.async { [unowned self] in
self.sessionQueue.async {
if capturing {
self.inProgressLivePhotoCapturesCount += 1
}
else {
} else {
self.inProgressLivePhotoCapturesCount -= 1
}
let inProgressLivePhotoCapturesCount = self.inProgressLivePhotoCapturesCount
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
if inProgressLivePhotoCapturesCount > 0 {
self.capturingLivePhotoLabel.isHidden = false
}
else if inProgressLivePhotoCapturesCount == 0 {
} else if inProgressLivePhotoCapturesCount == 0 {
self.capturingLivePhotoLabel.isHidden = true
}
else {
print("Error: In progress live photo capture count is less than 0");
} else {
print("Error: In progress live photo capture count is less than 0")
}
}
}
}, completed: { [unowned self] photoCaptureDelegate in
}, completionHandler: { photoCaptureProcessor in
// When the capture is complete, remove a reference to the photo capture delegate so it can be deallocated.
self.sessionQueue.async { [unowned self] in
self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = nil
self.sessionQueue.async {
self.inProgressPhotoCaptureDelegates[photoCaptureProcessor.requestedPhotoSettings.uniqueID] = nil
}
}
)
@ -577,8 +613,8 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
we store it in an array to maintain a strong reference to this object
until the capture is completed.
*/
self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = photoCaptureDelegate
self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureDelegate)
self.inProgressPhotoCaptureDelegates[photoCaptureProcessor.requestedPhotoSettings.uniqueID] = photoCaptureProcessor
self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureProcessor)
}
}
@ -587,35 +623,58 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
case off
}
private enum DepthDataDeliveryMode {
case on
case off
}
private var livePhotoMode: LivePhotoMode = .off
@IBOutlet private weak var livePhotoModeButton: UIButton!
@IBAction private func toggleLivePhotoMode(_ livePhotoModeButton: UIButton) {
sessionQueue.async { [unowned self] in
sessionQueue.async {
self.livePhotoMode = (self.livePhotoMode == .on) ? .off : .on
let livePhotoMode = self.livePhotoMode
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
if livePhotoMode == .on {
self.livePhotoModeButton.setTitle(NSLocalizedString("Live Photo Mode: On", comment: "Live photo mode button on title"), for: [])
}
else {
} else {
self.livePhotoModeButton.setTitle(NSLocalizedString("Live Photo Mode: Off", comment: "Live photo mode button off title"), for: [])
}
}
}
}
private var depthDataDeliveryMode: DepthDataDeliveryMode = .off
@IBOutlet private weak var depthDataDeliveryButton: UIButton!
@IBAction func toggleDepthDataDeliveryMode(_ depthDataDeliveryButton: UIButton) {
sessionQueue.async {
self.depthDataDeliveryMode = (self.depthDataDeliveryMode == .on) ? .off : .on
let depthDataDeliveryMode = self.depthDataDeliveryMode
DispatchQueue.main.async {
if depthDataDeliveryMode == .on {
self.depthDataDeliveryButton.setTitle(NSLocalizedString("Depth Data Delivery: On", comment: "Depth Data Delivery button on title"), for: [])
} else {
self.depthDataDeliveryButton.setTitle(NSLocalizedString("Depth Data Delivery: Off", comment: "Depth Data Delivery button off title"), for: [])
}
}
}
}
private var inProgressLivePhotoCapturesCount = 0
@IBOutlet var capturingLivePhotoLabel: UILabel!
// MARK: Recording Movies
private var movieFileOutput: AVCaptureMovieFileOutput? = nil
private var movieFileOutput: AVCaptureMovieFileOutput?
private var backgroundRecordingID: UIBackgroundTaskIdentifier? = nil
private var backgroundRecordingID: UIBackgroundTaskIdentifier?
@IBOutlet private weak var recordButton: UIButton!
@ -641,9 +700,9 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
before entering the session queue. We do this to ensure UI elements are
accessed on the main thread and session configuration is done on the session queue.
*/
let videoPreviewLayerOrientation = previewView.videoPreviewLayer.connection.videoOrientation
let videoPreviewLayerOrientation = previewView.videoPreviewLayer.connection?.videoOrientation
sessionQueue.async { [unowned self] in
sessionQueue.async {
if !movieFileOutput.isRecording {
if UIDevice.current.isMultitaskingSupported {
/*
@ -658,29 +717,34 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
}
// Update the orientation on the movie file output video connection before starting recording.
let movieFileOutputConnection = self.movieFileOutput?.connection(withMediaType: AVMediaTypeVideo)
movieFileOutputConnection?.videoOrientation = videoPreviewLayerOrientation
let movieFileOutputConnection = movieFileOutput.connection(with: .video)
movieFileOutputConnection?.videoOrientation = videoPreviewLayerOrientation!
let availableVideoCodecTypes = movieFileOutput.availableVideoCodecTypes
if availableVideoCodecTypes.contains(.hevc) {
movieFileOutput.setOutputSettings([AVVideoCodecKey: AVVideoCodecType.hevc], for: movieFileOutputConnection!)
}
// Start recording to a temporary file.
let outputFileName = NSUUID().uuidString
let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!)
movieFileOutput.startRecording(toOutputFileURL: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)
}
else {
movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)
} else {
movieFileOutput.stopRecording()
}
}
}
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
// Enable the Record button to let the user stop the recording.
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
self.recordButton.isEnabled = true
self.recordButton.setTitle(NSLocalizedString("Stop", comment: "Recording button stop title"), for: [])
}
}
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
/*
Note that currentBackgroundRecordingID is used to end the background task
associated with this recording. This allows a new recording to be started,
@ -691,13 +755,12 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
Note: Since we use a unique file path for each recording, a new recording will
not overwrite a recording currently being saved.
*/
func cleanup() {
func cleanUp() {
let path = outputFileURL.path
if FileManager.default.fileExists(atPath: path) {
do {
try FileManager.default.removeItem(atPath: path)
}
catch {
} catch {
print("Could not remove file at url: \(outputFileURL)")
}
}
@ -714,8 +777,8 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
var success = true
if error != nil {
print("Movie file finishing error: \(error)")
success = (((error as NSError).userInfo[AVErrorRecordingSuccessfullyFinishedKey] as AnyObject).boolValue)!
print("Movie file finishing error: \(String(describing: error))")
success = (((error! as NSError).userInfo[AVErrorRecordingSuccessfullyFinishedKey] as AnyObject).boolValue)!
}
if success {
@ -730,25 +793,23 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
creationRequest.addResource(with: .video, fileURL: outputFileURL, options: options)
}, completionHandler: { success, error in
if !success {
print("Could not save movie to photo library: \(error)")
print("Could not save movie to photo library: \(String(describing: error))")
}
cleanup()
cleanUp()
}
)
}
else {
cleanup()
} else {
cleanUp()
}
}
}
else {
cleanup()
} else {
cleanUp()
}
// Enable the Camera and Record buttons to let the user switch camera and start another recording.
DispatchQueue.main.async { [unowned self] in
DispatchQueue.main.async {
// Only enable the ability to change camera if the device has more than one camera.
self.cameraButton.isEnabled = self.videoDeviceDiscoverySession.uniqueDevicePositionsCount() > 1
self.cameraButton.isEnabled = self.videoDeviceDiscoverySession.uniqueDevicePositionsCount > 1
self.recordButton.isEnabled = true
self.captureModeControl.isEnabled = true
self.recordButton.setTitle(NSLocalizedString("Record", comment: "Recording button record title"), for: [])
@ -757,13 +818,32 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
// MARK: KVO and Notifications
private var sessionRunningObserveContext = 0
private var keyValueObservations = [NSKeyValueObservation]()
private func addObservers() {
session.addObserver(self, forKeyPath: "running", options: .new, context: &sessionRunningObserveContext)
let keyValueObservation = session.observe(\.isRunning, options: .new) { _, change in
guard let isSessionRunning = change.newValue else { return }
let isLivePhotoCaptureSupported = self.photoOutput.isLivePhotoCaptureSupported
let isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureEnabled
let isDepthDeliveryDataSupported = self.photoOutput.isDepthDataDeliverySupported
let isDepthDeliveryDataEnabled = self.photoOutput.isDepthDataDeliveryEnabled
NotificationCenter.default.addObserver(self, selector: #selector(subjectAreaDidChange), name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: videoDeviceInput.device)
NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: Notification.Name("AVCaptureSessionRuntimeErrorNotification"), object: session)
DispatchQueue.main.async {
// Only enable the ability to change camera if the device has more than one camera.
self.cameraButton.isEnabled = isSessionRunning && self.videoDeviceDiscoverySession.uniqueDevicePositionsCount > 1
self.recordButton.isEnabled = isSessionRunning && self.movieFileOutput != nil
self.photoButton.isEnabled = isSessionRunning
self.captureModeControl.isEnabled = isSessionRunning
self.livePhotoModeButton.isEnabled = isSessionRunning && isLivePhotoCaptureEnabled
self.livePhotoModeButton.isHidden = !(isSessionRunning && isLivePhotoCaptureSupported)
self.depthDataDeliveryButton.isEnabled = isSessionRunning && isDepthDeliveryDataEnabled
self.depthDataDeliveryButton.isHidden = !(isSessionRunning && isDepthDeliveryDataSupported)
}
}
keyValueObservations.append(keyValueObservation)
NotificationCenter.default.addObserver(self, selector: #selector(subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)
NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: .AVCaptureSessionRuntimeError, object: session)
/*
A session can only run when the app is full screen. It will be interrupted
@ -772,49 +852,29 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
interruptions and show a preview is paused message. See the documentation
of AVCaptureSessionWasInterruptedNotification for other interruption reasons.
*/
NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: Notification.Name("AVCaptureSessionWasInterruptedNotification"), object: session)
NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: Notification.Name("AVCaptureSessionInterruptionEndedNotification"), object: session)
NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: .AVCaptureSessionWasInterrupted, object: session)
NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: .AVCaptureSessionInterruptionEnded, object: session)
}
private func removeObservers() {
NotificationCenter.default.removeObserver(self)
session.removeObserver(self, forKeyPath: "running", context: &sessionRunningObserveContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &sessionRunningObserveContext {
let newValue = change?[.newKey] as AnyObject?
guard let isSessionRunning = newValue?.boolValue else { return }
let isLivePhotoCaptureSupported = photoOutput.isLivePhotoCaptureSupported
let isLivePhotoCaptureEnabled = photoOutput.isLivePhotoCaptureEnabled
DispatchQueue.main.async { [unowned self] in
// Only enable the ability to change camera if the device has more than one camera.
self.cameraButton.isEnabled = isSessionRunning && self.videoDeviceDiscoverySession.uniqueDevicePositionsCount() > 1
self.recordButton.isEnabled = isSessionRunning && self.movieFileOutput != nil
self.photoButton.isEnabled = isSessionRunning
self.captureModeControl.isEnabled = isSessionRunning
self.livePhotoModeButton.isEnabled = isSessionRunning && isLivePhotoCaptureEnabled
self.livePhotoModeButton.isHidden = !(isSessionRunning && isLivePhotoCaptureSupported)
}
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
for keyValueObservation in keyValueObservations {
keyValueObservation.invalidate()
}
keyValueObservations.removeAll()
}
@objc
func subjectAreaDidChange(notification: NSNotification) {
let devicePoint = CGPoint(x: 0.5, y: 0.5)
focus(with: .autoFocus, exposureMode: .continuousAutoExposure, at: devicePoint, monitorSubjectAreaChange: false)
focus(with: .continuousAutoFocus, exposureMode: .continuousAutoExposure, at: devicePoint, monitorSubjectAreaChange: false)
}
@objc
func sessionRuntimeError(notification: NSNotification) {
guard let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else {
return
}
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else { return }
let error = AVError(_nsError: errorValue)
print("Capture session runtime error: \(error)")
/*
@ -823,23 +883,22 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
to try to resume the session running.
*/
if error.code == .mediaServicesWereReset {
sessionQueue.async { [unowned self] in
sessionQueue.async {
if self.isSessionRunning {
self.session.startRunning()
self.isSessionRunning = self.session.isRunning
}
else {
DispatchQueue.main.async { [unowned self] in
} else {
DispatchQueue.main.async {
self.resumeButton.isHidden = false
}
}
}
}
else {
} else {
resumeButton.isHidden = false
}
}
@objc
func sessionWasInterrupted(notification: NSNotification) {
/*
In some scenarios we want to enable the user to resume the session running.
@ -849,19 +908,20 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
music playback in control center will not automatically resume the session
running. Also note that it is not always possible to resume, see `resumeInterruptedSession(_:)`.
*/
if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?, let reasonIntegerValue = userInfoValue.integerValue, let reason = AVCaptureSessionInterruptionReason(rawValue: reasonIntegerValue) {
if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?,
let reasonIntegerValue = userInfoValue.integerValue,
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
print("Capture session was interrupted with reason \(reason)")
var showResumeButton = false
if reason == AVCaptureSessionInterruptionReason.audioDeviceInUseByAnotherClient || reason == AVCaptureSessionInterruptionReason.videoDeviceInUseByAnotherClient {
if reason == .audioDeviceInUseByAnotherClient || reason == .videoDeviceInUseByAnotherClient {
showResumeButton = true
}
else if reason == AVCaptureSessionInterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps {
} else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps {
// Simply fade-in a label to inform the user that the camera is unavailable.
cameraUnavailableLabel.alpha = 0
cameraUnavailableLabel.isHidden = false
UIView.animate(withDuration: 0.25) { [unowned self] in
UIView.animate(withDuration: 0.25) {
self.cameraUnavailableLabel.alpha = 1
}
}
@ -870,30 +930,31 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
// Simply fade-in a button to enable the user to try to resume the session running.
resumeButton.alpha = 0
resumeButton.isHidden = false
UIView.animate(withDuration: 0.25) { [unowned self] in
UIView.animate(withDuration: 0.25) {
self.resumeButton.alpha = 1
}
}
}
}
@objc
func sessionInterruptionEnded(notification: NSNotification) {
print("Capture session interruption ended")
if !resumeButton.isHidden {
UIView.animate(withDuration: 0.25,
animations: { [unowned self] in
animations: {
self.resumeButton.alpha = 0
}, completion: { [unowned self] finished in
}, completion: { _ in
self.resumeButton.isHidden = true
}
)
}
if !cameraUnavailableLabel.isHidden {
UIView.animate(withDuration: 0.25,
animations: { [unowned self] in
animations: {
self.cameraUnavailableLabel.alpha = 0
}, completion: { [unowned self] finished in
}, completion: { _ in
self.cameraUnavailableLabel.isHidden = true
}
)
@ -901,33 +962,31 @@ class CameraViewController: UIViewController, AVCaptureFileOutputRecordingDelega
}
}
extension UIDeviceOrientation {
var videoOrientation: AVCaptureVideoOrientation? {
switch self {
case .portrait: return .portrait
case .portraitUpsideDown: return .portraitUpsideDown
case .landscapeLeft: return .landscapeRight
case .landscapeRight: return .landscapeLeft
extension AVCaptureVideoOrientation {
init?(deviceOrientation: UIDeviceOrientation) {
switch deviceOrientation {
case .portrait: self = .portrait
case .portraitUpsideDown: self = .portraitUpsideDown
case .landscapeLeft: self = .landscapeRight
case .landscapeRight: self = .landscapeLeft
default: return nil
}
}
init?(interfaceOrientation: UIInterfaceOrientation) {
switch interfaceOrientation {
case .portrait: self = .portrait
case .portraitUpsideDown: self = .portraitUpsideDown
case .landscapeLeft: self = .landscapeLeft
case .landscapeRight: self = .landscapeRight
default: return nil
}
}
}
extension UIInterfaceOrientation {
var videoOrientation: AVCaptureVideoOrientation? {
switch self {
case .portrait: return .portrait
case .portraitUpsideDown: return .portraitUpsideDown
case .landscapeLeft: return .landscapeLeft
case .landscapeRight: return .landscapeRight
default: return nil
}
}
}
extension AVCaptureDeviceDiscoverySession {
func uniqueDevicePositionsCount() -> Int {
var uniqueDevicePositions = [AVCaptureDevicePosition]()
extension AVCaptureDevice.DiscoverySession {
var uniqueDevicePositionsCount: Int {
var uniqueDevicePositions: [AVCaptureDevice.Position] = []
for device in devices {
if !uniqueDevicePositions.contains(device.position) {

View File

@ -1,32 +1,34 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Photo capture delegate.
Abstract:
Photo capture delegate.
*/
import AVFoundation
import Photos
class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
class PhotoCaptureProcessor: NSObject {
private(set) var requestedPhotoSettings: AVCapturePhotoSettings
private let willCapturePhotoAnimation: () -> ()
private let willCapturePhotoAnimation: () -> Void
private let capturingLivePhoto: (Bool) -> ()
private let livePhotoCaptureHandler: (Bool) -> Void
private let completed: (PhotoCaptureDelegate) -> ()
private let completionHandler: (PhotoCaptureProcessor) -> Void
private var photoData: Data? = nil
private var photoData: Data?
private var livePhotoCompanionMovieURL: URL? = nil
private var livePhotoCompanionMovieURL: URL?
init(with requestedPhotoSettings: AVCapturePhotoSettings, willCapturePhotoAnimation: @escaping () -> (), capturingLivePhoto: @escaping (Bool) -> (), completed: @escaping (PhotoCaptureDelegate) -> ()) {
init(with requestedPhotoSettings: AVCapturePhotoSettings,
willCapturePhotoAnimation: @escaping () -> Void,
livePhotoCaptureHandler: @escaping (Bool) -> Void,
completionHandler: @escaping (PhotoCaptureProcessor) -> Void) {
self.requestedPhotoSettings = requestedPhotoSettings
self.willCapturePhotoAnimation = willCapturePhotoAnimation
self.capturingLivePhoto = capturingLivePhoto
self.completed = completed
self.livePhotoCaptureHandler = livePhotoCaptureHandler
self.completionHandler = completionHandler
}
private func didFinish() {
@ -34,50 +36,53 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
if FileManager.default.fileExists(atPath: livePhotoCompanionMoviePath) {
do {
try FileManager.default.removeItem(atPath: livePhotoCompanionMoviePath)
}
catch {
} catch {
print("Could not remove file at url: \(livePhotoCompanionMoviePath)")
}
}
}
completed(self)
completionHandler(self)
}
func capture(_ captureOutput: AVCapturePhotoOutput, willBeginCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings) {
}
extension PhotoCaptureProcessor: AVCapturePhotoCaptureDelegate {
/*
This extension includes all the delegate callbacks for AVCapturePhotoCaptureDelegate protocol
*/
func photoOutput(_ output: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
if resolvedSettings.livePhotoMovieDimensions.width > 0 && resolvedSettings.livePhotoMovieDimensions.height > 0 {
capturingLivePhoto(true)
livePhotoCaptureHandler(true)
}
}
func capture(_ captureOutput: AVCapturePhotoOutput, willCapturePhotoForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings) {
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
willCapturePhotoAnimation()
}
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
if let photoSampleBuffer = photoSampleBuffer {
photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
}
else {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
} else {
photoData = photo.fileDataRepresentation()
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt outputFileURL: URL, resolvedSettings: AVCaptureResolvedPhotoSettings) {
livePhotoCaptureHandler(false)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if error != nil {
print("Error processing live photo companion movie: \(String(describing: error))")
return
}
}
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt outputFileURL: URL, resolvedSettings: AVCaptureResolvedPhotoSettings) {
capturingLivePhoto(false)
}
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplay photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if let _ = error {
print("Error processing live photo companion movie: \(error)")
return
}
livePhotoCompanionMovieURL = outputFileURL
}
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
didFinish()
@ -90,11 +95,13 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
return
}
PHPhotoLibrary.requestAuthorization { [unowned self] status in
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
PHPhotoLibrary.shared().performChanges({ [unowned self] in
PHPhotoLibrary.shared().performChanges({
let options = PHAssetResourceCreationOptions()
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: photoData, options: nil)
options.uniformTypeIdentifier = self.requestedPhotoSettings.processedFileType.map { $0.rawValue }
creationRequest.addResource(with: .photo, data: photoData, options: options)
if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL {
let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions()
@ -102,7 +109,7 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions)
}
}, completionHandler: { [unowned self] success, error in
}, completionHandler: { _, error in
if let error = error {
print("Error occurered while saving photo to photo library: \(error)")
}
@ -110,8 +117,7 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
self.didFinish()
}
)
}
else {
} else {
self.didFinish()
}
}

View File

@ -1,9 +1,8 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
See LICENSE.txt for this samples licensing information.
Abstract:
Application preview view.
Abstract:
Application preview view.
*/
import UIKit
@ -11,7 +10,11 @@ import AVFoundation
class PreviewView: UIView {
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
guard let layer = layer as? AVCaptureVideoPreviewLayer else {
fatalError("Expected `AVCaptureVideoPreviewLayer` type for layer. Check PreviewView.layerClass implementation.")
}
return layer
}
var session: AVCaptureSession? {

Some files were not shown because too many files have changed in this diff Show More