Spatial Video (공간동영상) in Swift

Spatial Video는 아이폰 15프로, 15프로맥스에서 지원하는 3차원 동영상 비디오 입니다. 아이폰 15프로 또는 맥스를 가지고 있다면 Spatial Video를 만들 수 있습니다. 하지만 현재 이렇게 녹화된 동영상은 애플 비젼프로, 오큘러스에서만 플레이 할수 있습니다.

Spatial Video의 기본 원리는 양쪽눈의 시각차이를 이용한 방법 이므로 이미 사용중인 3D 안경을 이용하는 방법으로 일반 동영상 플레이어에서도 3D 동영상을 볼 수 있습니다.

Swift를 이용한 동영상 분리

아래 링크의 프로젝트에서 Swift를 이용하여 Spatial Video를 분리하여 좌측, 우측 동영상을 분리하여 UIImage로 변환하는 기능을 볼수 있습니다.

Spatial Video Separate - Swift/SwiftUI
https://github.com/bipark/SpatialVideoSeparate-Swift

앱 다운로드

아래 링크의 앱은 Spatial Video의 좌측, 우측으로 분리하여 3D 안경을 이용하여 볼 수 있는 애너글리프 3D, 카드보드 3D 동영상으로 변환하는 기능을 가진 아이폰용 앱입니다.

https://apps.apple.com/us/app/spatial-video-converter-pro/id6479389968

BodyCam – 아이폰 동영상레코더

10년 넘게 앱 스토어에서 앱을 팔고(?) 있지만 최근엔 잘 안팔립니다. 시장도 포화되었고, 유저들 사용성도 고착화 되어 새로운 앱을 깔지 않죠. 게다가 유료라면 더욱 거들떠 보지도 않습니다.

그래도 스토어에 앱을 만들어 올리는 일은 개발자로서 긴장을 늦추지 않는 개인적인 방법입니다. 그러면서 평소같으면 하지 않을 프레임워크 공부도 하고 스토어 정책변화도 살피고 합니다. 그게 돈 버는 일 할때 도움이 되거든요. 재사용할 수 있는 코드도 미리 좀 만들어 놓고, 스토어 업로드 할때 당황(?)하지 않고.. ^^

올해 iOS의 AVFoundation을 많이 쓰게 되면서 공부삼아 만들어본 블랙박스 형식의 녹화 전용앱을 스토어에 업로드 했습니다. 단위 녹화시간, 해상도, fps를 조정할 수 있고, 애플워치로 녹화/중지도 되고, 앨범에 저장하지 않고 자체적으로 저장하거나 iCloud 에 저장할 수 있습니다만 유료입니다. 2.99달러. 물론 안사셔도 됩니다. 그냥 광고입니다 ^^;

자전거에 달고 액션캠으로 써보려고 만든건데 테스트 하다보니 아이폰 15프로를 사고 싶다는 생각이 간절하더군요.

다운로드 링크

https://apps.apple.com/us/app/bodycam-for-professional/id6473234309?platform=iphone

크랙 디텍터 – Crack Detector

크랙디텍터(Crack Detector) V3가 앱스토어에 릴리즈되었습니다. 크랙디텍터는 스마트폰 카메라에서 촬영한 콘크리트 벽면의 벌어진틈(크랙)을 찾아내고 크랙의 두께와 길이를 이미지 프로세싱 방법으로 측정하는 소프트웨어 서비스 입니다.

V3 – 머신러닝

크랙디텍터를 만들기 시작한건 거의 10년이 넘었습니다만 상용화 가능한 수준의 제품이 된건 이번 버전이 처음입니다.

V3에서 가장 큰 변화는 사진에서 크랙을 찾아내는 방법을 기존의 룰베이스 이미지 프로세싱에서 세그멘테이션 머신러닝을 도입한 것입니다. 기존의 이미지 프로세싱 방법은 현장의 위치, 조명, 시간등에 따른 이미지 품질 차이로 인해서 크랙을 못찾거나 그림자를 크랙으로 인식하는 등의 문제가 있었습니다만 머신러닝 도입후 크랙디텍션 성능이 매우 향상되었습니다.

