Get 50% off during WWDC 2025!

Design+Code logo

Quick links

Suggested search

Setup

Before starting, if you want to offer this feature for offline mode, I suggest you to read the section on Network Reachability to learn how to create a NetworkReachability class to check the user's internet connection.

Create your class

Let's start by creating a class called DownloadManager. It'll conform to an ObservableObject.

// DownloadManager.swift

final class DownloadManager: ObservableObject {

}

Create a isDownloading state

This @Published state will let us know if the video file is currently downloading, so we can show a loading indicator to the user while it's being downloaded.

@Published var isDownloading = false

Download the file

Create the following downloadFile function.

// DownloadManager.swift

func downloadFile() {

}

First, inside of downloadFile, we're setting the isDownloading state to true.

// DownloadManager.swift

func downloadFile() {
    isDownloading = true
}

Then, we're creating a docsUrl variable. It's a link to the device's file manager, under the user's personal files. Then, we're creating a destinationUrl. The String we're passing to the appendingPathComponent function is the name we'll give to our file.

// DownloadManager.swift

func downloadFile() {
    isDownloading = true

    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
}

Next, we're unwrapping the destinationUrl, to make sure that it exists before continuing. Inside of the if statement, we're checking if the file is already downloaded - if it is, we won't download it again.

// DownloadManager.swift

func downloadFile() {
    isDownloading = true

    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")

    if let destinationUrl = destinationUrl {
				if FileManager().fileExists(atPath: destinationUrl.path) {
            print("File already exists")
            isDownloading = false
        } else {

				} 
		}
}

In the else statement, we're creating an HTTP request to get the video file. Make sure the link you provide is a link to the actual mp4 file. Links to HLS links can't be downloaded with this method. Once we get the data, we're writing it locally. The file will be saved on the device, inside of the application's .ipa file.

func downloadFile() {
    isDownloading = true

    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")

    if let destinationUrl = destinationUrl {
        if FileManager().fileExists(atPath: destinationUrl.path) {
            print("File already exists")
            isDownloading = false
        } else {
            let urlRequest = URLRequest(url: URL(string: "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4")!)

            let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
                if let error = error {
                    print("Request error: ", error)
                    self.isDownloading = false
                    return
                }

                guard let response = response as? HTTPURLResponse else { return }

                if response.statusCode == 200 {
                    guard let data = data else {
                        self.isDownloading = false
                        return
                    }
                    DispatchQueue.main.async {
                        do {
                            try data.write(to: destinationUrl, options: Data.WritingOptions.atomic)
                            DispatchQueue.main.async {
                                self.isDownloading = false
                            }
                        } catch let error {
                            print("Error decoding: ", error)
                            self.isDownloading = false
                        }
                    }
                }
            }
            dataTask.resume()
        }
    }
}

Delete the file

If you want to allow users to delete the file, use the following function.

func deleteFile() {
    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
    if let destinationUrl = destinationUrl {
        guard FileManager().fileExists(atPath: destinationUrl.path) else { return }
        do {
            try FileManager().removeItem(atPath: destinationUrl.path)
            self.isDownloaded = false
            print("File deleted successfully")
        } catch let error {
            print("Error while deleting video file: ", error)
        }
    }
}

Check if file is already downloaded

If you need to check if the file is already downloaded, create a @Published variable that tells you if the file is downloaded.

@Published var isDownloaded = false

Then, use the function below. It'll let you know if the file already exists in the FileManager, and save it in the isDownloaded variable.

func checkFileExists() {
    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
    if let destinationUrl = destinationUrl {
        if (FileManager().fileExists(atPath: destinationUrl.path)) {
            isDownloaded = true
        } else {
            isDownloaded = false
        }
    } else {
        isDownloaded = false
    }
}

Get the downloaded video

To get the downloaded video and get the AVPlayerItem in return, use the following function. Remember to import AVKit at the top of the file. You can read more about integrating a video player in your application with the Play Video with AVPlayer section of this handbook. When creating your AVPlayer instance, simply replace url with the playerItem you get from this function.

// DownloadManager.swift

func getVideoFileAsset() -> AVPlayerItem? {
    let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

    let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
    if let destinationUrl = destinationUrl {
        if (FileManager().fileExists(atPath: destinationUrl.path)) {
            let avAssest = AVAsset(url: destinationUrl)
            return AVPlayerItem(asset: avAssest)
        } else {
            return nil
        }
    } else {
        return nil
    }
}

Final code

Find below the final code for the DownloadManager class.

import Foundation
import AVKit

final class DownloadManager: ObservableObject {
    @Published var isDownloading = false
    @Published var isDownloaded = false

