How to fetch YouTube Videos using YouTube API in Swift (Part1)
Let's fetch Dragon Ball videos from YouTube!
Almost a year ago, I made a fitness app that allows users to schedule their fitness activities. One of the features is that they can find fitness videos on Youtube and save the video for later use.
Today, I'll be sharing a way of fetching YouTube API with SwiftUI.
In this article, you can learn the following:
・Fetching Youtube videos via Youtube API with search feature (Part1)
・Paginating (Part2)
・Play Video (Part3)
1 - Go to Google Cloud Platform, create a new project and enable YouTube API Data API v3
You can name your project whatever you like. In this case, I name it "SampleProject"
Go to NavigationBar > APIs & Services > Library and choose YouTube API Data V3
Click "Create Credential" & "API Key"
You will need the API key inside the Swift code.
2 - Create a model
The YouTube API returns the response with the structure below.
struct YoutubeSearchList: Codable {
let kind: String
let etag: String
let nextPageToken: String
let regionCode: String
let items: [YouTubeSearchItem]
}
struct YouTubeSearchItem: Codable {
let id: YouTubeId
let snippet: Snippet
}
struct YouTubeId: Codable {
let kind: String
let videoId: String
}
struct Snippet: Codable {
let title: String
let description: String
let thumbnails: ThumbnailInfo
}
struct ThumbnailInfo: Codable {
let `default`: ThumbDefaultInfo?
let high: ThumbHighInfo?
}
struct ThumbDefaultInfo: Codable {
let url: String
let width: Int
let height: Int
}
struct ThumbHighInfo: Codable {
let url: String
let width: Int
let height: Int
}
3 - Create a View Model for Youtube Video
class YoutubeVideoViewModel: ObservableObject {
let youtubeApiService = YoutubeApiService()
@Published var youtubeSearchList: YoutubeSearchList?
@Published var youtubeSearchItems = [YouTubeSearchItem]()
func fetchVideoWithSeach(searchText: String) {
youtubeApiService.fetchYouTubeVideos(searchText: searchText) { [weak self] youtubeSearchList in
self?.youtubeSearchItems = youtubeSearchList.items
}
}
}
struct YoutubeApiService {
public func fetchYouTubeVideos(searchText: String, completion: @escaping (YoutubeSearchList) -> Void) {
// As default, api returns only 5 results so I added a "maxResults" parameter to 15 to fetch more results
let apiKey = "Your API Key"
let urlString = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=\(searchText)®ionCode=US&maxResults=15&type=video&key=\(apiKey)"
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { jsonData, response, err in
if let err = err {
print("failed to get json data", err.localizedDescription)
return
}
guard let jsonData = jsonData else {return}
do {
let youtubeSeachList = try JSONDecoder().decode(YoutubeSearchList.self, from: jsonData)
DispatchQueue.main.async {
completion(youtubeSeachList)
}
} catch let jsonError {
print("json serialization error", jsonError)
}
}
task.resume()
}
}
4 - Create a custom image for loading URL.
Since Youtube API returns the image URL, let's create an observable object that can be converted to an image object.
class ImageDownloader : ObservableObject {
@Published var downloadData: Data? = nil
func downloadImage(url: String) {
guard let imageURL = URL(string: url) else { return }
DispatchQueue.global().async {
let data = try? Data(contentsOf: imageURL)
DispatchQueue.main.async {
self.downloadData = data
}
}
}
}
struct URLImage: View {
let url: String
@ObservedObject private var imageDownloader = ImageDownloader()
init(url: String) {
self.url = url
self.imageDownloader.downloadImage(url: self.url)
}
var body: some View {
if let imageData = self.imageDownloader.downloadData {
let img = UIImage(data: imageData)
return VStack {
Image(uiImage: img!).resizable()
}
} else {
return VStack {
let img = UIImage()
Image(uiImage: img).resizable()
}
}
}
}
5 - Create a View with Search text
After fetching Youtube API, I displayed the title, the description, and the image of the video.
struct ContentView: View {
@ObservedObject var youtubeVideoViewModel = YoutubeVideoViewModel()
@State private var searchText = ""
var body: some View {
NavigationView {
VStack {
TextField("Search", text: $searchText) { isEditing in
print("isEditing", isEditing)
} onCommit: {
print("onCommit do something when hitting enter", searchText)
youtubeVideoViewModel.fetchVideoWithSeach(searchText: searchText)
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
List {
ForEach(0..<youtubeVideoViewModel.youtubeSearchItems.count, id: \.self) { index in
HStack {
URLImage(url: youtubeVideoViewModel.youtubeSearchItems[index].snippet.thumbnails.high?.url ?? "")
.aspectRatio(contentMode: .fit).background(Color.blue)
VStack {
Text(youtubeVideoViewModel.youtubeSearchItems[index].snippet.title)
.bold()
.padding(EdgeInsets(top: 5, leading: 5, bottom: 0, trailing: 5))
Text(youtubeVideoViewModel.youtubeSearchItems[index].snippet.description)
.padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
}
}.frame(width: .none, height: 150, alignment: .center).background(Color.green)
}
}
}
.navigationTitle("Anime")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Let's see how it looks and works!
In this example, I only showed 15 results from the API, meaning showing only 15 results even after scrolling till the end.
I will show you how to paginate more results in Part 2 of this series so stay tuned!
Reference:
・ Youtube Data API