일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 27 | 28 |
29 | 30 | 31 |
- 코딩
- react-native
- 개발자
- 국비 지원
- Spring
- 상태관리
- 인스타그램
- 풀스택
- Java
- 비전공자
- 프론트 엔드
- 스프링
- react native
- 리엑트 네이티브
- expo
- 클론코딩
- 리엑트
- Redux
- react
- 비전공
- ffmpeg
- 스프링 부트
- 스타트업
- 국비지원
- 자바
- 풀스택 개발자
- 프론트엔드
- 개발
- 백엔드
- spring boot
- Today
- Total
오티스의개발일기
[SwiftUI] AVFoundation을 사용하여 나만의 카메라 프레임워크 및 라이브러리 만들기😁 [코드 제공] 본문
[SwiftUI] AVFoundation을 사용하여 나만의 카메라 프레임워크 및 라이브러리 만들기😁 [코드 제공]
안되면 될때까지.. 2024. 12. 17. 19:18
안녕하세요!
오늘부터 AVFoundation을 사용하여
나만의 카메라를 만들어보는 시간을 가져보겠습니다.!!!
기본적으로 제가 기존에만든 CameraFramework 라이브러리를 사용하여
제작할예정입니다.!
CameraFramework 의 코드도 오픈소스로 풀어놨으니 많은 관심 부탁드립니다!!!!
소스 코드 깃헙 주소는 글 가장 아래에 위치해있습니다!
궁금한점은
아래 댓글 혹은 깃험 프로필에있는 연락처로 연락주시면
설명드리겠습니다!! 언제든 연락주세요!
자 그럼 시작하겠습니다!!
산출물
일단 진행하기에 앞서
기능소개와
목차를 대략적으로 보여드리고
각기능을 어떻게 사용하는지 하나하나 알려드리겠습니다.
기능
1. 싱글카메라, 멀티카메라 사용
2. 카메라 전,후면 변경
3. 미러모드 기능
4. 노출 (빛) 정도 조절
5. 미니 스크린 둥글기 조절
6. 오디오 세션
7. 썸네일 추가 기능
8. 플레쉬 ON/OFF
9. 자동초점 (ON/OFF)기능
10. 줌 IN/OUT
목차
1. 프로젝트 생성
2. info.list에 퍼미션 추가하기
3. 프로젝트 구조잡기
4. LazyView 제작
5. 카메라 View, ViewModel 제작
1. 프로젝트 생성
프로젝트는 IOS App 으로 생성하시고 이름은 원하시는 이름으로 사용하시면 됩니다!
제작한 라이브러리의 미니멈 타겟은 14.0 입니다!
그이상의 버전으로 사용하셔도되구요
IOS 14.0 아래는 사용이 불가능합니다!
2. info.list 에 카메라, 마이크 권한 추가하기
퍼미션 추가하는 방법은 왼쪽 프로젝트 클릭 -> info 클릭후 -> 리스트에 마우스를 가져다 대면 +버튼이 나옵니다 + 버튼을 누르시고 추가하시면 됩니다.!
카메라 권한추가
Privacy - Camera Usage Description
마이크 권한추가
Privacy - Microphone Usage Description
3. 라이브러리 추가
1. 프로젝트를 클릭합니다.
2. General 을 클릭하고 아래부분에 아래 사진과같은 목록이있습니다 여기에서 + 버튼을 클릭해주세요
3. + 를 클릭하고 아래 사진처럼 Add Package Dependency 를 클릭해주세요
4. 왼쪽 상단에
https://github.com/1domybest/CameraManagerLibrary
를 검색하시고 엔터를 클릭하게되면 아래와 같은 라이브러리가 나옵니다!
5. AddPackage를 눌러 프로젝트에 라이브러리를 추가시켜주세요
6. 추가에 성공하셨다면 아래와같은 모습을 볼수있습니다!
5. 프로젝트 구조
5. 전체 코드
위에 구조순서대로 파일코드를 아래 접어두었으니
"더보기" 를 통해 확인해주세요!
ContentView - 앱이 실행될때 처음 호출될 View
"아래 더보기 클릭"
//
// ContentView.swift
// Example
//
// Created by 온석태 on 10/24/24.
//
import SwiftUI
import CameraManagerFrameWork
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
VStack {
Spacer()
NavigationLink(destination: LazyView(SingleCameraView())) {
RoundedRectangle(cornerRadius: 20)
.foregroundColor(.blue)
.frame(width: 200, height: 100)
.overlay(
Text("SingleCameraView")
.bold()
.foregroundColor(.white)
)
}
Spacer().frame(height: 30)
NavigationLink(destination: LazyView(MultiCameraView())) {
RoundedRectangle(cornerRadius: 20)
.foregroundColor(.red)
.frame(width: 200, height: 100)
.overlay(
Text("MultiCameraView")
.bold()
.foregroundColor(.white)
)
}
Spacer()
}
}
}
}
}
LazyView - 간단하게 말씀드리면 렌더링이 되기전까지 객체를 미리 생성하지않게 하기위해제작된 View입니다.
일단 왜 그냥 View를 사용하지않고 굳이 Lazy한 View를 만들었는가????????
그이유는 바로 메모리와 관련되어있는데요
기본적으로 저희같은 모바일 개발자들은 메모리관리가 철저해야합니다.
그렇게 되지않으면 세션이 정상적으로 종료되지않기때문에 다시 카메라를 켰을때 정상적으로 동작하지않을수도있구요
가장중요한건 메모리 누수에대한 문제가 발생하면 어느정도 시간이지나 메모리가 꽉차게된다면 앱크래쉬가 발생되기때문에
항상 작업을하고 메모리가 잘 빠져나갔는지 확인하는 습관이 필요합니다.!
이러한 이유때문에 LazyView를 생성했고 이게 없는 상태에서는 카메라 뷰로 스택을 쌓기전에도 객체가 생성되면 해제되지않는
문제점이 있기때문에 사용하였습니다.!!! 이것과 관련해서 UIKit과 SwiftUI를 적절히 섞어 만든 프로젝트도 있는데
이것과 관련한 글은 다음에 쓰고 참조하도록 하겠습니다!!!
"아래 더보기 클릭"
//
// LazyView.swift
// Example
//
// Created by 온석태 on 10/25/24.
//
import Foundation
import SwiftUI
// LazyView 구현
struct LazyView<Content: View>: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}
UIKitViewRepresentable - UIKit에서 만든 View를 SwiftUI에서 사용가능하도록 브릿지 역할을 해주는 파일입니다.
"아래 더보기 클릭"
//
// UIKitViewRepresentable.swift
// HypyG
//
// Created by 온석태 on 8/30/24.
//
import Foundation
import SwiftUI
import UIKit
struct UIKitViewRepresentable: UIViewRepresentable {
let view: UIView?
var width: CGFloat?
var height: CGFloat?
func makeUIView(context: Context) -> UIView {
let uiView = UIView()
return view ?? uiView
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let width = width , let height = height else {
return
}
uiView.frame.size = CGSize(width: width, height: height)
}
}
MultiCamera, SingleCamera - 이 두개의 폴더안에는 MVVM형식으로 라이브러리를 적절히 호출하고 함수들을 만든 파일들입니다!
멀티 카메라
MultiCameraView
"아래 더보기 클릭"
//
// MultiCameraView.swift
// Example
//
// Created by 온석태 on 10/25/24.
//
import Foundation
import SwiftUI
import CameraManagerFrameWork
struct MultiCameraView: View {
@ObservedObject var vm: MultiCameraViewModel = MultiCameraViewModel()
var body: some View {
ZStack {
UIKitViewRepresentable(view: vm.cameraMananger?.multiCameraView)
.frame(height: (UIScreen.main.bounds.width / 9) * 16 )
.overlay(
VStack {
Spacer().frame(height: 10)
HStack {
Spacer()
Button(action: {
self.vm.toggleTorch()
}, label: {
Text("Torch \(self.vm.isTorchOn ? "OFF" : "ON")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isTorchOn ? .black : .red)
)
})
}
Spacer()
VStack {
HStack {
Text("UV Exposure Control")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.orange)
)
Spacer()
}
Spacer().frame(height: 10)
Slider(value: $vm.brightness, in: -8...8, step: 0.1)
}.frame(width: UIScreen.main.bounds.width - 30)
Spacer().frame(height: 20)
HStack {
VStack {
Button(action: {
self.vm.toggleCamera(showThumbnail: true)
}, label: {
Text("Camera \(self.vm.isCameraOn ? "OFF With Thumbnail" : "ON")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isCameraOn ? .black : .red)
)
})
Spacer().frame(height: 20)
Button(action: {
self.vm.toggleThumbnail()
}, label: {
Text("Thumbnail \(self.vm.isShowThumnnail ? "OFF" : "Show")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isShowThumnnail ? .blue : .red)
)
})
Spacer().frame(height: 10)
}
Spacer()
VStack {
Button(action: {
self.vm.toggleCameraScreenMode()
}, label: {
Text("\(self.vm.currentScreenMode == .doubleScreen ? "SingleScreen" : "DoubleScreen")")
.foregroundColor(self.vm.currentScreenMode == .doubleScreen ? .white : .black)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.currentScreenMode == .doubleScreen ? .black : .white)
)
})
Spacer().frame(height: 10)
Button(action: {
self.vm.toggleMainCameraPosition()
}, label: {
Text("MainCamera [\(self.vm.isFrontMainCamera ? "Front" : "Back")]")
.foregroundColor(self.vm.isFrontMainCamera ? .white : .black)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isFrontMainCamera ? .black : .white)
)
})
}
Spacer().frame(width: 10)
}
}
)
.onChange(of: vm.brightness) { value in
self.vm.changeExposure()
}
}
.onDisappear {
self.vm.unrference()
}
}
}
MultiCameraViewModel
"아래 더보기 클릭"
//
// MultiCameraViewModel.swift
// Example
//
// Created by 온석태 on 10/25/24.
//
import Foundation
import CameraManagerFrameWork
import UIKit
class MultiCameraViewModel:ObservableObject {
@Published var cameraMananger: CameraManager?
@Published var isShowThumnnail: Bool = false
@Published var isCameraOn: Bool = true
@Published var brightness:Float = 0.0
@Published var isFrontMainCamera:Bool = false
@Published var isTorchOn:Bool = false
@Published var currentScreenMode: CameraScreenMode = .doubleScreen
init () {
var cameraOption = CameraOptions()
cameraOption.cameraSessionMode = .multiSession
cameraOption.cameraScreenMode = .doubleScreen
cameraOption.enAblePinchZoom = true
cameraOption.cameraRenderingMode = .normal
cameraOption.tapAutoFocusAndExposure = true
cameraOption.showTapAutoFocusAndExposureRoundedRectangle = true
cameraOption.startPostion = .back
cameraOption.onChangeMainScreenPostion = { currentPosition in
self.isFrontMainCamera = currentPosition == .front ? true : false
}
cameraOption.onChangeScreenMode = { currentScreenMode in
guard let currentScreenMode = currentScreenMode else { return }
self.currentScreenMode = currentScreenMode
}
self.cameraMananger = CameraManager(cameraOptions: cameraOption)
self.cameraMananger?.initialize()
self.cameraMananger?.setThumbnail(image: UIImage(named: "testThumbnail")!)
}
deinit {
print("MultiCameraViewModel deinit")
}
func unrference () {
self.cameraMananger?.unreference()
self.cameraMananger = nil
}
func toggleCameraScreenMode () {
if !self.isCameraOn { return }
self.currentScreenMode = currentScreenMode == .doubleScreen ? .singleScreen : .doubleScreen
self.cameraMananger?.setCameraScreenMode(cameraScreenMode: self.currentScreenMode)
}
func toggleTorch() {
self.isTorchOn = self.isTorchOn ? false : true
self.cameraMananger?.setTorch(onTorch: self.isTorchOn)
}
func toggleCamera (showThumbnail: Bool) {
if isCameraOn {
self.isCameraOn = false
self.cameraMananger?.pauseCameraSession(showThumbnail: showThumbnail)
} else {
self.isCameraOn = true
self.isShowThumnnail = false
self.cameraMananger?.startCameraSession()
}
}
func toggleThumbnail () {
if isShowThumnnail {
self.isShowThumnnail = false
self.cameraMananger?.setShowThumbnail(isShow: false)
} else {
self.isShowThumnnail = true
self.cameraMananger?.setShowThumbnail(isShow: true)
}
}
func toggleMainCameraPosition() {
if !self.isCameraOn { return }
self.isFrontMainCamera = isFrontMainCamera ? false : true
self.cameraMananger?.setMainCameraPostion(mainCameraPostion: self.isFrontMainCamera ? .front : .back)
if self.isFrontMainCamera {
self.isTorchOn = false
}
}
func changeExposure() {
if !self.isCameraOn { return }
self.cameraMananger?.changeExposureBias(to: self.brightness)
}
}
싱글 카메라
SingleCameraView
"아래 더보기 클릭"
//
// SingleCamera.swift
// Example
//
// Created by 온석태 on 10/25/24.
//
import Foundation
import SwiftUI
import CameraManagerFrameWork
struct SingleCameraView: View {
@ObservedObject var vm: SingleCameraViewModel = SingleCameraViewModel()
var body: some View {
ZStack {
UIKitViewRepresentable(view: vm.cameraMananger?.singleCameraView)
.frame(height: (UIScreen.main.bounds.width / 9) * 16 )
.overlay(
VStack {
Spacer().frame(height: 10)
HStack {
Spacer()
Button(action: {
self.vm.toggleTorch()
}, label: {
Text("Torch \(self.vm.isTorchOn ? "OFF" : "ON")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isTorchOn ? .black : .red)
)
})
}
Spacer()
VStack {
HStack {
Text("UV Exposure Control")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.orange)
)
Spacer()
}
Spacer().frame(height: 10)
Slider(value: $vm.brightness, in: -8...8, step: 0.1)
}.frame(width: UIScreen.main.bounds.width - 30)
Spacer().frame(height: 20)
HStack {
VStack {
Button(action: {
self.vm.toggleCamera(showThumbnail: true)
}, label: {
Text("Camera \(self.vm.isCameraOn ? "OFF With Thumbnail" : "ON")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isCameraOn ? .black : .red)
)
})
Spacer().frame(height: 20)
Button(action: {
self.vm.toggleThumbnail()
}, label: {
Text("Thumbnail \(self.vm.isShowThumnnail ? "OFF" : "Show")")
.foregroundColor(.white)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isShowThumnnail ? .blue : .red)
)
})
Spacer().frame(height: 10)
}
Spacer()
Button(action: {
self.vm.changePosition()
}, label: {
Text("\(self.vm.isFront ? "Back" : "Front")")
.foregroundColor(self.vm.isFront ? .white : .black)
.bold()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(self.vm.isFront ? .black : .white)
)
})
Spacer().frame(width: 10)
}
}
)
.onChange(of: vm.brightness) { value in
self.vm.changeExposure()
}
}
.onDisappear {
self.vm.unrference()
}
}
}
SingleCameraViewModel
"아래 더보기 클릭"
//
// SingleCameraViewModel.swift
// Example
//
// Created by 온석태 on 10/25/24.
//
import Foundation
import CameraManagerFrameWork
import UIKit
class SingleCameraViewModel:ObservableObject {
@Published var cameraMananger: CameraManager?
@Published var isShowThumnnail: Bool = false
@Published var isCameraOn: Bool = true
@Published var brightness:Float = 0.0
@Published var isFront:Bool = false
@Published var isTorchOn:Bool = false
init () {
var cameraOption = CameraOptions()
cameraOption.cameraSessionMode = .singleSession
cameraOption.cameraScreenMode = .singleScreen
cameraOption.enAblePinchZoom = true
cameraOption.cameraRenderingMode = .normal
cameraOption.tapAutoFocusAndExposure = true
cameraOption.showTapAutoFocusAndExposureRoundedRectangle = true
cameraOption.startPostion = .back
self.cameraMananger = CameraManager(cameraOptions: cameraOption)
self.cameraMananger?.initialize()
self.cameraMananger?.setThumbnail(image: UIImage(named: "testThumbnail")!)
}
deinit {
print("SingleCameraViewModel deinit")
}
func unrference () {
self.cameraMananger?.unreference()
self.cameraMananger = nil
}
func toggleTorch() {
if isTorchOn {
self.isTorchOn = false
} else {
if !self.isFront {
self.isTorchOn = true
}
}
self.cameraMananger?.setTorch(onTorch: self.isTorchOn)
}
func toggleCamera (showThumbnail: Bool) {
if isCameraOn {
self.isCameraOn = false
self.cameraMananger?.pauseCameraSession(showThumbnail: showThumbnail)
} else {
self.isCameraOn = true
self.isShowThumnnail = false
self.cameraMananger?.startCameraSession()
}
}
func toggleThumbnail () {
if isShowThumnnail {
self.isShowThumnnail = false
self.cameraMananger?.setShowThumbnail(isShow: false)
} else {
self.isShowThumnnail = true
self.cameraMananger?.setShowThumbnail(isShow: true)
}
}
func changeExposure() {
if !self.isCameraOn { return }
self.cameraMananger?.changeExposureBias(to: self.brightness)
}
func changePosition() {
if !self.isCameraOn { return }
self.isFront = isFront ? false : true
self.cameraMananger?.setPosition(self.isFront ? .front : .back)
if self.isFront {
self.isTorchOn = false
}
}
}
전체 코드와 예제코드는 아래 깃헙에 있에서 확인해주세요!
도큐멘트
https://1domybest.github.io/CameraManagerLibrary/documentation/cameramanagerframework/
전체코드 (즉시 실행및 테스트 가능)
https://github.com/1domybest/OTIS_CameraManager_Example
'IOS 개발 > CameraManager' 카테고리의 다른 글
AVFoundation 의 AVCaptureSession 와 AVCaptureMultiCamSession 를 사용한 커스텀 멀티카메라 라이브러리 최종 산출물 🥰 (0) | 2024.10.28 |
---|