    func downloadFile() {
        print("downloadFile")
        isDownloading = true

        let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

        let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
        if let destinationUrl = destinationUrl {
            if (FileManager().fileExists(atPath: destinationUrl.path)) {
                print("File already exists")
                isDownloading = false
            } else {
                let urlRequest = URLRequest(url: URL(string: "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4")!)

                let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in

                    if let error = error {
                        print("Request error: ", error)
                        self.isDownloading = false
                        return
                    }

                    guard let response = response as? HTTPURLResponse else { return }

                    if response.statusCode == 200 {
                        guard let data = data else {
                            self.isDownloading = false
                            return
                        }
                        DispatchQueue.main.async {
                            do {
                                try data.write(to: destinationUrl, options: Data.WritingOptions.atomic)

                                DispatchQueue.main.async {
                                    self.isDownloading = false
                                    self.isDownloaded = true
                                }
                            } catch let error {
                                print("Error decoding: ", error)
                                self.isDownloading = false
                            }
                        }
                    }
                }
                dataTask.resume()
            }
        }
    }

    func deleteFile() {
        let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

        let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
        if let destinationUrl = destinationUrl {
            guard FileManager().fileExists(atPath: destinationUrl.path) else { return }
            do {
                try FileManager().removeItem(atPath: destinationUrl.path)
                print("File deleted successfully")
                isDownloaded = false
            } catch let error {
                print("Error while deleting video file: ", error)
            }
        }
    }

    func checkFileExists() {
        let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

        let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
        if let destinationUrl = destinationUrl {
            if (FileManager().fileExists(atPath: destinationUrl.path)) {
                isDownloaded = true
            } else {
                isDownloaded = false
            }
        } else {
            isDownloaded = false
        }
    }

    func getVideoFileAsset() -> AVPlayerItem? {
        let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

        let destinationUrl = docsUrl?.appendingPathComponent("myVideo.mp4")
        if let destinationUrl = destinationUrl {
            if (FileManager().fileExists(atPath: destinationUrl.path)) {
                let avAssest = AVAsset(url: destinationUrl)
                return AVPlayerItem(asset: avAssest)
            } else {
                return nil
            }
        } else {
            return nil
        }
    }
}

Learn with videos and source files. Available to Pro subscribers only.

Purchase includes access to 50+ courses, 320+ premium tutorials, 300+ hours of videos, source files and certificates.

BACK TO

Network Connection

READ NEXT

Download Files Locally Part 2

Templates and source code

Download source files

Download the videos and assets to refer and learn offline without interuption.

check

Design template

check

Source code for all sections

check

Video files, ePub and subtitles

Videos

Assets

ePub

Subtitles

1

Firebase Auth

How to install Firebase authentification to your Xcode project

8:18

2

Read from Firestore

Install Cloud Firestore in your application to fetch and read data from a collection

8:01

3

Write to Firestore

Save the data users input in your application in a Firestore collection

5:35

4

Join an Array of Strings

Turn your array into a serialized String

3:33

5

Data from JSON

Load data from a JSON file into your SwiftUI application

5:08

6

HTTP Request

Create an HTTP Get Request to fetch data from an API

6:31

7

WKWebView

Integrate an HTML page into your SwiftUI application using WKWebView and by converting Markdown into HTML

5:25

8

Code Highlighting in a WebView

Use Highlight.js to convert your code blocks into beautiful highlighted code in a WebView

5:11

9

Test for Production in the Simulator

Build your app on Release scheme to test for production

1:43

10

Debug Performance in a WebView

Enable Safari's WebInspector to debug the performance of a WebView in your application

1:57

11

Debug a Crash Log

Learn how to debug a crash log from App Store Connect in Xcode

2:22

12

Simulate a Bad Network

Test your SwiftUI application by simulating a bad network connection with Network Link Conditionner

2:11

13

Archive a Build in Xcode

Archive a build for beta testing or to release in the App Store

1:28

14

Apollo GraphQL Part I

Install Apollo GraphQL in your project to fetch data from an API

6:21

15

Apollo GraphQL Part 2

Make a network call to fetch your data and process it into your own data type

6:43

16

Apollo GraphQL Part 3

Display the data fetched with Apollo GraphQL in your View

5:08

17

Configuration Files in Xcode

Create configuration files and add variables depending on the environment - development or production

4:35

18

App Review

Request an app review from your user for the AppStore

5:43

19

ImagePicker

Create an ImagePicker to choose a photo from the library or take a photo from the camera

5:06

20

Compress a UIImage

Compress a UIImage by converting it to JPEG, reducing its size and quality

3:32

21

Firebase Storage

Upload, delete and list files in Firebase Storage

11:11

22

Search Feature

Implement a search feature to filter through your content in your SwiftUI application

9:13

23

Push Notifications Part 1

Set up Firebase Cloud Messaging as a provider server to send push notifications to your users

5:59

24

Push Notifications Part 2

Create an AppDelegate to ask permission to send push notifications using Apple Push Notifications service and Firebase Cloud Messaging

6:30

25

Push Notifications Part 3

Tie everything together and test your push notifications feature in production

6:13

26

Network Connection

Verify the network connection of your user to perform tasks depending on their network's reachability

6:49

27

Download Files Locally Part 1

Download videos and files locally so users can watch them offline

6:05

28

Download Files Locally Part 2

Learn how to use the DownloadManager class in your views for offline video viewing

6:02

29

Offline Data with Realm

Save your SwiftUI data into a Realm so users can access them offline

10:20

30

HTTP Request with Async Await

Create an HTTP get request function using async await

6:11

31

Xcode Cloud

Automate workflows with Xcode Cloud

9:23

32

SceneStorage and TabView

Use @SceneStorage with TabView for better user experience on iPad

3:52

33

Network Connection Observer

Observe the network connection state using NWPathMonitor

4:37

34

Apollo GraphQL Caching

Cache data for offline availability with Apollo GraphQL

9:42

35

Create a model from an API response

Learn how to create a SwiftUI model out of the response body of an API

5:37

36

Multiple type variables in Swift

Make your models conform to the same protocol to create multiple type variables

4:23

37

Parsing Data with SwiftyJSON

Make API calls and easily parse data with this JSON package

9:36

38

ShazamKit

Build a simple Shazam clone and perform music recognition

12:38

39

Firebase Remote Config

Deliver changes to your app on the fly remotely

9:05