마커

앱을 다운 받고 콘크리트 벽에 크랙을 촬영하면 이미지를 분석해서 크랙을 찾아냅니다만 크랙의 크기를 Pixel 단위로 표시합니다. 마커를 활용하면 Pixel 단위를 mm 단위로 정확하게 변환 할 수 있습니다.

마커는 정밀측정이 가능한 4개의 마커를 가진 하드웨어 타입과 1개의 마커를 사용하는 스티커 타입을 쓸 수 있습니다.

앱다운로드

다운로드 - 아이폰
https://apps.apple.com/kr/app/crack-detector/id1530337221

무료다운로드 이며 안드로이드용은 준비 중입니다. 앱사용에 관한 문의는 rtlink.park@gmail.com

개발배경

OpenSource – Flutter WebRTC Camara

최근 WebRTC 관련 작업을 많이 하고 있습니다. 주로 Flutter를 사용해서 앱을 만들고 있는데 아래 플러터 패키지가 꾸준히 업그레이드 되고 있어서 도움을 많이 받고 있습니다.

플러터용 WebRTC 패키지
https://pub.dev/packages/flutter_webrtc
WebRTC를 이용한 반려동물 CCTV
http://practical.kr/?p=580

시그널링(Signaling)

WebRTC는 offer – answer – candidate 등의 데이터를 주고받는 과정을 거쳐야만 화상통신을 연결 할 수 있는데 이 과정을 시그널링이라고 하고 일반적으로 시그널 서버를 만들어서 카메라와 뷰어를 연결합니다. 규정된 방법은 없고 개발자가 원하는 방법으로 데이터를 전달 하기만 하면 됩니다. 주로 소켓을 많이 이용합니다.

저는 이런 방법을 사용해 보기도 했습니다.

MQTT 기반으로 WebRTC 연결
http://practical.kr/?p=521

이것을 위해 별도의 서버를 운영을 해야 합니다. 언젠가 이런 생각을 해 봤습니다. 로그인 하지 않고 서버 없이 연결 할 수 없을까? 원격은 좀 어렵겠지만 로컬 Wifi에서는 가능하지 않을까? 로컬 Wifi 에 연결된 모든 앱이 소켓을 열고 서로 데이터를 주고 받으면 WebRTC 서버 없이 연결할 수 있겠는데? … 그런데 일단 앱의 IP를 알아야만 되더군요.

IP 찾기( Discover)

Bonsoir 패키지
https://pub.dev/packages/bonsoir

