diff --git a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuFramePreviewView.swift b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuFramePreviewView.swift index b5be40ce..c97b7b8d 100644 --- a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuFramePreviewView.swift +++ b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuFramePreviewView.swift @@ -32,10 +32,10 @@ struct DuZzonKuFramePreviewView: View { case photoLibrary } - // 제스처 임시 값 - @State private var currentScale: CGFloat = 1.0 - @State private var currentRotation: Angle = .zero - @State private var currentOffset: CGSize = .zero + // 제스처 임시 값 (인덱스별로 저장) + @State private var currentScales: [Int: CGFloat] = [:] + @State private var currentRotations: [Int: Angle] = [:] + @State private var currentOffsets: [Int: CGSize] = [:] // 크기 설정 private let targetFrameHeight: CGFloat = 376 @@ -84,17 +84,17 @@ struct DuZzonKuFramePreviewView: View { // 현재 편집 중인 영역에 사진 저장 if let index = editingRectIndex { viewModel.setPhoto(image, at: index) + // 새 사진 선택 시 해당 인덱스의 변환 초기화 + viewModel.setPhotoScale(1.0, at: index) + viewModel.setPhotoRotation(.zero, at: index) + viewModel.setPhotoOffset(.zero, at: index) + currentScales[index] = 1.0 + currentRotations[index] = .zero + currentOffsets[index] = .zero } else { viewModel.selectedPhotoImage = image } - // 새 사진 선택 시 변환 초기화 - viewModel.photoScale = 1.0 - viewModel.photoRotation = .zero - viewModel.photoOffset = .zero - currentScale = 1.0 - currentRotation = .zero - currentOffset = .zero showEditButton = false editingRectIndex = nil } @@ -130,17 +130,17 @@ struct DuZzonKuFramePreviewView: View { // 현재 편집 중인 영역에 사진 저장 if let index = editingRectIndex { viewModel.setPhoto(uiImage, at: index) + // 새 사진 선택 시 해당 인덱스의 변환 초기화 + viewModel.setPhotoScale(1.0, at: index) + viewModel.setPhotoRotation(.zero, at: index) + viewModel.setPhotoOffset(.zero, at: index) + currentScales[index] = 1.0 + currentRotations[index] = .zero + currentOffsets[index] = .zero } else { viewModel.selectedPhotoImage = uiImage } - // 새 사진 선택 시 변환 초기화 - viewModel.photoScale = 1.0 - viewModel.photoRotation = .zero - viewModel.photoOffset = .zero - currentScale = 1.0 - currentRotation = .zero - currentOffset = .zero showEditButton = false editingRectIndex = nil } @@ -152,19 +152,20 @@ struct DuZzonKuFramePreviewView: View { } } - private var photoGestures: some Gesture { + private func photoGestures(for index: Int) -> some Gesture { // 확대/축소 let magnificationGesture = MagnificationGesture(minimumScaleDelta: 0.0) .onChanged { value in Task { @MainActor in - currentScale = value + currentScales[index] = value } } .onEnded { value in Task { @MainActor in - let newScale = viewModel.photoScale * value - viewModel.photoScale = min(max(newScale, 0.5), 3.0) - currentScale = 1.0 + let currentScale = viewModel.getPhotoScale(at: index) + let newScale = currentScale * value + viewModel.setPhotoScale(min(max(newScale, 0.5), 3.0), at: index) + currentScales[index] = 1.0 } } @@ -172,13 +173,14 @@ struct DuZzonKuFramePreviewView: View { let rotationGesture = RotationGesture(minimumAngleDelta: .zero) .onChanged { value in Task { @MainActor in - currentRotation = value + currentRotations[index] = value } } .onEnded { value in Task { @MainActor in - viewModel.photoRotation += value - currentRotation = .zero + let currentRotation = viewModel.getPhotoRotation(at: index) + viewModel.setPhotoRotation(currentRotation + value, at: index) + currentRotations[index] = .zero } } @@ -186,7 +188,7 @@ struct DuZzonKuFramePreviewView: View { let dragGesture = DragGesture(minimumDistance: 10) .onChanged { value in Task { @MainActor in - currentOffset = CGSize( + currentOffsets[index] = CGSize( width: value.translation.width, height: value.translation.height ) @@ -194,11 +196,15 @@ struct DuZzonKuFramePreviewView: View { } .onEnded { value in Task { @MainActor in - viewModel.photoOffset = CGSize( - width: viewModel.photoOffset.width + value.translation.width, - height: viewModel.photoOffset.height + value.translation.height + let currentOffset = viewModel.getPhotoOffset(at: index) + viewModel.setPhotoOffset( + CGSize( + width: currentOffset.width + value.translation.width, + height: currentOffset.height + value.translation.height + ), + at: index ) - currentOffset = .zero + currentOffsets[index] = .zero } } @@ -207,19 +213,26 @@ struct DuZzonKuFramePreviewView: View { .simultaneously(with: dragGesture) } - private var finalScale: CGFloat { - let calculatedScale = viewModel.photoScale * currentScale + // 특정 인덱스의 최종 변환 값 계산 + private func finalScale(for index: Int) -> CGFloat { + let baseScale = viewModel.getPhotoScale(at: index) + let currentScale = currentScales[index] ?? 1.0 + let calculatedScale = baseScale * currentScale return min(max(calculatedScale, 0.5), 3.0) } - private var finalRotation: Angle { - viewModel.photoRotation + currentRotation + private func finalRotation(for index: Int) -> Angle { + let baseRotation = viewModel.getPhotoRotation(at: index) + let currentRotation = currentRotations[index] ?? .zero + return baseRotation + currentRotation } - private var finalOffset: CGSize { - CGSize( - width: viewModel.photoOffset.width + currentOffset.width, - height: viewModel.photoOffset.height + currentOffset.height + private func finalOffset(for index: Int) -> CGSize { + let baseOffset = viewModel.getPhotoOffset(at: index) + let currentOffset = currentOffsets[index] ?? .zero + return CGSize( + width: baseOffset.width + currentOffset.width, + height: baseOffset.height + currentOffset.height ) } @@ -265,6 +278,7 @@ struct DuZzonKuFramePreviewView: View { .onDisappear { isFrameLoaded = false } + .offset(x: 2) .onAppear { print("checkerBoardRects:", frame.checkerBoardRects ?? []) } @@ -311,9 +325,9 @@ struct DuZzonKuFramePreviewView: View { .resizable() .scaledToFill() .frame(width: photoWidth, height: photoHeight) - .scaleEffect(finalScale) - .rotationEffect(finalRotation) - .offset(finalOffset) + .scaleEffect(finalScale(for: index)) + .rotationEffect(finalRotation(for: index)) + .offset(finalOffset(for: index)) .clipShape(clipShape) // 수정 버튼 표시 시 딤 처리 @@ -324,7 +338,7 @@ struct DuZzonKuFramePreviewView: View { .frame(width: photoWidth, height: photoHeight) .clipShape(clipShape) .contentShape(clipShape) - .gesture(photoGestures) + .gesture(photoGestures(for: index)) .onTapGesture { withAnimation(.easeInOut(duration: 0.2)) { if editingRectIndex == index { diff --git a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM+ImageConversion.swift b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM+ImageConversion.swift index 84d37011..259d47b1 100644 --- a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM+ImageConversion.swift +++ b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM+ImageConversion.swift @@ -58,6 +58,8 @@ extension DuZzonKuVM { let renderer = UIGraphicsImageRenderer(size: targetFrameSize) let composedImage = renderer.image { context in + // 전체 context를 x축으로 2포인트 이동 + context.cgContext.translateBy(x: 2, y: 0) // 1. 각 체커보드 영역에 체커보드 + 사진 그리기 for (index, rect) in checkerBoardRects.enumerated() { @@ -79,7 +81,11 @@ extension DuZzonKuVM { // 1-2. 해당 인덱스의 사진 그리기 if let photo = getPhoto(at: index) { context.cgContext.saveGState() - context.cgContext.addRect(photoRect) + + // cornerRadius 적용하여 클리핑 + let cornerRadius = rect.cornerRadius ?? 0 + let path = UIBezierPath(roundedRect: photoRect, cornerRadius: cornerRadius) + context.cgContext.addPath(path.cgPath) context.cgContext.clip() // 사진을 영역에 맞게 scaledToFill로 그리기 @@ -107,9 +113,11 @@ extension DuZzonKuVM { ) } - // 사진 변환 적용 (확대/축소, 회전, 이동) - // 현재는 모든 사진에 동일한 변환 적용 - // 필요시 인덱스별로 다른 변환 저장 가능 + // 해당 인덱스의 변환 적용 + let photoScale = getPhotoScale(at: index) + let photoRotation = getPhotoRotation(at: index) + let photoOffset = getPhotoOffset(at: index) + let centerX = drawRect.midX let centerY = drawRect.midY @@ -127,10 +135,10 @@ extension DuZzonKuVM { photo.draw(in: centeredRect) context.cgContext.restoreGState() } - - // 2. 프레임 이미지 - originalFrameImage.draw(in: CGRect(origin: .zero, size: targetFrameSize)) } + + // 2. 프레임 이미지 + originalFrameImage.draw(in: CGRect(origin: .zero, size: targetFrameSize)) } bodyImage = composedImage diff --git a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM.swift b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM.swift index 5e6a8b2f..90693b9b 100644 --- a/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM.swift +++ b/Keychy/Keychy/Presentation/KeyringMaker/Templates/DuZzonKu/ViewModels/DuZzonKuVM.swift @@ -35,10 +35,36 @@ class DuZzonKuVM: KeyringViewModelProtocol { var selectedPhotoImage: UIImage? = nil var photoImages: [Int: UIImage] = [:] // 인덱스별 사진 저장 - // MARK: - Photo Transform State - var photoScale: CGFloat = 1.0 - var photoRotation: Angle = .zero - var photoOffset: CGSize = .zero + // MARK: - Photo Transform State (인덱스별) + var photoScales: [Int: CGFloat] = [:] + var photoRotations: [Int: Angle] = [:] + var photoOffsets: [Int: CGSize] = [:] + + // 특정 인덱스의 변환 값 가져오기 (기본값 반환) + func getPhotoScale(at index: Int) -> CGFloat { + return photoScales[index] ?? 1.0 + } + + func getPhotoRotation(at index: Int) -> Angle { + return photoRotations[index] ?? .zero + } + + func getPhotoOffset(at index: Int) -> CGSize { + return photoOffsets[index] ?? .zero + } + + // 특정 인덱스의 변환 값 설정 + func setPhotoScale(_ scale: CGFloat, at index: Int) { + photoScales[index] = scale + } + + func setPhotoRotation(_ rotation: Angle, at index: Int) { + photoRotations[index] = rotation + } + + func setPhotoOffset(_ offset: CGSize, at index: Int) { + photoOffsets[index] = offset + } // MARK: - Body Image var bodyImage: UIImage? = nil @@ -91,6 +117,11 @@ class DuZzonKuVM: KeyringViewModelProtocol { /// 특정 인덱스의 사진 제거 func removePhoto(at index: Int) { photoImages.removeValue(forKey: index) + // 변환 값도 제거 + photoScales.removeValue(forKey: index) + photoRotations.removeValue(forKey: index) + photoOffsets.removeValue(forKey: index) + if index == 0 { selectedPhotoImage = nil } @@ -163,9 +194,9 @@ class DuZzonKuVM: KeyringViewModelProtocol { selectedFrame = nil selectedPhotoImage = nil photoImages.removeAll() - photoScale = 1.0 - photoRotation = .zero - photoOffset = .zero + photoScales.removeAll() + photoRotations.removeAll() + photoOffsets.removeAll() bodyImage = nil availableFrames.removeAll() isComposingPhoto = false