├── Complete ├── MetalCamera.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── alex.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── alex.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── MetalCamera │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── switchcamera.imageset │ │ ├── Contents.json │ │ └── switchcamera.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── CapturedImageView.swift │ ├── Info.plist │ ├── SceneDelegate.swift │ ├── ViewController+Extras.swift │ └── ViewController.swift ├── README.md └── Starter ├── MetalCamera.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── alex.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── alex.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── MetalCamera ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── switchcamera.imageset │ ├── Contents.json │ └── switchcamera.png ├── Base.lproj └── LaunchScreen.storyboard ├── CapturedImageView.swift ├── Info.plist ├── SceneDelegate.swift ├── ViewController+Extras.swift └── ViewController.swift /Complete/MetalCamera.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA13F9F524783EF10040E4F4 /* CapturedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */; }; 11 | FA13F9F7247993360040E4F4 /* ViewController+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */; }; 12 | FA92FA582476EB4A00EEE1F1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */; }; 13 | FA92FA5A2476EB4A00EEE1F1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */; }; 14 | FA92FA5C2476EB4A00EEE1F1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */; }; 15 | FA92FA612476EB4C00EEE1F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */; }; 16 | FA92FA642476EB4C00EEE1F1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturedImageView.swift; sourceTree = ""; }; 21 | FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+Extras.swift"; sourceTree = ""; }; 22 | FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MetalCamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 25 | FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | FA92FA632476EB4C00EEE1F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | FA92FA652476EB4C00EEE1F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | FA92FA512476EB4A00EEE1F1 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | FA92FA4B2476EB4A00EEE1F1 = { 43 | isa = PBXGroup; 44 | children = ( 45 | FA92FA562476EB4A00EEE1F1 /* MetalCamera */, 46 | FA92FA552476EB4A00EEE1F1 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | FA92FA552476EB4A00EEE1F1 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | FA92FA562476EB4A00EEE1F1 /* MetalCamera */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */, 62 | FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */, 63 | FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */, 64 | FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */, 65 | FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */, 66 | FA92FA652476EB4C00EEE1F1 /* Info.plist */, 67 | FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */, 68 | FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */, 69 | ); 70 | path = MetalCamera; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | FA92FA532476EB4A00EEE1F1 /* MetalCamera */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = FA92FA682476EB4C00EEE1F1 /* Build configuration list for PBXNativeTarget "MetalCamera" */; 79 | buildPhases = ( 80 | FA92FA502476EB4A00EEE1F1 /* Sources */, 81 | FA92FA512476EB4A00EEE1F1 /* Frameworks */, 82 | FA92FA522476EB4A00EEE1F1 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = MetalCamera; 89 | productName = MetalCamera; 90 | productReference = FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | FA92FA4C2476EB4A00EEE1F1 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 1140; 100 | LastUpgradeCheck = 1140; 101 | ORGANIZATIONNAME = ca.alexs; 102 | TargetAttributes = { 103 | FA92FA532476EB4A00EEE1F1 = { 104 | CreatedOnToolsVersion = 11.4.1; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = FA92FA4F2476EB4A00EEE1F1 /* Build configuration list for PBXProject "MetalCamera" */; 109 | compatibilityVersion = "Xcode 9.3"; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = FA92FA4B2476EB4A00EEE1F1; 117 | productRefGroup = FA92FA552476EB4A00EEE1F1 /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | FA92FA532476EB4A00EEE1F1 /* MetalCamera */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXResourcesBuildPhase section */ 127 | FA92FA522476EB4A00EEE1F1 /* Resources */ = { 128 | isa = PBXResourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | FA92FA642476EB4C00EEE1F1 /* LaunchScreen.storyboard in Resources */, 132 | FA92FA612476EB4C00EEE1F1 /* Assets.xcassets in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | FA92FA502476EB4A00EEE1F1 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | FA13F9F524783EF10040E4F4 /* CapturedImageView.swift in Sources */, 144 | FA92FA5C2476EB4A00EEE1F1 /* ViewController.swift in Sources */, 145 | FA13F9F7247993360040E4F4 /* ViewController+Extras.swift in Sources */, 146 | FA92FA582476EB4A00EEE1F1 /* AppDelegate.swift in Sources */, 147 | FA92FA5A2476EB4A00EEE1F1 /* SceneDelegate.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin PBXVariantGroup section */ 154 | FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | FA92FA632476EB4C00EEE1F1 /* Base */, 158 | ); 159 | name = LaunchScreen.storyboard; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXVariantGroup section */ 163 | 164 | /* Begin XCBuildConfiguration section */ 165 | FA92FA662476EB4C00EEE1F1 /* Debug */ = { 166 | isa = XCBuildConfiguration; 167 | buildSettings = { 168 | ALWAYS_SEARCH_USER_PATHS = NO; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_MODULES = YES; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_ENABLE_OBJC_WEAK = YES; 176 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_COMMA = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 181 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 182 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INFINITE_RECURSION = YES; 186 | CLANG_WARN_INT_CONVERSION = YES; 187 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 189 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEBUG_INFORMATION_FORMAT = dwarf; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | ENABLE_TESTABILITY = YES; 201 | GCC_C_LANGUAGE_STANDARD = gnu11; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_NO_COMMON_BLOCKS = YES; 204 | GCC_OPTIMIZATION_LEVEL = 0; 205 | GCC_PREPROCESSOR_DEFINITIONS = ( 206 | "DEBUG=1", 207 | "$(inherited)", 208 | ); 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 216 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 217 | MTL_FAST_MATH = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = iphoneos; 220 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 222 | }; 223 | name = Debug; 224 | }; 225 | FA92FA672476EB4C00EEE1F1 /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | ALWAYS_SEARCH_USER_PATHS = NO; 229 | CLANG_ANALYZER_NONNULL = YES; 230 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 231 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 232 | CLANG_CXX_LIBRARY = "libc++"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_ENABLE_OBJC_WEAK = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 252 | CLANG_WARN_STRICT_PROTOTYPES = YES; 253 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 254 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 255 | CLANG_WARN_UNREACHABLE_CODE = YES; 256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 257 | COPY_PHASE_STRIP = NO; 258 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 259 | ENABLE_NS_ASSERTIONS = NO; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu11; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 270 | MTL_ENABLE_DEBUG_INFO = NO; 271 | MTL_FAST_MATH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_COMPILATION_MODE = wholemodule; 274 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 275 | VALIDATE_PRODUCT = YES; 276 | }; 277 | name = Release; 278 | }; 279 | FA92FA692476EB4C00EEE1F1 /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 283 | CODE_SIGN_STYLE = Automatic; 284 | DEVELOPMENT_TEAM = YB435W5MT8; 285 | INFOPLIST_FILE = MetalCamera/Info.plist; 286 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 287 | LD_RUNPATH_SEARCH_PATHS = ( 288 | "$(inherited)", 289 | "@executable_path/Frameworks", 290 | ); 291 | PRODUCT_BUNDLE_IDENTIFIER = ca.alexs.MetalCamera; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_VERSION = 5.0; 294 | TARGETED_DEVICE_FAMILY = 1; 295 | }; 296 | name = Debug; 297 | }; 298 | FA92FA6A2476EB4C00EEE1F1 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 302 | CODE_SIGN_STYLE = Automatic; 303 | DEVELOPMENT_TEAM = YB435W5MT8; 304 | INFOPLIST_FILE = MetalCamera/Info.plist; 305 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 306 | LD_RUNPATH_SEARCH_PATHS = ( 307 | "$(inherited)", 308 | "@executable_path/Frameworks", 309 | ); 310 | PRODUCT_BUNDLE_IDENTIFIER = ca.alexs.MetalCamera; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_VERSION = 5.0; 313 | TARGETED_DEVICE_FAMILY = 1; 314 | }; 315 | name = Release; 316 | }; 317 | /* End XCBuildConfiguration section */ 318 | 319 | /* Begin XCConfigurationList section */ 320 | FA92FA4F2476EB4A00EEE1F1 /* Build configuration list for PBXProject "MetalCamera" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | FA92FA662476EB4C00EEE1F1 /* Debug */, 324 | FA92FA672476EB4C00EEE1F1 /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | FA92FA682476EB4C00EEE1F1 /* Build configuration list for PBXNativeTarget "MetalCamera" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | FA92FA692476EB4C00EEE1F1 /* Debug */, 333 | FA92FA6A2476EB4C00EEE1F1 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | /* End XCConfigurationList section */ 339 | }; 340 | rootObject = FA92FA4C2476EB4A00EEE1F1 /* Project object */; 341 | } 342 | -------------------------------------------------------------------------------- /Complete/MetalCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Complete/MetalCamera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Complete/MetalCamera.xcodeproj/project.xcworkspace/xcuserdata/alex.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barbulescualex/iOSMetalCamera/578f120a734748095834ad2ffeb3df7727192afa/Complete/MetalCamera.xcodeproj/project.xcworkspace/xcuserdata/alex.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Complete/MetalCamera.xcodeproj/xcuserdata/alex.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Complete/MetalCamera.xcodeproj/xcuserdata/alex.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MetalCamera.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Complete/MetalCamera/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | return true 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Complete/MetalCamera/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Complete/MetalCamera/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Complete/MetalCamera/Assets.xcassets/switchcamera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "switchcamera.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Complete/MetalCamera/Assets.xcassets/switchcamera.imageset/switchcamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barbulescualex/iOSMetalCamera/578f120a734748095834ad2ffeb3df7727192afa/Complete/MetalCamera/Assets.xcassets/switchcamera.imageset/switchcamera.png -------------------------------------------------------------------------------- /Complete/MetalCamera/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Complete/MetalCamera/CapturedImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CapturedImageView.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-22. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CapturedImageView : UIView { 12 | //MARK:- Vars 13 | var image : UIImage? { 14 | didSet { 15 | guard let image = image else {return} 16 | imageView.image = image 17 | } 18 | } 19 | 20 | //MARK:- View Components 21 | let imageView : UIImageView = { 22 | let imageView = UIImageView() 23 | imageView.contentMode = .scaleAspectFill 24 | imageView.layer.cornerRadius = 8 25 | imageView.clipsToBounds = true 26 | imageView.translatesAutoresizingMaskIntoConstraints = false 27 | return imageView 28 | }() 29 | 30 | //MARK:- Init 31 | override init(frame: CGRect) { 32 | super.init(frame: .zero) 33 | setupView() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | //MARK:- Setup 41 | func setupView(){ 42 | translatesAutoresizingMaskIntoConstraints = false 43 | backgroundColor = .white 44 | layer.cornerRadius = 10 45 | addSubview(imageView) 46 | 47 | NSLayoutConstraint.activate([ 48 | imageView.topAnchor.constraint(equalTo: topAnchor, constant: 2), 49 | imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2), 50 | imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2), 51 | imageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2), 52 | ]) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Complete/MetalCamera/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | If you don't enable this, this tutorial is useless 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Complete/MetalCamera/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let windowScene = (scene as? UIWindowScene) else { return } 16 | 17 | window = UIWindow(windowScene: windowScene) 18 | let rootVC = ViewController() 19 | window?.rootViewController = rootVC 20 | window?.makeKeyAndVisible() 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Complete/MetalCamera/ViewController+Extras.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+Extras.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-23. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | extension ViewController { 13 | //MARK:- View Setup 14 | func setupView(){ 15 | view.backgroundColor = .black 16 | mtkView.translatesAutoresizingMaskIntoConstraints = false 17 | view.addSubview(mtkView) 18 | view.addSubview(switchCameraButton) 19 | view.addSubview(captureImageButton) 20 | view.addSubview(capturedImageView) 21 | 22 | NSLayoutConstraint.activate([ 23 | switchCameraButton.widthAnchor.constraint(equalToConstant: 30), 24 | switchCameraButton.heightAnchor.constraint(equalToConstant: 30), 25 | switchCameraButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), 26 | switchCameraButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), 27 | 28 | captureImageButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), 29 | captureImageButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), 30 | captureImageButton.widthAnchor.constraint(equalToConstant: 50), 31 | captureImageButton.heightAnchor.constraint(equalToConstant: 50), 32 | 33 | capturedImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), 34 | capturedImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), 35 | capturedImageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25), 36 | capturedImageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5, constant: -70), 37 | 38 | mtkView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 39 | mtkView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 40 | mtkView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 41 | mtkView.topAnchor.constraint(equalTo: view.topAnchor) 42 | ]) 43 | 44 | switchCameraButton.addTarget(self, action: #selector(switchCamera(_:)), for: .touchUpInside) 45 | captureImageButton.addTarget(self, action: #selector(captureImage(_:)), for: .touchUpInside) 46 | } 47 | 48 | //MARK:- Permissions 49 | func checkPermissions() { 50 | let cameraAuthStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) 51 | switch cameraAuthStatus { 52 | case .authorized: 53 | return 54 | case .denied: 55 | abort() 56 | case .notDetermined: 57 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: 58 | { (authorized) in 59 | if(!authorized){ 60 | abort() 61 | } 62 | }) 63 | case .restricted: 64 | abort() 65 | @unknown default: 66 | fatalError() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Complete/MetalCamera/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import MetalKit 12 | 13 | class ViewController: UIViewController { 14 | //MARK:- Vars 15 | var captureSession : AVCaptureSession! 16 | 17 | var backCamera : AVCaptureDevice! 18 | var frontCamera : AVCaptureDevice! 19 | var backInput : AVCaptureInput! 20 | var frontInput : AVCaptureInput! 21 | 22 | var videoOutput : AVCaptureVideoDataOutput! 23 | 24 | var takePicture = false 25 | var backCameraOn = true 26 | 27 | //metal 28 | var metalDevice : MTLDevice! 29 | var metalCommandQueue : MTLCommandQueue! 30 | 31 | //core image 32 | var ciContext : CIContext! 33 | 34 | var currentCIImage : CIImage? 35 | 36 | let fadeFilter = CIFilter(name: "CIPhotoEffectFade") 37 | let sepiaFilter = CIFilter(name: "CISepiaTone") 38 | 39 | //MARK:- View Components 40 | let switchCameraButton : UIButton = { 41 | let button = UIButton() 42 | let image = UIImage(named: "switchcamera")?.withRenderingMode(.alwaysTemplate) 43 | button.setImage(image, for: .normal) 44 | button.tintColor = .white 45 | button.translatesAutoresizingMaskIntoConstraints = false 46 | return button 47 | }() 48 | 49 | let captureImageButton : UIButton = { 50 | let button = UIButton() 51 | button.backgroundColor = .white 52 | button.tintColor = .white 53 | button.layer.cornerRadius = 25 54 | button.translatesAutoresizingMaskIntoConstraints = false 55 | return button 56 | }() 57 | 58 | let capturedImageView = CapturedImageView() 59 | 60 | let mtkView = MTKView() 61 | 62 | //MARK:- Life Cycle 63 | override func viewDidLoad() { 64 | super.viewDidLoad() 65 | setupView() 66 | } 67 | 68 | override func viewDidAppear(_ animated: Bool) { 69 | super.viewDidAppear(animated) 70 | checkPermissions() 71 | setupMetal() 72 | setupCoreImage() 73 | setupAndStartCaptureSession() 74 | } 75 | 76 | //MARK:- Camera Setup 77 | func setupAndStartCaptureSession(){ 78 | DispatchQueue.global(qos: .userInitiated).async{ 79 | //init session 80 | self.captureSession = AVCaptureSession() 81 | //start configuration 82 | self.captureSession.beginConfiguration() 83 | 84 | //session specific configuration 85 | if self.captureSession.canSetSessionPreset(.photo) { 86 | self.captureSession.sessionPreset = .photo 87 | } 88 | self.captureSession.automaticallyConfiguresCaptureDeviceForWideColor = true 89 | 90 | //setup inputs 91 | self.setupInputs() 92 | 93 | //setup output 94 | self.setupOutput() 95 | 96 | //commit configuration 97 | self.captureSession.commitConfiguration() 98 | //start running it 99 | self.captureSession.startRunning() 100 | } 101 | } 102 | 103 | func setupInputs(){ 104 | //get back camera 105 | if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { 106 | backCamera = device 107 | } else { 108 | //handle this appropriately for production purposes 109 | fatalError("no back camera") 110 | } 111 | 112 | //get front camera 113 | if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) { 114 | frontCamera = device 115 | } else { 116 | fatalError("no front camera") 117 | } 118 | 119 | //now we need to create an input objects from our devices 120 | guard let bInput = try? AVCaptureDeviceInput(device: backCamera) else { 121 | fatalError("could not create input device from back camera") 122 | } 123 | backInput = bInput 124 | if !captureSession.canAddInput(backInput) { 125 | fatalError("could not add back camera input to capture session") 126 | } 127 | 128 | guard let fInput = try? AVCaptureDeviceInput(device: frontCamera) else { 129 | fatalError("could not create input device from front camera") 130 | } 131 | frontInput = fInput 132 | if !captureSession.canAddInput(frontInput) { 133 | fatalError("could not add front camera input to capture session") 134 | } 135 | 136 | //connect back camera input to session 137 | captureSession.addInput(backInput) 138 | } 139 | 140 | func setupOutput(){ 141 | videoOutput = AVCaptureVideoDataOutput() 142 | let videoQueue = DispatchQueue(label: "videoQueue", qos: .userInteractive) 143 | videoOutput.setSampleBufferDelegate(self, queue: videoQueue) 144 | 145 | if captureSession.canAddOutput(videoOutput) { 146 | captureSession.addOutput(videoOutput) 147 | } else { 148 | fatalError("could not add video output") 149 | } 150 | 151 | videoOutput.connections.first?.videoOrientation = .portrait 152 | } 153 | 154 | func switchCameraInput(){ 155 | //don't let user spam the button, fun for the user, not fun for performance 156 | switchCameraButton.isUserInteractionEnabled = false 157 | 158 | //reconfigure the input 159 | captureSession.beginConfiguration() 160 | if backCameraOn { 161 | captureSession.removeInput(backInput) 162 | captureSession.addInput(frontInput) 163 | backCameraOn = false 164 | } else { 165 | captureSession.removeInput(frontInput) 166 | captureSession.addInput(backInput) 167 | backCameraOn = true 168 | } 169 | 170 | //deal with the connection again for portrait mode 171 | videoOutput.connections.first?.videoOrientation = .portrait 172 | 173 | //mirror video if front camera 174 | videoOutput.connections.first?.isVideoMirrored = !backCameraOn 175 | 176 | //commit config 177 | captureSession.commitConfiguration() 178 | 179 | //acitvate the camera button again 180 | switchCameraButton.isUserInteractionEnabled = true 181 | } 182 | 183 | //MARK:- Metal 184 | func setupMetal(){ 185 | //fetch the default gpu of the device (only one on iOS devices) 186 | metalDevice = MTLCreateSystemDefaultDevice() 187 | 188 | //tell our MTKView which gpu to use 189 | mtkView.device = metalDevice 190 | 191 | //tell our MTKView to use explicit drawing meaning we have to call .draw() on it 192 | mtkView.isPaused = true 193 | mtkView.enableSetNeedsDisplay = false 194 | 195 | //create a command queue to be able to send down instructions to the GPU 196 | metalCommandQueue = metalDevice.makeCommandQueue() 197 | 198 | //conform to our MTKView's delegate 199 | mtkView.delegate = self 200 | 201 | //let it's drawable texture be writen to 202 | mtkView.framebufferOnly = false 203 | } 204 | 205 | //MARK:- Core Image 206 | func setupCoreImage(){ 207 | ciContext = CIContext(mtlDevice: metalDevice) 208 | setupFilters() 209 | } 210 | 211 | //MARK: Filters 212 | func setupFilters(){ 213 | sepiaFilter?.setValue(NSNumber(value: 1), forKeyPath: "inputIntensity") 214 | } 215 | 216 | func applyFilters(inputImage image: CIImage) -> CIImage? { 217 | var filteredImage : CIImage? 218 | 219 | //apply filters 220 | sepiaFilter?.setValue(image, forKeyPath: kCIInputImageKey) 221 | filteredImage = sepiaFilter?.outputImage 222 | 223 | fadeFilter?.setValue(image, forKeyPath: kCIInputImageKey) 224 | filteredImage = fadeFilter?.outputImage 225 | 226 | return filteredImage 227 | } 228 | 229 | //MARK:- Actions 230 | @objc func captureImage(_ sender: UIButton?){ 231 | takePicture = true 232 | } 233 | 234 | @objc func switchCamera(_ sender: UIButton?){ 235 | switchCameraInput() 236 | } 237 | 238 | } 239 | 240 | extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 241 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 242 | //try and get a CVImageBuffer out of the sample buffer 243 | guard let cvBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 244 | return 245 | } 246 | 247 | //get a CIImage out of the CVImageBuffer 248 | let ciImage = CIImage(cvImageBuffer: cvBuffer) 249 | 250 | //filter it 251 | guard let filteredCIImage = applyFilters(inputImage: ciImage) else { 252 | return 253 | } 254 | 255 | self.currentCIImage = filteredCIImage 256 | 257 | mtkView.draw() 258 | 259 | //get UIImage out of CIImage 260 | let uiImage = UIImage(ciImage: filteredCIImage) 261 | 262 | if !takePicture { 263 | return //we have nothing to do with the image buffer 264 | } 265 | 266 | DispatchQueue.main.async { 267 | self.capturedImageView.image = uiImage 268 | self.takePicture = false 269 | } 270 | } 271 | 272 | } 273 | 274 | extension ViewController : MTKViewDelegate { 275 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 276 | //tells us the drawable's size has changed 277 | } 278 | 279 | func draw(in view: MTKView) { 280 | //create command buffer for ciContext to use to encode it's rendering instructions to our GPU 281 | guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else { 282 | return 283 | } 284 | 285 | //make sure we actually have a ciImage to work with 286 | guard let ciImage = currentCIImage else { 287 | return 288 | } 289 | 290 | //make sure the current drawable object for this metal view is available (it's not in use by the previous draw cycle) 291 | guard let currentDrawable = view.currentDrawable else { 292 | return 293 | } 294 | 295 | //make sure frame is centered on screen 296 | let heightOfciImage = ciImage.extent.height 297 | let heightOfDrawable = view.drawableSize.height 298 | let yOffsetFromBottom = (heightOfDrawable - heightOfciImage)/2 299 | 300 | //render into the metal texture 301 | self.ciContext.render(ciImage, 302 | to: currentDrawable.texture, 303 | commandBuffer: commandBuffer, 304 | bounds: CGRect(origin: CGPoint(x: 0, y: -yOffsetFromBottom), size: view.drawableSize), 305 | colorSpace: CGColorSpaceCreateDeviceRGB()) 306 | 307 | //register where to draw the instructions in the command buffer once it executes 308 | commandBuffer.present(currentDrawable) 309 | //commit the command to the queue so it executes 310 | commandBuffer.commit() 311 | } 312 | } 313 | 314 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetalCamera 2 | Tutorial on implementing a camera using Metal in Swift on iOS 3 | -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA13F9F524783EF10040E4F4 /* CapturedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */; }; 11 | FA13F9F7247993360040E4F4 /* ViewController+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */; }; 12 | FA92FA582476EB4A00EEE1F1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */; }; 13 | FA92FA5A2476EB4A00EEE1F1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */; }; 14 | FA92FA5C2476EB4A00EEE1F1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */; }; 15 | FA92FA612476EB4C00EEE1F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */; }; 16 | FA92FA642476EB4C00EEE1F1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturedImageView.swift; sourceTree = ""; }; 21 | FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+Extras.swift"; sourceTree = ""; }; 22 | FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MetalCamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 25 | FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | FA92FA632476EB4C00EEE1F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | FA92FA652476EB4C00EEE1F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | FA92FA512476EB4A00EEE1F1 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | FA92FA4B2476EB4A00EEE1F1 = { 43 | isa = PBXGroup; 44 | children = ( 45 | FA92FA562476EB4A00EEE1F1 /* MetalCamera */, 46 | FA92FA552476EB4A00EEE1F1 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | FA92FA552476EB4A00EEE1F1 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | FA92FA562476EB4A00EEE1F1 /* MetalCamera */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | FA92FA572476EB4A00EEE1F1 /* AppDelegate.swift */, 62 | FA92FA592476EB4A00EEE1F1 /* SceneDelegate.swift */, 63 | FA92FA5B2476EB4A00EEE1F1 /* ViewController.swift */, 64 | FA92FA602476EB4C00EEE1F1 /* Assets.xcassets */, 65 | FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */, 66 | FA92FA652476EB4C00EEE1F1 /* Info.plist */, 67 | FA13F9F424783EF10040E4F4 /* CapturedImageView.swift */, 68 | FA13F9F6247993360040E4F4 /* ViewController+Extras.swift */, 69 | ); 70 | path = MetalCamera; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | FA92FA532476EB4A00EEE1F1 /* MetalCamera */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = FA92FA682476EB4C00EEE1F1 /* Build configuration list for PBXNativeTarget "MetalCamera" */; 79 | buildPhases = ( 80 | FA92FA502476EB4A00EEE1F1 /* Sources */, 81 | FA92FA512476EB4A00EEE1F1 /* Frameworks */, 82 | FA92FA522476EB4A00EEE1F1 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = MetalCamera; 89 | productName = MetalCamera; 90 | productReference = FA92FA542476EB4A00EEE1F1 /* MetalCamera.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | FA92FA4C2476EB4A00EEE1F1 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 1140; 100 | LastUpgradeCheck = 1140; 101 | ORGANIZATIONNAME = ca.alexs; 102 | TargetAttributes = { 103 | FA92FA532476EB4A00EEE1F1 = { 104 | CreatedOnToolsVersion = 11.4.1; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = FA92FA4F2476EB4A00EEE1F1 /* Build configuration list for PBXProject "MetalCamera" */; 109 | compatibilityVersion = "Xcode 9.3"; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = FA92FA4B2476EB4A00EEE1F1; 117 | productRefGroup = FA92FA552476EB4A00EEE1F1 /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | FA92FA532476EB4A00EEE1F1 /* MetalCamera */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXResourcesBuildPhase section */ 127 | FA92FA522476EB4A00EEE1F1 /* Resources */ = { 128 | isa = PBXResourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | FA92FA642476EB4C00EEE1F1 /* LaunchScreen.storyboard in Resources */, 132 | FA92FA612476EB4C00EEE1F1 /* Assets.xcassets in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | FA92FA502476EB4A00EEE1F1 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | FA13F9F524783EF10040E4F4 /* CapturedImageView.swift in Sources */, 144 | FA92FA5C2476EB4A00EEE1F1 /* ViewController.swift in Sources */, 145 | FA13F9F7247993360040E4F4 /* ViewController+Extras.swift in Sources */, 146 | FA92FA582476EB4A00EEE1F1 /* AppDelegate.swift in Sources */, 147 | FA92FA5A2476EB4A00EEE1F1 /* SceneDelegate.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin PBXVariantGroup section */ 154 | FA92FA622476EB4C00EEE1F1 /* LaunchScreen.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | FA92FA632476EB4C00EEE1F1 /* Base */, 158 | ); 159 | name = LaunchScreen.storyboard; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXVariantGroup section */ 163 | 164 | /* Begin XCBuildConfiguration section */ 165 | FA92FA662476EB4C00EEE1F1 /* Debug */ = { 166 | isa = XCBuildConfiguration; 167 | buildSettings = { 168 | ALWAYS_SEARCH_USER_PATHS = NO; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_MODULES = YES; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_ENABLE_OBJC_WEAK = YES; 176 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_COMMA = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 181 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 182 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INFINITE_RECURSION = YES; 186 | CLANG_WARN_INT_CONVERSION = YES; 187 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 189 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEBUG_INFORMATION_FORMAT = dwarf; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | ENABLE_TESTABILITY = YES; 201 | GCC_C_LANGUAGE_STANDARD = gnu11; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_NO_COMMON_BLOCKS = YES; 204 | GCC_OPTIMIZATION_LEVEL = 0; 205 | GCC_PREPROCESSOR_DEFINITIONS = ( 206 | "DEBUG=1", 207 | "$(inherited)", 208 | ); 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 216 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 217 | MTL_FAST_MATH = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = iphoneos; 220 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 222 | }; 223 | name = Debug; 224 | }; 225 | FA92FA672476EB4C00EEE1F1 /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | ALWAYS_SEARCH_USER_PATHS = NO; 229 | CLANG_ANALYZER_NONNULL = YES; 230 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 231 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 232 | CLANG_CXX_LIBRARY = "libc++"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_ENABLE_OBJC_WEAK = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 252 | CLANG_WARN_STRICT_PROTOTYPES = YES; 253 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 254 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 255 | CLANG_WARN_UNREACHABLE_CODE = YES; 256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 257 | COPY_PHASE_STRIP = NO; 258 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 259 | ENABLE_NS_ASSERTIONS = NO; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu11; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 270 | MTL_ENABLE_DEBUG_INFO = NO; 271 | MTL_FAST_MATH = YES; 272 | SDKROOT = iphoneos; 273 | SWIFT_COMPILATION_MODE = wholemodule; 274 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 275 | VALIDATE_PRODUCT = YES; 276 | }; 277 | name = Release; 278 | }; 279 | FA92FA692476EB4C00EEE1F1 /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 283 | CODE_SIGN_STYLE = Automatic; 284 | DEVELOPMENT_TEAM = YB435W5MT8; 285 | INFOPLIST_FILE = MetalCamera/Info.plist; 286 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 287 | LD_RUNPATH_SEARCH_PATHS = ( 288 | "$(inherited)", 289 | "@executable_path/Frameworks", 290 | ); 291 | PRODUCT_BUNDLE_IDENTIFIER = ca.alexs.MetalCamera; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_VERSION = 5.0; 294 | TARGETED_DEVICE_FAMILY = 1; 295 | }; 296 | name = Debug; 297 | }; 298 | FA92FA6A2476EB4C00EEE1F1 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 302 | CODE_SIGN_STYLE = Automatic; 303 | DEVELOPMENT_TEAM = YB435W5MT8; 304 | INFOPLIST_FILE = MetalCamera/Info.plist; 305 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 306 | LD_RUNPATH_SEARCH_PATHS = ( 307 | "$(inherited)", 308 | "@executable_path/Frameworks", 309 | ); 310 | PRODUCT_BUNDLE_IDENTIFIER = ca.alexs.MetalCamera; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_VERSION = 5.0; 313 | TARGETED_DEVICE_FAMILY = 1; 314 | }; 315 | name = Release; 316 | }; 317 | /* End XCBuildConfiguration section */ 318 | 319 | /* Begin XCConfigurationList section */ 320 | FA92FA4F2476EB4A00EEE1F1 /* Build configuration list for PBXProject "MetalCamera" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | FA92FA662476EB4C00EEE1F1 /* Debug */, 324 | FA92FA672476EB4C00EEE1F1 /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | FA92FA682476EB4C00EEE1F1 /* Build configuration list for PBXNativeTarget "MetalCamera" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | FA92FA692476EB4C00EEE1F1 /* Debug */, 333 | FA92FA6A2476EB4C00EEE1F1 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | /* End XCConfigurationList section */ 339 | }; 340 | rootObject = FA92FA4C2476EB4A00EEE1F1 /* Project object */; 341 | } 342 | -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/project.xcworkspace/xcuserdata/alex.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barbulescualex/iOSMetalCamera/578f120a734748095834ad2ffeb3df7727192afa/Starter/MetalCamera.xcodeproj/project.xcworkspace/xcuserdata/alex.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/xcuserdata/alex.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Starter/MetalCamera.xcodeproj/xcuserdata/alex.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MetalCamera.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Starter/MetalCamera/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | return true 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Starter/MetalCamera/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Starter/MetalCamera/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Starter/MetalCamera/Assets.xcassets/switchcamera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "switchcamera.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Starter/MetalCamera/Assets.xcassets/switchcamera.imageset/switchcamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barbulescualex/iOSMetalCamera/578f120a734748095834ad2ffeb3df7727192afa/Starter/MetalCamera/Assets.xcassets/switchcamera.imageset/switchcamera.png -------------------------------------------------------------------------------- /Starter/MetalCamera/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Starter/MetalCamera/CapturedImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CapturedImageView.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-22. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CapturedImageView : UIView { 12 | //MARK:- Vars 13 | var image : UIImage? { 14 | didSet { 15 | guard let image = image else {return} 16 | imageView.image = image 17 | } 18 | } 19 | 20 | //MARK:- View Components 21 | let imageView : UIImageView = { 22 | let imageView = UIImageView() 23 | imageView.contentMode = .scaleAspectFill 24 | imageView.layer.cornerRadius = 8 25 | imageView.clipsToBounds = true 26 | imageView.translatesAutoresizingMaskIntoConstraints = false 27 | return imageView 28 | }() 29 | 30 | //MARK:- Init 31 | override init(frame: CGRect) { 32 | super.init(frame: .zero) 33 | setupView() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | //MARK:- Setup 41 | func setupView(){ 42 | translatesAutoresizingMaskIntoConstraints = false 43 | backgroundColor = .white 44 | layer.cornerRadius = 10 45 | addSubview(imageView) 46 | 47 | NSLayoutConstraint.activate([ 48 | imageView.topAnchor.constraint(equalTo: topAnchor, constant: 2), 49 | imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2), 50 | imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2), 51 | imageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2), 52 | ]) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Starter/MetalCamera/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | If you don't enable this, this tutorial is useless 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Starter/MetalCamera/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let windowScene = (scene as? UIWindowScene) else { return } 16 | 17 | window = UIWindow(windowScene: windowScene) 18 | let rootVC = ViewController() 19 | window?.rootViewController = rootVC 20 | window?.makeKeyAndVisible() 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Starter/MetalCamera/ViewController+Extras.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+Extras.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-23. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | extension ViewController { 13 | //MARK:- View Setup 14 | func setupView(){ 15 | view.backgroundColor = .black 16 | view.addSubview(switchCameraButton) 17 | view.addSubview(captureImageButton) 18 | view.addSubview(capturedImageView) 19 | 20 | NSLayoutConstraint.activate([ 21 | switchCameraButton.widthAnchor.constraint(equalToConstant: 30), 22 | switchCameraButton.heightAnchor.constraint(equalToConstant: 30), 23 | switchCameraButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), 24 | switchCameraButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), 25 | 26 | captureImageButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), 27 | captureImageButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), 28 | captureImageButton.widthAnchor.constraint(equalToConstant: 50), 29 | captureImageButton.heightAnchor.constraint(equalToConstant: 50), 30 | 31 | capturedImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), 32 | capturedImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), 33 | capturedImageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25), 34 | capturedImageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5, constant: -70) 35 | ]) 36 | 37 | switchCameraButton.addTarget(self, action: #selector(switchCamera(_:)), for: .touchUpInside) 38 | captureImageButton.addTarget(self, action: #selector(captureImage(_:)), for: .touchUpInside) 39 | } 40 | 41 | //MARK:- Permissions 42 | func checkPermissions() { 43 | let cameraAuthStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) 44 | switch cameraAuthStatus { 45 | case .authorized: 46 | return 47 | case .denied: 48 | abort() 49 | case .notDetermined: 50 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: 51 | { (authorized) in 52 | if(!authorized){ 53 | abort() 54 | } 55 | }) 56 | case .restricted: 57 | abort() 58 | @unknown default: 59 | fatalError() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Starter/MetalCamera/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MetalCamera 4 | // 5 | // Created by Alex Barbulescu on 2020-05-21. 6 | // Copyright © 2020 ca.alexs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class ViewController: UIViewController { 13 | //MARK:- Vars 14 | var captureSession : AVCaptureSession! 15 | 16 | var backCamera : AVCaptureDevice! 17 | var frontCamera : AVCaptureDevice! 18 | var backInput : AVCaptureInput! 19 | var frontInput : AVCaptureInput! 20 | 21 | var previewLayer : AVCaptureVideoPreviewLayer! 22 | 23 | var videoOutput : AVCaptureVideoDataOutput! 24 | 25 | var takePicture = false 26 | var backCameraOn = true 27 | 28 | //MARK:- View Components 29 | let switchCameraButton : UIButton = { 30 | let button = UIButton() 31 | let image = UIImage(named: "switchcamera")?.withRenderingMode(.alwaysTemplate) 32 | button.setImage(image, for: .normal) 33 | button.tintColor = .white 34 | button.translatesAutoresizingMaskIntoConstraints = false 35 | return button 36 | }() 37 | 38 | let captureImageButton : UIButton = { 39 | let button = UIButton() 40 | button.backgroundColor = .white 41 | button.tintColor = .white 42 | button.layer.cornerRadius = 25 43 | button.translatesAutoresizingMaskIntoConstraints = false 44 | return button 45 | }() 46 | 47 | let capturedImageView = CapturedImageView() 48 | 49 | //MARK:- Life Cycle 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | setupView() 53 | } 54 | 55 | override func viewDidAppear(_ animated: Bool) { 56 | super.viewDidAppear(animated) 57 | checkPermissions() 58 | setupAndStartCaptureSession() 59 | } 60 | 61 | //MARK:- Camera Setup 62 | func setupAndStartCaptureSession(){ 63 | DispatchQueue.global(qos: .userInitiated).async{ 64 | //init session 65 | self.captureSession = AVCaptureSession() 66 | //start configuration 67 | self.captureSession.beginConfiguration() 68 | 69 | //session specific configuration 70 | if self.captureSession.canSetSessionPreset(.photo) { 71 | self.captureSession.sessionPreset = .photo 72 | } 73 | self.captureSession.automaticallyConfiguresCaptureDeviceForWideColor = true 74 | 75 | //setup inputs 76 | self.setupInputs() 77 | 78 | DispatchQueue.main.async { 79 | //setup preview layer 80 | self.setupPreviewLayer() 81 | } 82 | 83 | //setup output 84 | self.setupOutput() 85 | 86 | //commit configuration 87 | self.captureSession.commitConfiguration() 88 | //start running it 89 | self.captureSession.startRunning() 90 | } 91 | } 92 | 93 | func setupInputs(){ 94 | //get back camera 95 | if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { 96 | backCamera = device 97 | } else { 98 | //handle this appropriately for production purposes 99 | fatalError("no back camera") 100 | } 101 | 102 | //get front camera 103 | if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) { 104 | frontCamera = device 105 | } else { 106 | fatalError("no front camera") 107 | } 108 | 109 | //now we need to create an input objects from our devices 110 | guard let bInput = try? AVCaptureDeviceInput(device: backCamera) else { 111 | fatalError("could not create input device from back camera") 112 | } 113 | backInput = bInput 114 | if !captureSession.canAddInput(backInput) { 115 | fatalError("could not add back camera input to capture session") 116 | } 117 | 118 | guard let fInput = try? AVCaptureDeviceInput(device: frontCamera) else { 119 | fatalError("could not create input device from front camera") 120 | } 121 | frontInput = fInput 122 | if !captureSession.canAddInput(frontInput) { 123 | fatalError("could not add front camera input to capture session") 124 | } 125 | 126 | //connect back camera input to session 127 | captureSession.addInput(backInput) 128 | } 129 | 130 | func setupOutput(){ 131 | videoOutput = AVCaptureVideoDataOutput() 132 | let videoQueue = DispatchQueue(label: "videoQueue", qos: .userInteractive) 133 | videoOutput.setSampleBufferDelegate(self, queue: videoQueue) 134 | 135 | if captureSession.canAddOutput(videoOutput) { 136 | captureSession.addOutput(videoOutput) 137 | } else { 138 | fatalError("could not add video output") 139 | } 140 | 141 | videoOutput.connections.first?.videoOrientation = .portrait 142 | } 143 | 144 | func setupPreviewLayer(){ 145 | previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 146 | view.layer.insertSublayer(previewLayer, below: switchCameraButton.layer) 147 | previewLayer.frame = self.view.layer.frame 148 | } 149 | 150 | func switchCameraInput(){ 151 | //don't let user spam the button, fun for the user, not fun for performance 152 | switchCameraButton.isUserInteractionEnabled = false 153 | 154 | //reconfigure the input 155 | captureSession.beginConfiguration() 156 | if backCameraOn { 157 | captureSession.removeInput(backInput) 158 | captureSession.addInput(frontInput) 159 | backCameraOn = false 160 | } else { 161 | captureSession.removeInput(frontInput) 162 | captureSession.addInput(backInput) 163 | backCameraOn = true 164 | } 165 | 166 | //deal with the connection again for portrait mode 167 | videoOutput.connections.first?.videoOrientation = .portrait 168 | 169 | //mirror the video stream for front camera 170 | videoOutput.connections.first?.isVideoMirrored = !backCameraOn 171 | 172 | //commit config 173 | captureSession.commitConfiguration() 174 | 175 | //acitvate the camera button again 176 | switchCameraButton.isUserInteractionEnabled = true 177 | } 178 | 179 | //MARK:- Actions 180 | @objc func captureImage(_ sender: UIButton?){ 181 | takePicture = true 182 | } 183 | 184 | @objc func switchCamera(_ sender: UIButton?){ 185 | switchCameraInput() 186 | } 187 | 188 | } 189 | 190 | extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 191 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 192 | if !takePicture { 193 | return //we have nothing to do with the image buffer 194 | } 195 | 196 | //try and get a CVImageBuffer out of the sample buffer 197 | guard let cvBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 198 | return 199 | } 200 | 201 | //get a CIImage out of the CVImageBuffer 202 | let ciImage = CIImage(cvImageBuffer: cvBuffer) 203 | 204 | //get UIImage out of CIImage 205 | let uiImage = UIImage(ciImage: ciImage) 206 | 207 | DispatchQueue.main.async { 208 | self.capturedImageView.image = uiImage 209 | self.takePicture = false 210 | } 211 | } 212 | 213 | } 214 | 215 | --------------------------------------------------------------------------------