NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

How to let users import videos using PhotosPicker

Paul Hudson    @twostraws   

Updated for Xcode 14.2

New in iOS 16

SwiftUI’s PhotosPicker allows users to select videos and bring them into our app, but in my experience it needs to be used in a fairly precise way to avoid problems.

I’ll show you the code first, then explain why it takes as much work as it does:

import AVKit
import PhotosUI
import SwiftUI

struct Movie: Transferable {
    let url: URL

    static var transferRepresentation: some TransferRepresentation {
        FileRepresentation(contentType: .movie) { movie in
            SentTransferredFile(movie.url)
        } importing: { received in
            let copy = URL.documentsDirectory.appending(path: "movie.mp4")

            if FileManager.default.fileExists(atPath: copy.path()) {
                try FileManager.default.removeItem(at: copy)
            }

            try FileManager.default.copyItem(at: received.file, to: copy)
            return Self.init(url: copy)
        }
    }
}

struct ContentView: View {
    enum LoadState {
        case unknown, loading, loaded(Movie), failed
    }

    @State private var selectedItem: PhotosPickerItem?
    @State private var loadState = LoadState.unknown

    var body: some View {
        VStack {
            PhotosPicker("Select movie", selection: $selectedItem, matching: .videos)

            switch loadState {
            case .unknown:
                EmptyView()
            case .loading:
                ProgressView()
            case .loaded(let movie):
                VideoPlayer(player: AVPlayer(url: movie.url))
                    .scaledToFit()
                    .frame(width: 300, height: 300)
            case .failed:
                Text("Import failed")
            }
        }
        .onChange(of: selectedItem) { _ in
            Task {
                do {
                    loadState = .loading

                    if let movie = try await selectedItem?.loadTransferable(type: Movie.self) {
                        loadState = .loaded(movie)
                    } else {
                        loadState = .failed
                    }
                } catch {
                    loadState = .failed
                }
            }
        }
    }
}

Download this as an Xcode project

Yes, that’s a lot, so let me break it down.

First, we need to import AVKit in order to have access to the VideoPlayer view, and we need PhotosUI to have access to the PhotosPicker view.

Second, the custom Movie struct is how we tell SwiftUI to import movie data. It can send data using Transferable by converting its URL into a SentTransferredFile, which means we can drag Movie instances out of an app, for example. It can also receive data using the importing closure: it copies the movie URL to our documents directory as “movie.mp4”, removing any existing file.

Third, importing a movie can take some time, so we need to make sure the user has some idea of our import state while the app runs. This is handled through an enum with four cases: unknown when the app starts, loading to show a progress spinner, loaded when we have a finished Movie to work with, and failed when the import failed for some reason.

Finally, in the onChange() modifier we ask the system to give us a Movie instance so we accept the URL and move it into the correct location for our app to use. This also takes care of setting the loadState property so our UI stays in sync.

Hopefully Apple can find a way to simplify this API in the future, but until then I’d certainly be keen to hear suggestions to make this code simpler!

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.