In this tutorial, we will learn how to make an iOS app using Swift5 and youtube API, at the end of the article, we will have our app like the one in the below video.

By the end of the Tutorial 

First of all, we must know what is meant by "API", API is an abbreviation for Application Programming Interface. We used it here as it's working like a bridge between the App and the Server and gladly here we have our API already by Google Service all you have to do is to follow the steps below:

1- Follow this Link:

2- Choose References from the top menu "Explained in the image."

3- Select PlaylistItems from the menu in the left "Explained in the image."

4- Choose the option "list" from the previous menu

5- You will have a side menu in the right have some attributes

We have now some attributes to fill, but suddenly we have to fill only two items from the above but NOTE: let API Key unchecked as we don't have an API Key for now. Let's fill the attributes now.

The first one called "part" we will fill it with "snippet" just like a keyword to mark our container with "Explained Later"

The Second one is playlistId which we will follow the steps to get it :

1- Open this Link

2- Pick any playlist you need for example https://www.youtube.com/watch?v=4D8QwqvLKkE&list=PLcqMcXfu9uXOeIaxeey0WookmsAqMWQEK

Note that after the equal sign you have your ID which is "PLcqMcXfu9uXOeIaxeey0WookmsAqMWQEK"

So now you have both attributes needed to hit execute and don't worry it will require you to login with your account and after logging in you will get response 200 which is a success response then you have to choose Show Code

We now have our first attribute in the app which is the API link, so we will create Constants file and add our constants attributes as below : File -> Constants.SWIFT

struct Constants {

    static var API_KEY = "" // It must be very vey secure and private so don't share it with anyone
    static var API_URL = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(Constants.PLAYLIST_ID)&key=\(Constants.API_KEY)"
    static var PLAYLIST_ID = "PLcqMcXfu9uXOeIaxeey0WookmsAqMWQEK"
    }
    

As we saw in the file we have three attributes we got the PLAYLIST_ID and the API_URL and we have to get the final and most secure one which is API_KEY so let's go and see how we can get it.

1- Go to the overview button in the top of the left menu and choose Developer's Console's

2- Then you will be directed to this page "Described in image" you will choose "Credentials" Then "Create Credentials" and finally you will press "API Key" and now you have your own API Key "Remember: you can't share it with anyone"


Now we need to create our Video Model but to create it we need to know what are the attributes available for each video and we can discover it by using the same window we used before which is:

Try API 

And here we will take all of this code which is in JSON Format, to check it, we will be using  jsonlint.com for this, let's check what we get when we copy and paste the JSON in the tool;

Video Attributes in JSON

Now we got the biggest container which is called "items", in this container we have many things, but in the previous screenshots we will find our "snippet" which we declared before "Try API" screenshot and in this snippet, we find what we need which is "Video Attributes" but as we mentioned before these attributes are in JSON Format so to translate this JSON response to this list of objects or to be specific "decoding JSON to list of objects" we need to use "Decodable Protocol" to help transfer this JSON easily to list of objects as we have five objects in this JSON format which are declared in the image below:

All Objects

