fix: 修复裁剪手势状态丢失 + 合并重复预览代码

- 为 MagnificationGesture/DragGesture 添加 .onEnded 处理器,
  新增 lastCropOffset/lastCropScale 累积变量,修复抬手后
  缩放/偏移从初始值重新开始的问题
- 提取 cropPreview(height:dynamicHeight:) 共享方法和
  cropGesture 计算属性,消除 iPhone/iPad 约 60 行重复代码
- resetCropState() 同步重置累积变量

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-07 21:07:59 +08:00
parent cfc39c75fc
commit 1556dfd167

View File

@@ -31,6 +31,8 @@ struct EditorView: View {
// //
@State private var cropOffset: CGSize = .zero // @State private var cropOffset: CGSize = .zero //
@State private var cropScale: CGFloat = 1.0 // @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 @State private var compatibilityMode: Bool = false
@@ -72,7 +74,7 @@ struct EditorView: View {
private var iPhoneLayout: some View { private var iPhoneLayout: some View {
ScrollView { ScrollView {
VStack(spacing: 20) { VStack(spacing: 20) {
cropPreviewSection cropPreview(height: 360)
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty { if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
diagnosisSection(diagnosis: diagnosis) diagnosisSection(diagnosis: diagnosis)
@@ -97,7 +99,7 @@ struct EditorView: View {
HStack(alignment: .top, spacing: 24) { HStack(alignment: .top, spacing: 24) {
// //
VStack(spacing: 16) { VStack(spacing: 16) {
iPadCropPreviewSection cropPreview(height: 500, dynamicHeight: true)
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty { if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
diagnosisSection(diagnosis: diagnosis) diagnosisSection(diagnosis: diagnosis)
@@ -124,80 +126,50 @@ struct EditorView: View {
.padding(DesignTokens.Spacing.xxl) .padding(DesignTokens.Spacing.xxl)
} }
// MARK: - iPad // MARK: -
@ViewBuilder private var cropGesture: some Gesture {
private var iPadCropPreviewSection: some View { SimultaneousGesture(
GeometryReader { geometry in MagnificationGesture()
let containerWidth = geometry.size.width .onChanged { value in
let containerHeight = min(500, geometry.size.width * 1.2) cropScale = max(1.0, min(3.0, lastCropScale * value))
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()
} }
.onEnded { value in
if selectedAspectRatio != .original { lastCropScale = max(1.0, min(3.0, lastCropScale * value))
CropOverlay( },
aspectRatio: selectedAspectRatio, DragGesture()
containerSize: CGSize(width: containerWidth, height: containerHeight) .onChanged { value in
cropOffset = CGSize(
width: lastCropOffset.width + value.translation.width,
height: lastCropOffset.height + value.translation.height
) )
} }
} .onEnded { value in
.frame(width: containerWidth, height: containerHeight) lastCropOffset = CGSize(
.clipShape(RoundedRectangle(cornerRadius: 16)) width: lastCropOffset.width + value.translation.width,
.background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16))) height: lastCropOffset.height + value.translation.height
} )
.frame(height: 500) }
)
} }
// MARK: - // MARK: -
@ViewBuilder @ViewBuilder
private var cropPreviewSection: some View { private func cropPreview(height: CGFloat, dynamicHeight: Bool = false) -> some View {
GeometryReader { geometry in GeometryReader { geometry in
let containerWidth = geometry.size.width let containerWidth = geometry.size.width
let containerHeight: CGFloat = 360 let containerHeight = dynamicHeight ? min(height, geometry.size.width * 1.2) : height
ZStack { ZStack {
//
if let player { if let player {
VideoPlayer(player: player) VideoPlayer(player: player)
.aspectRatio(videoNaturalSize, contentMode: .fit) .aspectRatio(videoNaturalSize, contentMode: .fit)
.scaleEffect(cropScale) .scaleEffect(cropScale)
.offset(cropOffset) .offset(cropOffset)
.gesture( .gesture(cropGesture)
SimultaneousGesture(
MagnificationGesture()
.onChanged { value in
cropScale = max(1.0, min(3.0, value))
},
DragGesture()
.onChanged { value in
cropOffset = value.translation
}
)
)
} else { } else {
ProgressView() ProgressView()
} }
//
if selectedAspectRatio != .original { if selectedAspectRatio != .original {
CropOverlay( CropOverlay(
aspectRatio: selectedAspectRatio, aspectRatio: selectedAspectRatio,
@@ -209,7 +181,7 @@ struct EditorView: View {
.clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(RoundedRectangle(cornerRadius: 16))
.background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16))) .background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16)))
} }
.frame(height: 360) .frame(height: height)
} }
// MARK: - // MARK: -
@@ -710,6 +682,8 @@ struct EditorView: View {
private func resetCropState() { private func resetCropState() {
cropOffset = .zero cropOffset = .zero
cropScale = 1.0 cropScale = 1.0
lastCropOffset = .zero
lastCropScale = 1.0
} }
private func calculateCropRect() -> CropRect { private func calculateCropRect() -> CropRect {