Bonsoir는 Zeroconf(https://ko.wikipedia.org/wiki/Zeroconf) 기반의 Discover 패키지입니다. 아래의 코드처럼 이벤트를 리스닝하고 있으면 브로드개스트를 하는 모든 기기를 찾을 수 있습니다. 애플은 봉주르라는 이름으로 기기들을 연결하고 있습니다.

  Future<void> _discoverService() async {
    _discovery = BonsoirDiscovery(type: _type);
    if (_discovery != null) {
      await _discovery!.ready;

      _discovery!.eventStream!.listen((event) {
        if (event.service != null) {
          ResolvedBonsoirService service = event.service as ResolvedBonsoirService;

          if (event.type == BonsoirDiscoveryEventType.discoveryServiceResolved) {
            final index = _resolvedServices.indexWhere((ResolvedBonsoirService item) => item.ip == service.ip);
            if (index == -1 && service.ip.toString() != "null") {
              _resolvedServices.add(service);
              setState(() {});
            }
          } else if (event.type == BonsoirDiscoveryEventType.discoveryServiceLost) {
            _resolvedServices.remove(service);
            setState(() {});
          }
        }
      });
      await _discovery!.start();
    }
  }

오픈소스 WebRTC 카메라

위의 몇가지 기술을 조립해서 앱을 만들어 봤습니다. 플러터의 장점인 멀티 플랫폼 지원 기능으로 아이폰 / 안드로이드 / 맥 에서 실행 가능 하도록 만들었습니다. 아래 링크에서 소스를 다운 받을 수 있습니다.

우선 맥앱을 빌드해서 맥에 실행하고 스마트폰에 앱을 빌드해서 실행하면 위의 그림과 같이 와이파이 무선 카메라를 실행 할 수 있습니다. 연결에 제한이 없으므로 많은 카메라를 한번에 연결 할 수도 있습니다.

코드를 수정하면 두대의 스마트폰으로 한대는 서버로 다른 스마트폰을 카메라로 사용할 수도 있습니다.

소스코드 링크
https://github.com/bipark/flutter_webrtc_wifi_camera

rtlink.park@gmail.com

반즐(Banzle) – 반려동물 전용 동영상 플레이어

반즐은 집에 혼자있는 반려친구를 위한 스마트폰용 동영상 플레이어입니다.

사용하지 않는 스마트폰을 활용해서 집에 혼자 있는 반려친구에게 전용 동영상을 틀어주세요.

반려견용, 반려묘용 동영상이 따로 준비되어 있어요. 밖에서도 원격으로 동영상을 바꿔서 틀어줄 수도 있어요.

그리고 혼자 있을때 무얼 하고 있는지 스마트폰 카메라로 화상통화를 할수 있어요.

아래 링크에서 다운받아 보세요. 무료앱이예요.

사용방법은 아래와 같아요. 그런데 주의할 점은 두대의 스마트폰에 반드시 같은 아이디로 로그인해야 원격 플레이와 화상통화를 할수 있어요.

◆ 주의사항

  1. 외출할때 꼭 “반즐”앱을 실행하고 나가야 해요
  2. 충전기에 연결되어 있어야해요
  3. 반려친구가 건드려서 앱이 꺼지지 않도록 적절한 위치에 두세요

브릿지(BritGi – Smart CCTV) 릴리즈

사용하지 않는 스마트폰을 CCTV로 활용할 수 있는 앱

브릿지(BritGi) V1.0

브릿지(BritGi)는 오래되서 사용하지 않는 스마트폰을 CCTV로 활용할 수 있는 앱입니다. 집을 비울때 반려견, 반려묘가 무었을 하고 있는지 내 스마트폰으로 실시간 조회 할 수 있습니다.

다운로드

아래 링크에서 무료로 다운로드 할 수 있습니다. 인앱결재 없이 계속 무료로 사용할 수 있습니다. 아이패드, 안드로이드 패드 지원합니다.

사용방법

브릿지(BritGi)는 오래된 스마트폰을 카메라로 사용하고 현재 쓰고 있는 스마트폰을 조회용으로 사용합니다.

  1. 카메라로 사용할 오래된 스마트폰과 지금쓰고 있는 스마트폰에 앱을 다운로드 하여 설치 합니다.
  2. 두개의 스마트폰에서 동일한 아이디(구글로그인 또는 애플로그인)로 로그인 합니다. (주의) 반드시 동일한 아이디로 로그인 해야 합니다. 카메라폰과 뷰어폰은 다른 종류라도 됩니다. – 예를들면 카메라는 안드로이드 뷰어는 아이폰
  3. 카메라 폰에서 카메라버튼을 눌러 카메라를 실행하고 뷰어폰에서 새로고침을 실행하면 카메라 목록이 화면에 나타나고 목록을 누르면 원격 영상을 볼수 있습니다.
  4. 카메라에 상시전원을 연결하고 고정하여 원격에서 전면, 후면 카메라를 선택하며 360도 거의 모든 방향을 볼 수 있습니다.

WebRTC(Peer-to-Peer)

브릿지(BritGi)는 카메라와 뷰어를 직접 연결하는 방식(WebRTC – Peer to Peer)으로 서버를 거치지 않고 영상정보를 전송합니다. 그래서 서버에 영상이 저장되지 않고 실시간으로만 조회가 가능합니다.

HD / Wifi, LTE, 5G

  • Wifi 연결에서는 HD 화질을 30 frame / sec 로 연결됩니다.
  • 5G/LTE 환경에서는 SD화질로 연결됩니다.
  • 가급적 Wifi 환경에서 사용하세요.

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

SwiftUI 에서 URL Schemes 과 Deeplink

최근 SwiftUI를 공부하며 개인적으로 문제가 되고 있던것 중에 하나가 과거에 하던 방식으로 문제를 해결하려는 버릇인데 URLSchames 문제를 해결하려다 또 그런일이 있었다. 프레임워크가 변하면 생각을 바꿔야 하는데 그게 쉽지 않다.

URL Schemes

URL Schemes와 그에따른 처리방법은 아이폰앱에서 데이터를 전달할 수 있는 규격을 등록하고 등록된 규격의 데이터를 수신 했을때의 처리를 위한 일련의 처리를 말하는데 거의 초기버전의 iOS부터 지원한 방법이다.

내가 알고 있던 이 문제를 해결하는 방법은 AppDelegate와 SceneDelegate 에 아래와 같은 함수를 정의하고 URLContext의 URL을 파싱해서 데이터의 내용에 따라 분기를 하고 데이터에 맞는 ViewController를 오픈하는 단계까지 가는 것이 문제의 해결이었다.

 func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {            
            print("url: \(url.absoluteURL)")
            print("scheme: \(url.scheme)")
            print("host: \(url.host)")
        }
    }

