구유 미니 해커톤에서 직접 촬영한 이미지 위에 그림을 그리는 기능을 담당했다.
로직은 간단하게
사진 촬영 혹은 갤러리 이미지 가져오기 → 사진 위에 그림 그리기 → 사진 + 그림 각각 추출 및 병합 → 게시글 작성 및 업로드
로 진행될 예정이다.
import SwiftUI
import PencilKit
struct CanvasView: UIViewRepresentable {
@Binding var canvasView: PKCanvasView
func makeUIView(context: Context) -> PKCanvasView {
// 손가락 또는 애플펜슬을 통한 입력을 허용
canvasView.drawingPolicy = .anyInput
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
}
}
struct CanvasContentView: View {
@State private var canvasView = PKCanvasView()
@State private var toolPicker = PKToolPicker()
var body: some View {
VStack {
CanvasView(canvasView: $canvasView)
.onAppear {
if let window = UIApplication.shared.windows.first {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}
.background(Color.white)
}
}
}
#Preview {
CanvasContentView()
}

진짜 너무 쉽게 그림판 만들 수 있어서 놀랐음 … 애플 체고

info.plist에 카메라 접근 허용 추가하기 !!
카메라 객체 정의하기
본격적으로 카메라를 사용하기 위한 기본적은 세팅 값을 정리해둔 곳이다.
import Foundation
import AVFoundation
import UIKit
@Observable
class Camera: NSObject {
var session = AVCaptureSession()
var videoDeviceInput: AVCaptureDeviceInput!
let output = AVCapturePhotoOutput()
var selectedImage: UIImage?
// 카메라 셋업 과정을 담당하는 함수, positio
func setUpCamera() {
if let device = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video, position: .back) {
do { // 카메라가 사용 가능하면 세션에 input과 output을 연결
videoDeviceInput = try AVCaptureDeviceInput(device: device)
if session.canAddInput(videoDeviceInput) {
session.addInput(videoDeviceInput)
}
if session.canAddOutput(output) {
session.addOutput(output)
output.isHighResolutionCaptureEnabled = true
output.maxPhotoQualityPrioritization = .quality
}
session.startRunning() // 세션 시작
} catch {
print(error) // 에러 프린트
}
}
}
func requestAndCheckPermissions() {
// 카메라 권한 상태 확인
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .notDetermined:
// 권한 요청
AVCaptureDevice.requestAccess(for: .video) { [weak self] authStatus in
if authStatus {
DispatchQueue.main.async {
self?.setUpCamera()
}
}
}
case .restricted:
break
case .authorized:
// 이미 권한 받은 경우 셋업
setUpCamera()
default:
// 거절했을 경우
print("Permession declined")
}
}
func capturePhoto() {
// 사진 옵션 세팅
let photoSettings = AVCapturePhotoSettings()
self.output.capturePhoto(with: photoSettings, delegate: self)
print("[Camera]: Photo's taken")
}
func savePhoto(_ imageData: Data) {
guard let image = UIImage(data: imageData) else { return }
selectedImage = image
// 사진 저장하기
print("[Camera]: Photo's saved")
}
}
extension Camera: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
}
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
}
func photoOutput(_ output: AVCapturePhotoOutput, didCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard let imageData = photo.fileDataRepresentation() else { return }
self.savePhoto(imageData)
print("[CameraModel]: Capture routine's done")
}
}
카메라를 통해 보이는 화면을 관리하는 뷰도 만들어야 한다… 이때 UIKit를 사용해야 하기 때문에 UIViewRepresentable를 사용한다.
import Foundation
import AVFoundation
import SwiftUI
struct CameraPreviewView: UIViewRepresentable {
class VideoPreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
let session: AVCaptureSession
func makeUIView(context: Context) -> VideoPreviewView {
let view = VideoPreviewView()
view.backgroundColor = .black
view.videoPreviewLayer.videoGravity = .resizeAspectFill
view.videoPreviewLayer.cornerRadius = 0
view.videoPreviewLayer.session = session
view.videoPreviewLayer.connection?.videoOrientation = .portrait
return view
}
func updateUIView(_ uiView: VideoPreviewView, context: Context) {
}
}
import SwiftUI
import AVFoundation
struct CameraView: View {
@StateObject var uploadViewModel: UploadViewModel
var body: some View {
ZStack {
uploadViewModel.cameraPreview.ignoresSafeArea()
.onAppear {
uploadViewModel.configure()
}
VStack {
Spacer()
cameraButton
}
}
}
@ViewBuilder
var cameraButton: some View {
if uploadViewModel.isTaken {
} else {
Button {
uploadViewModel.isTaken = true
uploadViewModel.model.capturePhoto()
uploadViewModel.send(action: .goToCanvas) // 카메라와 관련 X
} label: {
Circle()
.frame(width: 60, height: 60)
.foregroundStyle(Color.basicWhite)
.overlay {
Circle()
.strokeBorder(Color.primaryOrange, lineWidth: 5)
.frame(width: 60, height: 60)
.foregroundStyle(Color.basicWhite)
}
}
.padding(.bottom, 30)
}
}
}
struct CameraView_Previews: PreviewProvider {
static let container: DIContainer = .stub
static var previews: some View {
CameraView(uploadViewModel: .init(container: container))
}
}
내가 만든 카메라 화면 ... ! 
기본적인 캔버스 연습 + 일단 카메라 사용해서 화면에 카메라프리뷰까지 띄워봤다 ... (이건 실기기로 빌드해야 보인당)
다음은 촬영한 사진에 그림 얹어서 추출하는 기능을 정리해오겟다.