機械学習のcoreML用にswiftを使ったカメラアプリを作ってみた。その時、
・従来のAppKitApp Delegate形式
から
・新しい記述形式のSwiftUI App形式
を主にした
・SceneDelegate.swift
・AppDelegate.swift
を使ったカメラアプリの作成して使う方法を簡単にまとめる
目次
1.ファイル構成
2.Info.plistの設定
3.ファイル一覧
1.ファイル構成
メインのファイル構成$ tree ResNet50prj ├── Interactor.swift ├── CameraController.swift ├── CameraViewController.swift ├── ContentView.swift ├── Info.plist ├── AppDelegate.swift └── SceneDelegate.swift
Info.plistの設定
・カメラのアクセス許可
・SceneDelegateとAppDeletegateを使った形式でアプリを使うためにApplication Scene Manifestを追加
以下のように設定
ファイル一覧
AppDelegate.swift
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } }
SceneDelegate.swift
import UIKit import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. let contentView = ContentView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() } } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). } func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } }
ContentView.swift
import SwiftUI struct ContentView: View { var body: some View { CameraViewController() .edgesIgnoringSafeArea(.top) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Interactor.swift
import Foundation import AVKit final class SimpleVideoCaptureInteractor: NSObject, ObservableObject { private let captureSession = AVCaptureSession() @Published var previewLayer: AVCaptureVideoPreviewLayer? private var captureDevice: AVCaptureDevice? /// - Tag: CreateCaptureSession func setupAVCaptureSession() { print(#function) captureSession.sessionPreset = .photo if let availableDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first { captureDevice = availableDevice } do { let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice!) captureSession.addInput(captureDeviceInput) } catch let error { print(error.localizedDescription) } let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.name = "CameraPreview" previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill previewLayer.backgroundColor = UIColor.black.cgColor self.previewLayer = previewLayer let dataOutput = AVCaptureVideoDataOutput() dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String:kCVPixelFormatType_32BGRA] if captureSession.canAddOutput(dataOutput) { captureSession.addOutput(dataOutput) } captureSession.commitConfiguration() } func startSettion() { if captureSession.isRunning { return } captureSession.startRunning() } func stopSettion() { if !captureSession.isRunning { return } captureSession.stopRunning() } }
CameraController.swift
import UIKit import AVFoundation class CameraController: NSObject{ var captureSession: AVCaptureSession? var backCamera: AVCaptureDevice? var backCameraInput: AVCaptureDeviceInput? var previewLayer: AVCaptureVideoPreviewLayer? enum CameraControllerError: Swift.Error { case captureSessionAlreadyRunning case captureSessionIsMissing case inputsAreInvalid case invalidOperation case noCamerasAvailable case unknown } func prepare(completionHandler: @escaping (Error?) -> Void){ func createCaptureSession(){ self.captureSession = AVCaptureSession() } func configureCaptureDevices() throws { let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) self.backCamera = camera try camera?.lockForConfiguration() camera?.unlockForConfiguration() } func configureDeviceInputs() throws { guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing } if let backCamera = self.backCamera { self.backCameraInput = try AVCaptureDeviceInput(device: backCamera) if captureSession.canAddInput(self.backCameraInput!) { captureSession.addInput(self.backCameraInput!)} else { throw CameraControllerError.inputsAreInvalid } } else { throw CameraControllerError.noCamerasAvailable } captureSession.startRunning() } DispatchQueue(label: "prepare").async { do { createCaptureSession() try configureCaptureDevices() try configureDeviceInputs() } catch { DispatchQueue.main.async{ completionHandler(error) } return } DispatchQueue.main.async { completionHandler(nil) } } } func displayPreview(on view: UIView) throws { guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing } self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill self.previewLayer?.connection?.videoOrientation = .portrait view.layer.insertSublayer(self.previewLayer!, at: 0) self.previewLayer?.frame = view.frame } }
CameraViewController.swift
import UIKit import SwiftUI final class CameraViewController: UIViewController { let cameraController = CameraController() var previewView: UIView! override func viewDidLoad() { previewView = UIView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)) previewView.contentMode = UIView.ContentMode.scaleAspectFit view.addSubview(previewView) cameraController.prepare {(error) in if let error = error { print(error) } try? self.cameraController.displayPreview(on: self.previewView) } } } extension CameraViewController : UIViewControllerRepresentable{ public typealias UIViewControllerType = CameraViewController public func makeUIViewController(context: UIViewControllerRepresentableContext<CameraViewController>) -> CameraViewController { return CameraViewController() } public func updateUIViewController(_ uiViewController: CameraViewController, context: UIViewControllerRepresentableContext<CameraViewController>) { } }
参考サイト
・SwiftUI-Simple-camera-app・今までStoryboardしか使っていないプロジェクトで、SwiftUIをとりあえず表示させるまで。