하지만 현재 XCode(V 12.5) 에서 기본적인 프로젝트 생성에서 Life Cycle을 SwiftUI App으로 만들면 AppDelegate, SceneDelegate를 만들어 주지 않는다. 이게 존재 하지 않으니 어디에서 url을 받아들이고 분기해야 할지 알 수가 없었다.

참고로 앱을 생성할때 아래의 Life Cycle을 UIKit App Delegate를 선택하면 이전에 쓰던 AppDelegate과 SceneDelegate 파일이 자동으로 생성된다.

삽질

과거의 방법으로 문제를 해결하려는 시도는 결국 며칠간의 삽질로 끝났다. AppDelegate와 SceneDelegate를 새로 만들었고 이런 해결방법도 가능하다는 결론에 도달했지만 검색중에 보게된 코드 때문에 그 코드들을 모두 버리고 말았다.

.onOpenURL()

문제의 해결 방법이 너무 간단해서 약간 허탈하긴 했지만 아래의 코드와 같이 View의 메소드를 호출하는 방법으로 URL Schemes에 등록된 url의 접근을 확인 할 수 있다.

@main
struct MyApplication: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL { url in
          print(url)
        }
    }
  }
}

DeepLink

위의 코드에서 보는것과 같이 모든 View 컨트롤은 .onOpenURL() 메소드를 가지고 있다. 이것은 위에 보이는 메인뷰에서도 url 이벤트를 확인해서 실제로 보여주고자 하는 상세 페이지까지 이동 했을 경우 그 뷰에도 동일한 함수(.onOpenURL())를 호출하면 url값을 얻을 수도 있다.

.onOpenURL { url in
            if (url.scheme! == "where-board" && url.host! == "invite") {
                if let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true) {
                    for query in components.queryItems! {
                        print(query.name)
                        print(query.value!)                        
                    }
                }
            }            
        }

Sample 프로젝트

아래와 같이 신규 프로젝트를 생성해서 상세 페이지에서 URL Schames를 통해 전달된 데이터를 출력해 볼수 있다.

프로젝트의 Life Cycle은 SwiftUI App으로 선택한다.

URL Schemes을 생성하기 위해서는 프로젝트 – info – URL Types 를 선택하고 + 버튼을 눌러서 URL Schemes 항목에 유니크한 이름을 입력한다.

메인 프로젝트 App – Scene – View 의 구조에서 ContentView()에 아래의 코드와 같이 url을 확인 할 수 있다.

import SwiftUI

@main
struct DeepLink_TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL(perform: { url in
                    print(url)
                })
        }
    }
}

실제 콘텐츠뷰의 내부에서도 url 을 전달 받을 수 있다

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onOpenURL(perform: { url in
                print(url)
            })

    }
}

아이폰에 빌드하고 앱이 실핼된 상태에서 사파리 브라우저에서 위와 같이 주소를 입력하고 앱으로 전환되어 아래와 같이 url이 전달되는 것을 확인할 수 있었다. 두번 프린트 되었는데 첫번째는 위의 코드에서 두번째는 아래의 코드에서 전달된 데이터이다.