Each {} in the image mentioned before is pointing to an object or to be clear it points to a Container "This point will be declared in the code" so let's start by creating the video model: File -> Video.SWIFT

 struct Video: Decodable {
     var videoID = ""
     var videoTitle = ""
     var videoDescription = ""
     var videThumbnail = ""
    var videoPublishedDate = Date()
    
    

so we now have all of our attributes declared in code and JSON but there is only one attribute we didn't see before which is (Video ID) and we can find it in "resourceId" declared in the image below.

and now we have all of our attributes let's continue coding in Video Model -> Video.SWIFT

First of all we have to declare our Coding Keys and by saying that we have to be able to link our variables with JSON Format like in the below image :

so before closing our Struct we will put this code:

//MARK: - CodingKeys
    enum CodingKeys: String, CodingKey {
        
        
        case thumbnails
        case high
        case resourceId
        case snippet
        case videoPublishedDate = "publishedAt"
        case videoTitle = "title"
        case videoDescription = "description"
        case videThumbnail = "url"
        case videoID = "videoId"
    }

and the previous code is just declaring our attributes to be able to decode them and that's why we used "String & CodingKey" as for each one we will use a value of String and a Key so let's continue coding in the same file:

1- We will initialize our main Container to be able to put in it the values:

init(from decoder: Decoder) throws {
        
        let container = try decoder.container(keyedBy: CodingKeys.self)
        

2- Then we will initialize our snippet as a Container:

let snippetContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .snippet)

3- Parsing our attributes which are: video title, video description & video Published Date

self.videoTitle = try snippetContainer.decode(String.self, forKey: .videoTitle)
self.videoDescription = try snippetContainer.decode(String.self, forKey: .videoDescription)
self.videoPublishedDate = try snippetContainer.decode(Date.self, forKey: .videoPublishedDate)

4- Parsing our Video Thumbnail but as we saw before we have five objects which means five containers:

Containers

That's why we didn't give them any values but this doesn't mean that this is the only case we don't give a value for our cases we can also do it when the JSON attribute has the same value as our case for example instead of VideoTitle we can write it title in this case also we don't need to give it a value

Let's get back to the containers, we initialized snippet as a container before but now it's time to initialize thumbnails container to be able to have the image but as we saw we have Outer one which is Thumbnails in it we have some qualities like in the image below and in each quality we have a url and other attributes so we picked only one quality to work with which is high "image and Code explained it"

Thumbnails sub containers
let thumbnailsContainer = try snippetContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .thumbnails)
               
let highContainer = try thumbnailsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .high)

self.videThumbnail = try highContainer.decode(String.self, forKey: .videThumbnail)

So in the previous code:

1-  thumbnailsContainer is the outer one which its JSON in the image is: "thumbnails"

2- highContainer is sub container from thumbnailsContainer which its JSON in the image is: high

3- Finally,  we got the url from high to put it in our videoThumbnail

The final step in Video.SWIFT is to parse VideoID which is in a container called resourceId

let resourceIDContainer = try snippetContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .resourceId)
self.videoID = try resourceIDContainer.decode(String.self, forKey: .videoID)
    }
}

Note: thumbnailsContainer, resourceIDContainer are sub containers from snippet container


Create Class Model to Create the session and download videos in it ->  Model.SWIFT

we will create func called "getVideos" to get all the videos

before doing anything you have to think how  would you pass the video to be downloaded from the model to the controller? and that's why we have made a protocol called ModelDelegate and after that we will create a variable from our protocol to be able to access it in the controller which called delegate and now we have the func videosFetched () to be used afterwards in our Model Class and our controller

protocol ModelDelegate {
    
    func videosFetched (_ videos: [Video])
    
}

1- Create URL Object & creating delegate variable


class Model {
   //MARK: - Vars
    var delegate: ModelDelegate?
    
func getVideos () {

        let url = URL(string: Constants.API_URL)
   // API_URL From file Constants.SWIFT

2- Create URL Session object but first we have to make sure that url has a value that's why we used guard and for those who aren't aware what guard used for, It's used to check  on a value and if this value not equal to nil "like in our example" it will proceed in the code else it will stop here "return"

 guard url != nil else {return}
    
    let session = URLSession.shared

3- Create a Data Task from URLSession object

  • we are just checking on the data and error and if there is an error or there is no data program will return and not proceed in the rest of the code
   let dataTask = session.dataTask(with: url!) { (data, response, error) in
        
        if error != nil || data == nil {
            print(error!.localizedDescription)
            return
        }

3.1  Parsing data into Video Object

  • In the code below there is a commented func called dump(response) which used If we wanna check our response output in the console
  • Using DispatchQueue to throw the task to the main thread to avoid app crush and if you remove it and write only the line in it you will face a crush
  • We used videosFetched () func as we mentioned before to be able to get the items from the response and you forgot what are these items back to the image posted before under the name of All Objects
  • Finally dataTask.resume() used to start data task
        do {
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            let response = try decoder.decode(Response.self, from: data!)
            // dump(response) 
            // Return videos from delegate func
            if response.items != nil {
                
                DispatchQueue.main.async {
                      self.delegate?.videosFetched(response.items!)
                }
              
            }
        }
        catch {
            
        }
    }
    
    dataTask.resume()
}
                    

Now the last thing we need to do to finish everything in our Models is to create a file called Response.SWIFT to be able to get our response as now we have made the code which decode each item in our Items but we didn't do anything for the biggest and last container which is called items and we didn't mention it before but it's mentioned in the image of All Objects as items is the biggest container which contains the rest of our containers so let's go and start Coding in our file:

struct Response: Decodable {
    
