Swift/SwiftUI – 조도측정기 개발기

Swift/SwiftUI를 스터디 하며 개인적으로 두번째 앱을 스토어에 업로드 했습니다.

최근에 하던 작업에서 조도를 측정해야 할 일이 있었는데 측정수치만 보여주는 보여주는 앱을 다운 받았는데 측정 상황에 따른 데이터를 저장해 두고 싶었는데 그게 안되더군요. 그래서 하나 만들어 볼까 하는 생각을 했습니다.

개인적인 요구사항은 사진과 함께 측정결과값을 보관하고 다음에 다른 환경의 데이터와 비교해서 볼 수 있었으면 하는것이었습니다.

카메라뷰 컨트롤러

https://github.com/imaginary-cloud/CameraManager

요구 사항을 만족시키려면 우선 카메라가 필요합니다. 그리고 조도를 측정 하려면 프레임을 캡쳐해서 현재 화면의 메타데이터를 얻어야 합니다. 카메라 컨트롤은 위 링크의 오픈 소스를 사용했습니다. 이 오픈 소스는 아이폰 카메라를 만드는데 꼭 필요한 기본적인 기능을 갖추고 있는 아주 훌륭한 오픈소스 입니다.

하지만 SwiftUI 기반에서 프레임을 캡쳐하기 위하여 AVCaptureVideoDataOutputSampleBufferDelegate 을 얻으려면 몇가지 추가적인 작업을 해야 합니다. SwiftUI에서 View와 ViewController를 사용하려면 UIViewControllerRepresentable 프로토콜을 구현해서 델리게이션을 수행 할 수 있습니다.

델리게이션에서는 프레임이 변화될때마다 측정값을 계산합니다.

대략의 코드는 아래와 같습니다.

//
//  PreViewController.swift
//  CamMeter
//
//  Created by Park Billy on 2021/06/08.
//
import SwiftUI
import UIKit
import CameraManager
import AVFoundation


struct PreViewController: UIViewControllerRepresentable {
    @State var cameraManager:CameraManager
    @Binding var lux: Double

    func makeUIViewController(context: Context) -> some UIViewController {
        let controller = UIViewController()
            
        cameraManager.cameraDevice = UserDefaults.standard.integer(forKey: "device") == 1 ? CameraDevice.front : CameraDevice.back
        cameraManager.addPreviewLayerToView(controller.view)
        cameraManager.cameraOutputMode = CameraOutputMode.videoOnly
        
        let output = AVCaptureVideoDataOutput()
        if ((cameraManager.captureSession?.canAddOutput(output)) != nil) {
            cameraManager.captureSession?.addOutput(output)
            let VideoQueue = DispatchQueue(label: "VideoQueue")
            output.setSampleBufferDelegate(context.coordinator, queue: VideoQueue)
        }
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    }

    class Coordinator: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
        @Binding var lux: Double

        init(lux: Binding<Double>) {
            _lux = lux
        }

        func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {            
            if let metadata = CMGetAttachment(sampleBuffer, key: kCGImagePropertyExifDictionary, attachmentModeOut: nil) {
                if let fN = metadata["FNumber"] as? Double {
                    if let fT = metadata["ExposureTime"] as? Double {
                        if let speedArr = metadata["ISOSpeedRatings"] as? NSArray {                            
                            let calValue = UserDefaults.standard.double(forKey: "calvalue")                            
                            let fS = speedArr[0] as! Double
                            lux = (fN * fN) / (fT * fS) * calValue
                        }
                    }
                }
            }
            
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(lux: $lux)
    }

}
 

조도계산

위의 코드에서 보다시피 프레임만 얻으면 조도계산은 간단합니다. 얻어낸 sampleBuffer에서 Exif 데이터를 추출하고 그중에서 FNumber, ExposureTime, ISOSpeedRatings을 이용해서 간단한 계산식을 만들면 끝입니다.

캘리브레이션

계산 결과로 나온 수치는 얼마나 정확 할까요? 확인할 방법이 없어서 아래와 같이 생긴 조도계를 구입했습니다.

그리고 조도계의 결과와 동일한 출력이 나올 수 있도록 Calibration 프리셋을 입력 할 수 있는 기능을 Settings에 추가 했습니다. 기본 프리셋은 18입니다. 아마도 더 정확한 조도계를 가지고 있다면 거기에 맞도록 프리셋을 조정 하면 됩니다. 측정결과를 보며 캘리브레이션값을 조정 할 수 있도록 메인화면에 슬라이드를 보이도록 할 수도 있습니다.

SwiftUI

개인적으로 AutoLayout이 도입되면서부터 iOS 개발이 좀 재미가 없어지지 않았나 하는 생각을 했습니다. 그리고 그 사이에 웹프론트 프로젝트를 하면서 HTML, CSS를 알게 되었죠. 아무래도 그 덕분에 SwiftUI를 더 쉽게 받아 들이고 있는게 아닌가 하는 생각을 했습니다. 최근 쓰고 있는 Flutter도 마찬가지 인것 같구요.

iOS 개발이 다시 재미있어진건 무었보다도 SwiftUI 덕분입니다.

조도측정기

두어달 전에 SwiftUI 기반으로 처음만든 연봉계산기는 올렸습니다만 조도 측정기는 실제 측정기 구입비 정도는 나와야 하지 않을까 싶어서 1달러에 스토어에 올렸습니다. 제가 여ㅓ가지 조도측정앱을 다운 받아 봤습니다만 쓸만합니다. ^^;

엊그제 버전을 1.1로 올리면서 광원거리를 입력하면 칸델라단위도 보여주도록 수정했습니다. 한글/영문/일본어 지원

당연히! 필요하신 분만 사시면 됩니다. ^^

https://apps.apple.com/app/id1572227924

박병일/20200720