my-deeplink-test://share?id=1234
my-deeplink-test://share?id=1234

대략 아래와 같은 코드를 이용해서 url을 파싱할 수 있다.

        .onOpenURL { url in
            if (url.scheme! == "my-deeplink-test" && url.host! == "share") {
                if let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true) {
                    for query in components.queryItems! {
                        print(query.name)
                        print(query.value!)                        
                    }
                }
            }            
        }

20210515 박병일

iOS/Swift에서 OpenCV 사용

수년전에 iOS에서 OpenCV를 이용해서 작업을 해본적이 있었는데 새로 같은 작은 작업을 하려다보니 다 잊어버려서 OpenCV 프레임워크를 설치하는 과정을 잊어버려서 잊지 않기 위해서, 그리고 새버전의 Swift/Xcode에서 OpenCV와의 연결에 문제가 없는지 확인하기 위해 다시 한번 해보고 아래와 같이 정리 했다.

OpenCV 프레임워크 다운로드

iOS용 OpenCV 프레임워크를 다운 받는다. OpenCV가 버전업 될때마다 친절하게도 iOS용 프레임워크를 빌드해서 다운받을 수 있도록 준비가 되어 있다. 아래 링크에서 프레임워크를 다운 받을 수 있다. 현재 버전은 V4,5,2 최근 버전이 굉장히 빠르게 올라가고 있다. 머신러닝과 관련한 기능들이 많이 추가된것이 원인인듯하다.

https://opencv.org/releases/

다운 받아 압축을 해제 하면 iOS에서 바로 사용할 수 있는 프레임워크 형식의 파일과 폴더로 구성되어 있다.

Xcode 신규 프로젝트 생성

프레임워크 추가

Xcode에서 OpenCV-Test 라는 이름의 신규 프로젝트를 하나 생성 했다. 내가 쓰고 있는 Xcode의 버전은 12.5이다. 위에서 압축 해제한 OpenCV 프레임워크를 폴더를 생성한 프로젝트 폴더로 이동하고 프로젝트를 선택하고 [Build Phase] 에서 Link Binary With Libraries 의 + 버튼을 눌러서 프로젝트에 포함시킨다.

래퍼클래스 생성

File 메뉴에서 New -> File..을 선택해서 아래 그림과 같은 순서로 CVWrapper를 생성한다.

이때 주의할점은 Language 에 Swift 대신에 Objecitive-C 언어로 선택을 해주어야만한다. Next를 선택하면 Xcode는 자동으로 브릿지 Header 파일을 생성하겠냐는 메세지를 보여준다. Create Bridging Header를 선택해서 브릿지헤더파일을 생성한다.

그리고 생성된 CVWrapper.m 파일의 확장자를 CVWrapper.mm 으로 변경해준다.

Prefix Header 파일 생성

그리고 메뉴에서 File – New – File… 을 선택해서 PCH file을 위 그림과 같이 선택해서 주어진 파일명으로 생성하고 생성된 .pch 파일에 아래와 같은 코드를 삽입한다.

//
//  PrefixHeader.pch
//  OpenCV-Test
//
//  Created by Park Billy on 2021/05/07.
//

#ifndef PrefixHeader_pch
#define PrefixHeader_pch

#ifdef __cplusplus
#include <opencv2/opencv.hpp>
#endif

#endif /* PrefixHeader_pch */

생성한 Prefix 파일을 컴파일러에 등록해 주어야 하는데 아래의 그림과 같이 Build Settings에서 $(SRCROOT)/프로젝트명/PrefixHeader.pch의 형식으로 등록한다.

OpenCV 버전확인 하기

아래의 코드를 추가해서 OpenCV의 버전을 가져오는 기능을 만들어 봤다. CVWrapper.h / CVWrapper.mm

//
//  CVWrapper.h
//  OpenCV-Test
//
//  Created by Park Billy on 2021/05/07.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CVWrapper : NSObject