    var items: [Video]?
    
    enum CodingKeys: String, CodingKey {
        case items
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.items = try container.decode([Video].self, forKey: .items)
    }
    
    
}

In the previous code:

  • we have adapt our struct from Decodable protocol to be able to translate it from JSON as we mentioned before.
  • Then we created array of items from Video type from Video.SWIFT
  • We've created our CodingKeys as usual and the only case here is the one we didn't mention in Video.SWIFT which is items.
  • Finally we will initialize items as our container.

After Finishing our Models we have to create our design to be able to connect them together so let's start checking our Main.storyboard which will have a viewController already created by default so this is the one we will work on it we have to follow the images below:

In the images declared before we have made some steps which are:

  • "Image1" Open Library by pressing the + button or Command + Shift + L
  • search for TableView
  • "Image2" Drag & Drop it into your viewController
  • Choose TableView
  • pick the menu in the bottom which called Add New Constrains
  • "Image3" Setting all attributes from each side for 0 then press Add 4 Constrains and let Constrain to margins Checked as in the image
  • "Image4" Choose TableView from the left side menu
  • Press Hide or show inspectors at the top of the right menu
  • Press show the attributes inspector
  • Finally instead of 0 make it 1 to add your 1st cell

All you need now is to add what you want to link into the cell which means we need (Tittle, Date & Image "Thumbnail") so Tittle & Date will be labels and Thumbnail will be put in imageView, let's put them with Constrains:

  1. Do the same as before by opening Library and pick imageView
  2. Drop & Down it to the cell
  3. we will stretch the cell from the bench marks "Declared in 2nd image" and note that we will need to do the same for the cell to extend its height
  4. click on the imageView from the left menu Ctrl + Click and drag it to content View then press Options + Click on the items we need which "declared in 3rd image"
  5. we will repeat step1 & step2 to add a label
  6. Choose imageView and press Add Constrains
  7. Add bottom constrain but we need to make sure that this bottom constrain is to the label not to the view
  8. Set the height of the image with the value you need, for me 200 was good.

Note: We used equal width to the view to be able to adapt to any simulator as if you checked the constrain you will find imageView width has been initialized depending on the width of the content view.

we will add new label now as we did before from the library to have our two labels for the Tittle and Date:

  1. Changing the font style & size for the label to be bold and 20
  2. Set its constraints like we did before in image View

Note: We didn't set top Constraint as we did it before "ImageView.bottom =Label.Top" as they are under each other in parallel

  • We will do the same for the Date label

After finishing all of this and changing the color to the dark mode we will have the view:

  1. View Background Color: View Flipside Background color
  2. The rest will be clear color
  3. labels text color will be white

Let's Create the code for our ViewController but first of all we have to give our cell it's identifier and create a class for it:

Then you will choose the directory to save the file in.

For me I prefer to make Cell Identifier the same as the class name but it's up to you, you can name it whatever you want

ViewController.SWIFT

Before start coding we have to set the outlet for our tableView and this by following this image:

  1. Choose Main.storyboard and Options + Click on ViewController to show the code beside the storyboard
  2. Choose TableView then press Ctrl + Click to the file
  3. Name your Outlet whatever you want

Now If we checked the previous image we will find that our ViewController adapt from : UITableViewDataSource, UITableViewDelegate that's why we have to connect our tableView Delegate and DataSource with ViewController by clicking on Videos Table View Ctrl + Click to the yellow point in the controller and check each one of dataSource and delegate like in the image below:


Let's Start Coding -> ViewController.SWIFT

class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate, ModelDelegate {
    
    @IBOutlet weak var videosTableView: UITableView!
    
