WWDC23 SALE: Save 50% on all my Swift books and bundles! >>

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!

Save 50% in my WWDC23 sale.

SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.