+ (NSString *)openCVVersion;

@end

NS_ASSUME_NONNULL_END
//
//  CVWrapper.mm
//  OpenCV-Test
//
//  Created by Park Billy on 2021/05/07.
//

#import "CVWrapper.h"
#import <opencv2/opencv.hpp>

@implementation CVWrapper

+ (NSString *)openCVVersion {
    return [NSString stringWithFormat:@"OpenCV Version %s",  CV_VERSION];
}

@end

ContentView.Swift에서 래퍼클래스의 함수를 호출하여 실제 아이폰 화면에 OpenCV 버전을 보여주는 코드를 아래와 같이 추가했다.

//
//  ContentView.swift
//  OpenCV-Test
//
//  Created by Park Billy on 2021/05/07.
//

import SwiftUI

struct ContentView: View {
    @State var version = ""
    
    var body: some View {
        Text(self.version)
            .padding()
            .onAppear(){
                self.version = CVWrapper.openCVVersion()
            }
    }
}

실행결과

실행 결과는 아래와 같다.

실제로 iOS에서 OpenCV 기능을 활용하려면 위의 CVWrapper.h 에 함수를 정의 하고 CVWrapper.mm에 기능을 구현해서 Swift 코드에서 함수를 호출하는 방식으로 사용하면 된다.

소스코드

아래 링크에서 소스 코드를 다운 받을 수 있다. 이 프로젝트를 테스트 하려면 소스에 OpenCV 프레임워크는 포함되어 있지 않으므로 다운 받아서 이글의 첫번째 부분과 같이 프레임워크를 포함시켜야 한다.

https://github.com/bipark/opencv_ios_starter

20210507 박병일

Obj-C, AutoLayout, ReactNative, Flutter, SwiftUI, 개발생산성 등등

최근에 다시 Swift/SwiftUI를 스터디하며 그동안 iOS 개발을 하며 느꼈던 UI 개발에 대한 개인적인 생각을 정리해봤습니다. 시작과 끝에 대략 10년정도의 시간차이가 있습니다.

Obj-C, InterfaceBuilder 재미있었음

Objective-C를 접한건 2009년 4월. 델파이(Pascal)에 익숙해져 있던 나에겐 NS…로 시작하는 긴 함수명을 가진 프레임워크가 의외로 익숙했고 그해에 여러개의 앱을 스토어에 업로드해서 통산 100만 다운로드는 넘기는 경험을 해보기도 했다. 무료앱이라 그다지 수익을 내지는 못했지만… ^^;

Interface Builder는 델파이의 화면디자이너와 유사했고 시간이 지나며 StoryBoard로 화면이 통합되는 과정에 사이즈 때문에 느려지는 문제들이 있었지만 최소한의 일관성은 유지 했다는 측면에서 나쁘지 않았다는 생각

Obj-C, AotoLayout 재미없었음

iOS앱 개발이 재미가 없어지기 시작한 시점은 아마도 Xcode에 AutoLayout이 적용되기 시작한 시점이었던것 같은데 기껏 화면을 디자인해 놓으면 수정사항이 발생할때마다 Layout이 깨지고 화면전체의 레이아웃을 다시 잡아야하는 불편함을 감수해야 하는게 너무 싫었음. 시간이 지나면서 언어가 Obj-C에서 Swift로 바꿔었지만 UI를 만드는 과정에 너무 번거로운 상황이 반복되면서 점점 흥미를 잃기 시작

ReactNative, CSS 흥미로움

독립개발자에서 취직을 하면서 iOS와 Android를 동시에 지원해야 하는 일이 생기면서 멀티 플랫폼에 대한 고민을 하게 되었는데 적은 리소스로 목적을 달성해야하는 스타트업 비즈니의 환경에 적합한 솔루션을 찾은것이 ReactNative. (이하 RN)

RN은 웹개발 기술을 기반으로 하고 있는데 HTML, CSS를 이용한 화면 구성이 위에 언급한 AutoLayout과 대비되면서 Xcode에도 이런 방법이 도입되어야 하지 않을까 하는 생각을 했었던 기억.