    // MARK: - Vars
    var model = Model()
    var videos = [Video]()
    
    // MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    
        model.delegate = self
        model.getVideos()
    }

We have Outlet for the tableView and as we said before we were using ModelDelegate Protocol to be able to load the data and connect the model to the ViewController and that's why we adapt from it

  1. we created object from Model and array of objects from Video
  2. In ViewDidLoad() we initialize our delegate and call the function we made before
// MARK: - Model Delegate Methods
    
    func videosFetched(_ videos: [Video]) {
        self.videos = videos
        videosTableView.reloadData()
        
    }

We called the function of the delegate to set our videos and reloadData() to reload the tableView with the updated values.

// MARK: - Table view methods
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return videos.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = videosTableView.dequeueReusableCell(withIdentifier: "VideoCell", for: indexPath) as! VideoCell
        cell.generateCell(videos[indexPath.row])
        return cell
    }
    
}

we will have number of videos to return and then we will use the cell we created before but if we just took the code like that we will have and error as we initialize the cell with out setting its class then we will start coding in VideoCell.SWIFT

VideoCell Class

VideoCell.SWIFT

The same way we made tableView Outlet we will made our Outlets for (imageView, Title, Date)

class VideoCell: UITableViewCell {
    // MARK: - Outlets
    
    @IBOutlet weak var videoThumbnailImageView: UIImageView!
    @IBOutlet weak var lblVideoTitle: UILabel!
    @IBOutlet weak var lblVideoPublishedDate: UILabel!
    
    //MARK: - vars
    var video: Video?
    
    // MARK: - Cell Cycle
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    

Cell Cycle Section will be created by default when the file created but we won't use it this time, we've just created our Object from Video to be able to work on it

Before Continuing we will create CacheManager.SWIFT which will help us to save the data of the cell in the cache instead of loading it at each time:

class CacheManager {
    
    static var cacheDictionary = [String : Data]()
    
    static func setVideoCache(_ url: String, _ data: Data?) {
        // Store image data and take the url as the key
        cacheDictionary[url] = data
    }
    
    static func getVideoCache(_ url: String) -> Data? {
        // get the data for the key "url"
        return cacheDictionary[url]
    }
}

In the previous code setVideoCache() we just save the data to the url and then return it  in getVideoCache()

Back Again to VideoCell.SWIFT

 func generateCell (_ myVideo: Video) {
        self.video = myVideo
        
        guard self.video != nil else {return}
        lblVideoTitle.text = video!.videoTitle
        
        //Date
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE, MMM d, yyyy"
        self.lblVideoPublishedDate.text = dateFormatter.string(from: video!.videoPublishedDate)
        

We used guard to make sure  that video has value, then we set our title and after that we worked on the date to transfer the data type of it from Date to String

  1. Make an object From DateFormatter()
  2. Set Date Format
  3. Give it a string value

Note: You can use any date format from this website:  Date Formatter  

guard self.video!.videThumbnail != "" else {return}
        
       // Checking Cache
        if let cachedData = CacheManager.getVideoCache(self.video!.videThumbnail) {
            
            self.videoThumbnailImageView.image = UIImage(data: cachedData)
            return // we don't need to contuniue
        }
  1. We check if we have videoThumbnail using guard
  2. Check Cache to download data
  3. Set videoThumbnail from the Cache & return as we don't need to process in the rest of the code

If we don't have Cache Data we will need to download Thumbnail Data:


		//Create url
        let url = URL(string: self.video!.videThumbnail)
        
        guard url != nil else {return}
        
        //Create Session
        let session = URLSession.shared
        
        // Create DataTask
        
        let dataTask = session.dataTask(with: url!) { (data, response, error) in
            
            if error == nil || data != nil {
                
                // Save in Cache Manager
                CacheManager.setVideoCache(url!.absoluteString, data)
                
                if url!.absoluteString != self.video?.videThumbnail {
                    return
                }
  1. Create Url
  2. Check url have a data and not nil using guard
  3. Create URL Session object
  4. Create Data Task from URLSession object
  5. Check if there is no error and we have a data
  6. Save the data in CacheManager to be able to get it afterwards instead of downloading it again
  7. Check if video url equals to the download url
  8. Video cell is recycled for another video which doesn't match with the one in thumbnail
 		let image = UIImage(data: data!)
                
                DispatchQueue.main.async {
                    self.videoThumbnailImageView.image = image
                    
                }
            }
        }
        dataTask.resume()
    }
}
  1. Create image object
  2. Set the imageView
  3. Start Data Task

