You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何在UIImagePickerController内编辑录制的视频?

实现视频录制后自定义裁剪并保存的方案

你的现有代码依赖UIImagePickerController的默认编辑功能,但其视频裁剪交互灵活性不足。要实现「录制视频→调整长度→点击Use Video保存」的流程,需要自定义裁剪界面并结合AVFoundation处理视频裁剪,具体实现如下:

步骤1:调整视频录制逻辑,传递视频URL到主界面

修改ContentView和RecordVideoPicker,让录制完成后将视频URL传回主界面,而非直接保存到相册:

struct ContentView: View {
    @State private var isShowCamera = false
    @State private var selectedVideoURL: URL?
    
    var body: some View {
        VStack(spacing: 20) {
            Button {
                isShowCamera.toggle()
            } label: {
                Text("Record Video")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            
            // 选中视频后展示自定义裁剪界面
            if let videoURL = selectedVideoURL {
                VideoCropView(videoURL: videoURL, onSave: { croppedURL in
                    // 保存裁剪后的视频到相册
                    if let path = croppedURL?.path, UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path) {
                        UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil)
                    }
                    selectedVideoURL = nil
                }, onCancel: {
                    selectedVideoURL = nil
                })
            }
        }
        .sheet(isPresented: $isShowCamera) {
            RecordVideoPicker(sourceType: .camera) { url in
                selectedVideoURL = url
            }
        }
    }
}

struct RecordVideoPicker: UIViewControllerRepresentable {
    @Environment(\.presentationMode) private var presentationMode
    var sourceType: UIImagePickerController.SourceType = .camera
    let onVideoRecorded: (URL?) -> Void // 回调传递录制好的视频URL
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
    
    func makeUIViewController(context: Context) -> UIViewController {
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = sourceType
        mediaUI.mediaTypes = [kUTTypeMovie as String]
        mediaUI.allowsEditing = false // 关闭默认编辑,使用自定义裁剪界面
        mediaUI.delegate = context.coordinator
        return mediaUI
    }
    
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: RecordVideoPicker
        
        init(_ parent: RecordVideoPicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            parent.presentationMode.wrappedValue.dismiss()
            guard let url = info[.mediaURL] as? URL else {
                parent.onVideoRecorded(nil)
                return
            }
            parent.onVideoRecorded(url)
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.presentationMode.wrappedValue.dismiss()
            parent.onVideoRecorded(nil)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

步骤2:创建自定义视频裁剪界面

实现带滑动条的裁剪View,用AVPlayer播放视频,通过滑块控制裁剪的起始和结束时间:

import AVFoundation
import AVKit

struct VideoCropView: View {
    let videoURL: URL
    let onSave: (URL?) -> Void
    let onCancel: () -> Void
    
    @State private var player: AVPlayer?
    @State private var startTime: Double = 0.0
    @State private var endTime: Double = 0.0
    @State private var duration: Double = 0.0
    
    var body: some View {
        VStack(spacing: 16) {
            // 视频播放器
            VideoPlayer(player: player)
                .frame(height: 300)
                .cornerRadius(8)
            
            // 时间控制滑块组
            HStack {
                Text(String(format: "%.1f", startTime))
                Slider(value: $startTime, in: 0...endTime, step: 0.1)
                Text(String(format: "%.1f", endTime))
                Slider(value: $endTime, in: startTime...duration, step: 0.1)
            }
            .padding(.horizontal)
            
            // 操作按钮
            HStack(spacing: 20) {
                Button("Cancel") {
                    onCancel()
                }
                .padding()
                .background(Color.gray)
                .foregroundColor(.white)
                .cornerRadius(8)
                
                Button("Use Video") {
                    cropVideo()
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
                .disabled(startTime >= endTime) // 起始时间大于结束时间时禁用按钮
            }
        }
        .onAppear {
            setupPlayer()
        }
        .onDisappear {
            player?.pause()
        }
    }
    
    private func setupPlayer() {
        let player = AVPlayer(url: videoURL)
        self.player = player
        
        // 获取视频总时长
        let asset = AVURLAsset(url: videoURL)
        duration = CMTimeGetSeconds(asset.duration)
        endTime = duration
    }
    
    private func cropVideo() {
        let asset = AVURLAsset(url: videoURL)
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
            onSave(nil)
            return
        }
        
        // 设置裁剪时间范围
        let startCMTime = CMTimeMakeWithSeconds(startTime, preferredTimescale: 600)
        let endCMTime = CMTimeMakeWithSeconds(endTime, preferredTimescale: 600)
        exportSession.timeRange = CMTimeRange(start: startCMTime, end: endCMTime)
        
        // 设置输出路径(临时目录)
        let tempDir = NSTemporaryDirectory()
        let outputFilename = UUID().uuidString + ".mov"
        let outputURL = URL(fileURLWithPath: tempDir).appendingPathComponent(outputFilename)
        exportSession.outputURL = outputURL
        exportSession.outputFileType = .mov
        
        // 执行视频导出
        exportSession.exportAsynchronously(completionHandler: {
            DispatchQueue.main.async {
                if exportSession.status == .completed {
                    onSave(outputURL)
                } else {
                    onSave(nil)
                }
            }
        })
    }
}

核心说明

  • 关闭UIImagePickerController的默认编辑功能,改用自定义裁剪界面,完全匹配你需要的「编辑条调整长度」交互逻辑。
  • 使用AVFoundation的AVAssetExportSession处理视频裁剪,支持精确控制起始和结束时间。
  • 裁剪后的视频先保存到系统临时目录,再通过UISaveVideoAtPathToSavedPhotosAlbum保存到系统相册。
  • 「Use Video」按钮会在起始时间大于等于结束时间时自动禁用,避免无效操作。

内容的提问来源于stack exchange,提问作者Yung Sheng Zeng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:15:31