📽️

Adding full screen video in SwiftUI

Created
December 23, 2021 01:04 AM
Tags
SwiftUI
Right now in SwiftUI it seems like you have two options if you want a full screen video player.
Both (of course) have trade-offs:
1.
Put a video player inside .fullScreenCover
Upside: get native iOS controls including dismiss button without any extra work
Downside: no control over animating the view transition for when the video appears
2.
Roll your own UI for (at least) a dismiss button
Upside: get full control of the animation in/out, and use styling for controls that is “on-brand”
Downside: time, effort.
You can see in the images below the exact same implementation when presented inside .fullScreenCover gets you a dismiss button. I am yet to discover a way to get the dismiss button without .fullScreenCover.
VideoPlayer presented over other content in a ZStack that shows after tapping a button. Since there is nothing to dismiss we don’t get a dismiss button. I would like to be able to associate the dismiss button with the variable responsible for showing the video.
VideoPlayer presented over other content in a ZStack that shows after tapping a button. Since there is nothing to dismiss we don’t get a dismiss button. I would like to be able to associate the dismiss button with the variable responsible for showing the video.
When presented inside .fullScreenCover we get the helpful dismiss button, because of course it is something that can be dismissed. For now this is my preferred implementation.
When presented inside .fullScreenCover we get the helpful dismiss button, because of course it is something that can be dismissed. For now this is my preferred implementation.

Option 1: Using .fullScreenCover with AVPlayer()

If you want to quickly get a full screen player up and running this is the easiest thing to implement.
In my case I need to test with dummy data using a video from within my Xcode project rather than an URL from the web. I got stuck because you can’t add video directly to the Assets file like you can with images. You need to drag the video into the file structure of the project and then drop it into the Assets file! Check out this guide for a good video of the process. You can see in the code below how to reference this file.
IMPORTANT: I quickly discovered that the player has to touch the edges of the view with .edgesIgnoreSafeArea(.all) to have native controls appear and actually be full screen, even while being presented within .fullScreenCover.
This makes sense as .fullScreenCover is merely a modifier of the view you are covering (eg: “modify the current view by covering the full screen with something”) rather than making anything it contains fill the screen. It does what it says on the tin!
You can use .onAppear() {player.play()} on the video player to auto-play the video on open.
import SwiftUI
import AVKit

struct FullScreenVideo: View{
    @State var player = AVPlayer()
// NB. I hade a placeholder video in the root of the project called dummyVideo.mov
    var videoURL = Bundle.main.url(forResource: "dummyVideo", withExtension: ".mov")
    
    var body: some View {
				VideoPlayer(player: player)
            .onAppear() {
                player = AVPlayer(url: videoURL!)
								player.play()
            }
            .edgesIgnoringSafeArea(.all) // <-- this gives video native full screen controls!
    }
}

struct ContentView: View {
    @State private var showVideo = false
    
    var body: some View {
				Button("Open Video") {
           showVideo.toggle()
        }
     }
// NB: By using .fullScreenCover for video we get a dismiss button
// as long as the player has native controls visible
     .fullScreenCover(isPresented: $showVideo, content: {
	        FullScreenVideo()
      })
}
There is probably some improvement to be made here that I missed... HMU on Twitter if you see anything!
For now this method is how I will be implementing full screen video due to its simplicity.
Users get the player HUD they are familiar with, a dismiss and mute button, and they can double-tap to zoom the video.
My only annoyance is that fullScreenCover() doesn’t support transition animations and will only slide up from the bottom. I would love if it were to get the transition/animation modifiers available for other views.
In the context of the Metric app it would be preferable to have the full screen video appear to scale from the thumbnail that is being tapped. This way the user feels like the video is more of a tangible object. I believe this suits the tactile nature of good touch screen UI.

Option 2: Roll your own video player UI (at least a dismiss button)

I will not be going into detail because I quickly decided to kick the can down the road.
You can simply put everything in a ZStack{} and have the video player hidden until showVideo is toggled to true.
At this point you will have to add an overlay to the video with some sort of button to toggle the video player off again!
import SwiftUI
import AVKit

struct ContentView: View {
    @State var player = AVPlayer()
    @State private var showVideo = false
    var videoURL = Bundle.main.url(forResource: "dummyVideo", withExtension: ".mov")
    
    var body: some View {
        ZStack {
            Button("Open Video") {                
                showVideo.toggle()                
            }

// Not shown: a way to toggle showVideo again once the video player is 
// covering the screen and the Button above is hidden!            

            if showVideo {
                VideoPlayer(player: player)
                    .onAppear {
                        player = AVPlayer(url: videoURL!)
                        player.play()
                    }
                    .onDisappear() {
                        player.pause()
                    }
                    .edgesIgnoringSafeArea(.all)
                    .zIndex(1)
            }
        }
    }
}

Conclusion

I am a novice at any sort of coding only having dabbled intermittently since we started building Metric about nine months ago. I feel SwiftUI has made it possible for me to get the grasp of certain things more quickly than when I tried to learn other languages, particularly as it relates to UI implementation which is really as far as my real contribution to Metric extends!
As ever with SwiftUI there appears to be a super simple way to do most things as long as you are willing to accept certain trade-offs. I have yet to need to delve into UIKit to implement anything, but I think that is largely due to accepting a certain look & feel that doesn’t deviate too far from Apple’s standards.