Now we almost finished but all we need in to navigate to a view controller to be able to watch the video when clicking on a cell so we have to create a new class called: DetailsViewController.SWIFT

Following the same steps as we did before to  drag and drop viewController from library then:

  1. Choose VideoCell and Click + Ctrl to the new View and choose show
  2. Choose the new ViewController to initialize him a class and write our new class name: DetailsViewController.SWIFT, before starting to write our code we need to modify the design and add our outlets

Design:

  1. Add vertical stack view from the library
  2. Give it constraints 15 left and right, 50 Top and 0 bottom
  3. Add two labels as we used before one for Title and one for the date
  4. Add in it WebKit View as in the picture below

Note that while using stack view, it's petered to add items in the left menu under the name of your ViewController like in the image

Finally we will add a text view to put video description in it

Now we will set the only one constraint for our web view which is aspect Ratio which give the Web View width and height and Ration will be edited like in the second image to be 1280:720

Then we will Add spaces between each one of them inside the stack view

Before starting your code or doing anything you have to make sure that you add Webkit Fram work and import its module

It appears three items at the right image but this is because I've used it before in the same app, for you will find it only one and even if you found three items it's okay take them all and don't worry

Let's Code now -> DetailsViewController.SWIFT

1- We will take our outlets for each item as we did before many times


import UIKit
import WebKit

class DetailsViewController: UIViewController {
// MARK: - Outlets
    @IBOutlet weak var lblVideoTitle: UILabel!
    
    @IBOutlet weak var lblVideoPublishedDate: UILabel!
    
    @IBOutlet weak var webView: WKWebView!
    
    @IBOutlet weak var textViewVideoDescription: UITextView!
    
    // MARK: - Vars
    var video: Video?
    
    //MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
  1. Importing WebKit as we mentioned before
  2. Connecting our outlets
  3. Create a video object to work with
  4. we won't use the ViewDidLoad() here but we will use ViewWillAppear()

Before anything we have to created an embed URL for the video to be able to use it in the WebKit so we will add a constant to our Constants.SWIFT

static var YT_EMBED_URL = "https://www.youtube.com/embed/"

Continue Coding in DetailsViewController.SWIFT

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        
        guard video != nil else {return}
        
        //Create url
        let embedURL = Constants.YT_EMBED_URL + video!.videoID
        
        // Load Video 
        let url = URL(string: embedURL)
        let request = URLRequest(url: url!)
        webView.load(request)
        
        // Set other attributes
        lblVideoTitle.text = video!.videoTitle
        textViewVideoDescription.text = video!.videoDescription
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE, MMM d, yyyy"
        lblVideoPublishedDate.text = dateFormatter.string(from: video!.videoPublishedDate)
  
    }

}
  1. Check our video  to make sure that it has a value
  2. Create the Embed URL
  3. Load Video into WebView
  4. Set the rest of attributes as we did before.

Now everything is done but the only issue we have is that how could the cell know that it will navigate to this specific video not another one?

and that's why we will add this code block into our ViewController.SWIFT after ViewDidLoad() Func and before Model Delegate Methods

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        
        guard videosTableView.indexPathForSelectedRow != nil  else {
            return
        }
        
        
        let selectedVideo = videos[videosTableView.indexPathForSelectedRow!.row]
        
        
        let detailsVC = segue.destination as! DetailsViewController
        
        
        detailsVC.video = selectedVideo
    }
  1. Make sure that there is a selected video by using guard
  2. Get a reference to the selected video
  3. Get a reference to detailed ViewController
  4. Set the video

Note: Don't forget to transfer your DetailsViewController to the dark mode as we did in ViewController

Guess what !! Finally your App now is ready to run and test...
Have a Happy Coding Day...

Source Code: Youtube Playlist

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.