diff --git a/to-live-photo/to-live-photo/Views/EditorView.swift b/to-live-photo/to-live-photo/Views/EditorView.swift index 1d7a1bb..6722fdf 100644 --- a/to-live-photo/to-live-photo/Views/EditorView.swift +++ b/to-live-photo/to-live-photo/Views/EditorView.swift @@ -31,6 +31,8 @@ struct EditorView: View { // 裁剪相关(归一化坐标) @State private var cropOffset: CGSize = .zero // 拖拽偏移 @State private var cropScale: CGFloat = 1.0 // 缩放比例 + @State private var lastCropOffset: CGSize = .zero // 累积偏移 + @State private var lastCropScale: CGFloat = 1.0 // 累积缩放 // 兼容模式 @State private var compatibilityMode: Bool = false @@ -72,7 +74,7 @@ struct EditorView: View { private var iPhoneLayout: some View { ScrollView { VStack(spacing: 20) { - cropPreviewSection + cropPreview(height: 360) if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty { diagnosisSection(diagnosis: diagnosis) @@ -97,7 +99,7 @@ struct EditorView: View { HStack(alignment: .top, spacing: 24) { // 左侧:视频预览 VStack(spacing: 16) { - iPadCropPreviewSection + cropPreview(height: 500, dynamicHeight: true) if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty { diagnosisSection(diagnosis: diagnosis) @@ -124,80 +126,50 @@ struct EditorView: View { .padding(DesignTokens.Spacing.xxl) } - // MARK: - iPad 裁剪预览(更大尺寸) - @ViewBuilder - private var iPadCropPreviewSection: some View { - GeometryReader { geometry in - let containerWidth = geometry.size.width - let containerHeight = min(500, geometry.size.width * 1.2) - - ZStack { - if let player { - VideoPlayer(player: player) - .aspectRatio(videoNaturalSize, contentMode: .fit) - .scaleEffect(cropScale) - .offset(cropOffset) - .gesture( - SimultaneousGesture( - MagnificationGesture() - .onChanged { value in - cropScale = max(1.0, min(3.0, value)) - }, - DragGesture() - .onChanged { value in - cropOffset = value.translation - } - ) - ) - } else { - ProgressView() + // MARK: - 裁剪手势 + private var cropGesture: some Gesture { + SimultaneousGesture( + MagnificationGesture() + .onChanged { value in + cropScale = max(1.0, min(3.0, lastCropScale * value)) } - - if selectedAspectRatio != .original { - CropOverlay( - aspectRatio: selectedAspectRatio, - containerSize: CGSize(width: containerWidth, height: containerHeight) + .onEnded { value in + lastCropScale = max(1.0, min(3.0, lastCropScale * value)) + }, + DragGesture() + .onChanged { value in + cropOffset = CGSize( + width: lastCropOffset.width + value.translation.width, + height: lastCropOffset.height + value.translation.height ) } - } - .frame(width: containerWidth, height: containerHeight) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16))) - } - .frame(height: 500) + .onEnded { value in + lastCropOffset = CGSize( + width: lastCropOffset.width + value.translation.width, + height: lastCropOffset.height + value.translation.height + ) + } + ) } - // MARK: - 裁剪预览 + // MARK: - 裁剪预览(共享) @ViewBuilder - private var cropPreviewSection: some View { + private func cropPreview(height: CGFloat, dynamicHeight: Bool = false) -> some View { GeometryReader { geometry in let containerWidth = geometry.size.width - let containerHeight: CGFloat = 360 + let containerHeight = dynamicHeight ? min(height, geometry.size.width * 1.2) : height ZStack { - // 视频预览(全画幅) if let player { VideoPlayer(player: player) .aspectRatio(videoNaturalSize, contentMode: .fit) .scaleEffect(cropScale) .offset(cropOffset) - .gesture( - SimultaneousGesture( - MagnificationGesture() - .onChanged { value in - cropScale = max(1.0, min(3.0, value)) - }, - DragGesture() - .onChanged { value in - cropOffset = value.translation - } - ) - ) + .gesture(cropGesture) } else { ProgressView() } - // 裁剪框叠加层 if selectedAspectRatio != .original { CropOverlay( aspectRatio: selectedAspectRatio, @@ -209,7 +181,7 @@ struct EditorView: View { .clipShape(RoundedRectangle(cornerRadius: 16)) .background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16))) } - .frame(height: 360) + .frame(height: height) } // MARK: - 比例模板选择 @@ -710,6 +682,8 @@ struct EditorView: View { private func resetCropState() { cropOffset = .zero cropScale = 1.0 + lastCropOffset = .zero + lastCropScale = 1.0 } private func calculateCropRect() -> CropRect {