見出し画像

SwiftUI ReplayKitで画面録画する

お久しぶりです!りーさんです。
次回の開発で画面録画する機能を作ることになったのでサンプルで作ってみました。誰かの参考になればと思います。


ReplayKitとは?

デベロッパが収録やライブ・ブロードキャスト機能をAppに追加することを可能にするフレームワークです。 また、ユーザはデバイスの前面のカメラとマイクを使用して、収録やブロードキャストに注釈を加えることもできます。

参考コード

ContentView


import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ContentViewModel()
    @State private var showCountdown = false
    
    var body: some View {
        VStack {
            Text("ReplayKit")
                .font(.largeTitle)
                .fontWeight(.bold)
                .foregroundColor(.blue)
                .padding(.top, 20)
            
            if viewModel.isRecording {
                Button(action: {
                    viewModel.onTapRecordingButton()
                }) {
                    Image(systemName: "stop.circle")
                        .resizable()
                        .frame(width: 100, height: 100)
                        .foregroundColor(.red)
                }
            } else if viewModel.showCountdown {
                VStack {
                    Text("Recording will start in:")
                        .font(.headline)
                        .foregroundColor(.primary)
                        .padding(.bottom, 20)
                    
                    Text("\(viewModel.countdownValue)")
                        .font(.system(size: 60, weight: .bold, design: .default))
                        .foregroundColor(.blue)
                        .padding()
                        .background(
                            Circle()
                                .fill(Color.white)
                                .shadow(radius: 10)
                        )
                }
            } else {
                Button(action: {
                    viewModel.onTapRecordingButton()
                }) {
                    Image(systemName: "record.circle")
                        .resizable()
                        .frame(width: 100, height: 100)
                        .foregroundColor(.blue)
                }
            }
        }
        .padding()
        .alert(isPresented: $viewModel.isSaved) {
            Alert(
                title: Text("保存完了"),
                message: Text("動画をカメラロールに保存しました!"),
                dismissButton: .default(Text("OK"))
            )
        }
    }
}

#Preview {
    ContentView()
}


ContentViewModel


import ReplayKit
import Foundation
import Photos

final class ContentViewModel: ObservableObject {
    let recorder = RPScreenRecorder.shared()
        @Published var isRecording = false
        @Published var url: URL?
        @Published var isSaved = false
        @Published var showCountdown = false
        @Published var countdownValue = 3
        
    // 録画ボタンを押した時の処理
    func onTapRecordingButton() {
        if isRecording {
            Task { @MainActor in
                do {
                    self.url = try await stopRecording()
                    print(self.url ?? "")
                    self.isRecording = false
                    
                    if self.url != nil {
                        self.saveVideoToCameraRoll()
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        } else {
            startRecordingWithCountdown()
        }
    }
   

    // 録画開始のカウントダウン + 録画開始
    func startRecordingWithCountdown() {
        showCountdown = true
        countdownValue = 3
        
        let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            guard let self = self else { return }
            self.countdownValue -= 1
            
            if self.countdownValue == 0 {
                timer.invalidate()
                self.showCountdown = false
                
                startRecording { error in
                    if let error = error {
                        print(error.localizedDescription)
                        return
                    }
                }
                Task { @MainActor in
                    self.isRecording = true
                }
            }
        }
        timer.tolerance = 0.1
    }
    
    // 録画開始処理
    private func startRecording(enableMicorphone: Bool = false, completion: @escaping (Error?) -> ()) {
        recorder.isMicrophoneEnabled = false
        recorder.startRecording(handler: completion)
    }
    

    // 録画停止処理
    private func stopRecording() async throws -> URL {
        let name = UUID().uuidString + ".mov"
        let url = FileManager.default.temporaryDirectory.appendingPathComponent(name)
        
        try await recorder.stopRecording(withOutput: url)
        return url
    }
    
    // キャンセル処理
    private func cancelRecording() {
        recorder.discardRecording {}
    }
    
    // カメラロールへ保存
    private func saveVideoToCameraRoll() {
        self.isSaved = true
        guard let videoURL = self.url else {
               print("No video URL found.")
               return
           }

           PHPhotoLibrary.shared().performChanges {
               PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
           } completionHandler: { success, error in
               if success {
                   print("Video saved to Camera Roll successfully.")
                   self.isSaved = true
               } else {
                   if let error = error {
                       print("Error saving video to Camera Roll: \(error.localizedDescription)")
                   }
               }
           }
    }
}



アプリ動画


実際に録画された動画


この記事が気に入ったらサポートをしてみませんか?