From 1556dfd167c4d0500aa5b84f449c629071e4bee0 Mon Sep 17 00:00:00 2001 From: empty Date: Sat, 7 Feb 2026 21:07:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A3=81=E5=89=AA?= =?UTF-8?q?=E6=89=8B=E5=8A=BF=E7=8A=B6=E6=80=81=E4=B8=A2=E5=A4=B1=20+=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E9=87=8D=E5=A4=8D=E9=A2=84=E8=A7=88=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 MagnificationGesture/DragGesture 添加 .onEnded 处理器, 新增 lastCropOffset/lastCropScale 累积变量,修复抬手后 缩放/偏移从初始值重新开始的问题 - 提取 cropPreview(height:dynamicHeight:) 共享方法和 cropGesture 计算属性,消除 iPhone/iPad 约 60 行重复代码 - resetCropState() 同步重置累积变量 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../to-live-photo/Views/EditorView.swift | 90 +++++++------------ 1 file changed, 32 insertions(+), 58 deletions(-) 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 {