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:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user