Flutter, Widget 괜찮긴한데…

멀티플랫폼은 한번 발을 들이면 쉽게 빠져 나갈수 없는데, 아이폰과 안드로이드를 한번에 빌드할 수 있다는데 누가 관심을 갖지 않겠는가? 그런면에서 Flutter는 구글의 후광을 입고 가장 빠르게 성장하고 있는 모바일 개발 플랫폼. 성능도 꽤 괜찮다.

위젯기반의 UI 레이아웃은 좀 심하다 싶게 캐스케이드 되는것을 제외하면 화면 변경이 용이하고 코드와 레이아웃간의 연결성도 나쁘지 않은편. 이것은 위에 언급한 RN도 거의 동일한 개발 편의성을 가지고 있지만 Flutter가 Android Studio 지원등에서 장점이 있고, 패키지 관리등에서 RN보다 좋은점이 많이 있음.

1년이 넘게 Flutter를 쓰면서 문제점이 나타나기 시작한 부분은 UI는 아니고 패키지 관리 부분이었는데 과도하게 외부 패키지를 사용하면 각 버전들의 의존성이 달라서 업데이트에 애를먹는 상황이 발생하게 되는데 이부분 좀 심각함

Swift, SwiftUI 완전 괜찮은데?

애플에서 발표한지 2년이 다되어가는 기능을 두고 이제와서 좋다고 말하는데 어지간히 뒷북이긴 하지만 애플이 자존심을 버리고 CSS의 장점을 잘(?) 흡수해서 하나의 코드페이지에 통합한것은 늦었지만 훌륭한 작업이었다고 칭찬할만 하다고..

iOS만 지원하는 SwiftUI와 위의 멀티플랫폼 도구를 비교하는게 오류가 있을 수 있지만 UI 구성과 코드의 자연스러운 연결에 대한 부분만 본다면 SwiftUI가 가장 우수하지 않을까 하는게 최근 앱을 하나 만들어 보고 내린 결론. 물론 개인적인 생각!

SwiftUI 테스트 프로젝트

초심으로 돌아가 돈을 벌생각이 없는 무료앱을 하나 만들어봄. 대략 2주정도 걸렸고 익숙하지 않은 방법으로 화면을 만들어야 했지만 애플의 튜토리얼 프로젝트를 따라하다보니 위에 RN, Flutter에서 UI를 만들며 했던 경험들이 매우 도움이됨

https://developer.apple.com/tutorials/swiftui

위의 튜토리얼에서 iOS화면구성에 필요한 기본적인 컨트롤의 레이아웃을 구성하는 방법에 대한 정보를 얻을 수 있었고 여기에 더해 API통신(Alamofire), GoogleAnalytics등을 Pod으로 설치해서 프로젝트 생성

내연봉계산기

앱다운로드는 아래링크

https://apps.apple.com/kr/app/id1564866857

이 앱은 개인적으로 수년에 한번씩 만드는 테스트 프로젝트인데 새로운 개발 프레임워크를 테스트 할때마다 새로 만드는 앱. 해가 바꿜때마다 만들때마다 소득세를 포함한 각종 공제액의 비율이 달라져서 매년 업데이트를 해야만 해서 새로운 공부를 시작할때 유용하게 재사용하고 있음. 물론 완전히 새로 만드는거지만

결론

어느것이 더 좋다는 결론을 내리려고 하는것은 아니고 개인적으로 최근 수년간 Xcode를 그저 빌드 도구(RN, Flutter 역시 iOS 빌드는 Xcode로 한다)로만 사용하고 있었는데 멀티플랫폼에 대한 부담을 벗어버리니 Swift/SwiftUI가 개인적으로 다시 의미가 생겼다고나 할까?

SwuftUI가 UI빌딩과 메인터낸스에서 발군의 장점이 있는것과 동시에 그동안 깨닫지 못하고 있었던 빌드타임과 런타임성능이 RN, Flutter에 비해 현저하게 빠르다는것도 매우 큰 장점이다. 물론 멀티플랫폼을 지원하지 않는다는게 단점이라면… 그건 당연한거지만

20210501 박병일