홍보..👋
이 지루하기 짝이 없는 과정을 단 2줄로 압축한 패키지를 만들었습니다!!!
iOS를 처음 개발하는 사람도 사용할 수 있을 정도로 단순한 구조에 비디오 & 사진 촬영에 필수적인 기본 설정을 함께 제공합니다.
원한다면 커스터마이징도 물론 가능합니다. Swift DoCC
를 이용한 아름다운 문서와 자세한 구현 예시를 제공하는 데모 앱도 있으니 관심이 있으시다면 한 번 둘러보세요. 오신 김에 Star도 하나씩 주시면 감사하겠습니다!
이제 진짜 시작
엑스코드 켭시다
iOS개발의 시작은 엑스코드니 일단 켜고 시작하자. 그 다음 이름 정해주고, 번들 아이디 정해주고 그런 당연한 것들 해주자. 나는 짜세가 되라고 짜세캠이라고 이름붙였다.
- Xcode 버전은 13이다. iOS 15로 업데이트 했더니 빌드가 안돼서 업데이트 해버렸다.
View
이젠 뷰를 만들어보자. 대단한 거 없고 이전에 만들기로 한 기능들 따라 구색만 맞추면 되겠다.
- 플래시, 전/후면 카메라 전환, 셔터 소리 없애기- 무음 모드
기성품같이 버튼이 예쁘게 배치되었다. 이 정도만 해도 비개발자 친구에겐 ‘너 앱 좀 만드는구나?’라는 소리를 들을 수 있다. 하지만 우리의 목표는 카메라 앱 완성이지 사기꾼이 아니므로 하나하나 뜯어 보도록 하자.
ViewModel
버튼을 누를 때마다 노란색 / 속이 빈 흰색으로 변했으면 좋겠다. 이 작업을 먼저 해보자.
@State로 떡칠을 해도 되지만 스탠포드의 폴 헤거티 교수님이 @State
로 코드를 바르는 것은 뱃-해빗이라 하였다. MVVM으로 있어보이는 코드를 짜기 위해 CameraViewModel
을 만들어주자. CameraView
Struct 밑에 만들어도 되고 파일로 따로 빼도 되고 취향껏 하시면 된다.
class CameraViewModel: ObservableObject {
@Published var isFlashOn = false
@Published var isSilentModeOn = falsefunc switchFlash() {
isFlashOn.toggle()
}
func switchSilent() {
isSilentModeOn.toggle()
}
func capturePhoto() {
print("[CameraViewModel]: Photo captured!")
}
func changeCamera() {
print("[CameraViewModel]: Camera changed!")
}
}
보다시피 각 버튼에 들어갈 기능들을 ‘임시로' 넣어두었다. 이제 CameraView
의 각 버튼들에 action
을 넣어주도록 하자.
View에 ViewModel 연결
struct CameraView: View {var body: some View {
ZStack {
viewModel.cameraPreview
VStack {
HStack {
// 셔터사운드 온오프
Button(action: {viewModel.switchFlash()}) {
Image(systemName: viewModel.isFlashOn ?
"speaker.fill" : "speaker")
.foregroundColor(viewModel.isFlashOn ? .yellow : .white)
}
.padding(.horizontal, 30)
// 플래시 온오프
Button(action: {viewModel.switchSilent()}) {
Image(systemName: viewModel.isSilentModeOn ?
"bolt.fill" : "bolt")
.foregroundColor(viewModel.isSilentModeOn ? .yellow : .white)
}
.padding(.horizontal, 30)
}
.font(.system(size:25))
.padding()
Spacer()
HStack{
// 찍은 사진 미리보기, 일단 액션 X
Button(action: {}) {
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 5)
.frame(width: 75, height: 75)
.padding()
}
Spacer()
// 사진찍기 버튼
Button(action: {viewModel.capturePhoto()}) {
Circle()
.stroke(lineWidth: 5)
.frame(width: 75, height: 75)
.padding()
}
Spacer()
// 전후면 카메라 교체
Button(action: {viewModel.changeCamera()}) {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
}
.frame(width: 75, height: 75)
.padding()
}
}
.foregroundColor(.white)
}
}
}
- 처음에
@ObservedObject
로 뷰모델을 선언해주자. 당장은@StateObject
로 해도 상관없다. - 플래시와 사운드
Button
의action
부분에는 3항연산자를 이용해서action
에 true / false값에 따라 색과 모양이 변하는 로직을 만들고, 나머지 버튼은 단순히 메세지를print
한다.
기능들은 대충 연결이 되었으니 이제 배경에 임시로 깔아놓은 검은색을 지우고 실제 카메라 프리뷰를 연결해보도록 하자.
카메라 미리보기 구현
UIView를 마주할 시간이 왔다. UIView에 구현한 네이티브 카메라 기능을 UIViewRepresentable
프로토콜을 이용해 SwiftUI 스타일의 View로 바꿔준다. 이미 만들어 놓았으니 가져다 쓰기만 하면 된다.
- view에 여러가지 옵션을 조절할 수 있는데 몇 개 들여다 보면 다음과 같다.
// 기본 백그라운드 색을 지정
view.backgroundColor = .black // 카메라 프리뷰 ratio 조절(fit, fill)
view.videoPreviewLayer.videoGravity = .resizeAspectFill// 프리뷰 모서리에 corner radius를 결정
view.videoPreviewLayer.cornerRadius = 0// 카메라 세션 지정(필수)
view.videoPreviewLayer.session = session// 비디오 기본 방향 지정. .portrait이 우리가 아는 세로모드.
view.videoPreviewLayer.connection?.videoOrientation = .portrait
AVFoundation
을 import 해주어야 한다.- 사용은 SwiftUI의 View를 쓰듯 하면 된다.
CameraPreviewView(session: viewModel.session)
.onAppear { ... }
.onDisappear { ... }
그런데! 프리뷰를 띄우려니session
이라는 낯선 변수를 넣어줘야 한다. session
, 즉 AVCaputreSession
은 카메라 입출력과 이미지 처리를 위해 제공되는 기능인데 자세한 내용은 도큐먼트를 참고하자. 일단은 카메라 사용을 위해 session을 만들어야한다는 것만 알아두자.
Model
이미지 처리를 담당하게 될 session
은 Model 단에서 구현한다. 당장은 미리보기만 띄우는 것이 목표이므로 다음과 같이 session
을 만들고 start하는 코드를 후딱 작성해보자.
setupCamera
함수는session
구성 및session
시작(start)을 돕는다.requestAndCheckPermissions
함수는 권한 상태를 확인하고 그 결과에 따라 정해진 동작을 수행한다. 권한을 요청한 적이 없으면(.notDetermined
) 요청한 후setupCamera
를 실행하고 이미 있다면(.authorized
) 바로setupCamera
를 실행한다.
모델은 이 정도만 하고 이제CameraViewModel
로 넘어가자.
Model — ViewModel 연결
- init()에서 model 생성하고,
- 세션 불러 온 다음에,
CameraPreviewView
담을 변수 생성해주고,requestAndCheckPermissions
실행하는 함수 만들어줄 거다.
완성된 코드는 다음과 같음.
configure
는 CameraView의 생성과 동시에 실행시켜 카메라를 세팅한다.
이를 다 적용한 View는 다음과 같다.
Info.plist에서 권한 설정
이제 뷰에 미리보기를 추가해주고 빌드를 해보자.
안 된다!
뜨거운 오류를 뱉어내는 Xcode. 쫄지 않고 천천히 오류를 읽어보면 이렇다.
SwiftUI_JJaseCam[5852:481228] [access] This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
권한을 요청하지 않은 채 건방지게 카메라를 사용한 죄로 오류를 얻게 되었다. 권한 확인하는 함수만 만들어주면 장땡이 아니라 Info.plist에 들어가 관련 내용을 작성해주어야 한다.
- 왼쪽 탐색창에서 info.plist를 찾아 들어가 Key 부분에
Priavacy-Camera Usage Description
을 추가한다. 참고로 자동완성이 된다. 대소문자를 지켜 써야한다는 게 팁 아닌 팁. - Value에는 쓰고자하는 기능 + 써야하는 이유를 적어준다. 혼자 쓸 거면 ‘1234’ 같이 대충 적어도 상관 없으나 앱스토어에 올릴 계획이라면 이런 식으로 자세히 써 주어야 통과가 된다.
Info.plist까지 잘 정리했으니 이제 잘 되겠지? 참고로 Mac의 Simulator에서는 카메라가 나오는지 안나오는지 확인할 수가 없어서 디바이스를 직접 연결할 수 밖에 없다. 케이블 계속 꽂아놓으면 배터리가 광탈하니까 꼭 빼놓자. 자나깨나 기생충 조심.
잘 된다!
이만 하고 다음 시리즈에서 촬영 후 저장, 미리보기 까지 해보자.