240501
벌써 5월이라니 믿을 수 없음 입니다
TMDB 에서 api 키를 발급 받습니다.
방법은 다른 블로그에 많으니까,,
프로젝트 때 영화 목록만 봤던게 지겨워서 TV 로 받아올까 싶습니다
간단하게 타이틀이랑 이미지만 받아 올게요
UI는 Snapkit 으로 그렸습니다
데이터 모델 생성
원하는 걸 선택한 뒤 REQUEST 창에서 TRY It! 을 눌러 REPONSE 창에서 데이터들을 불어옵니다
데이터 모델을 자동으로 생성해 줘요
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let page: Int
let results: [Result]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
// MARK: - Result
struct Result: Codable {
let backdropPath: String
let id: Int
let originalName, overview, posterPath: String
let mediaType: MediaType
let adult: Bool
let name, originalLanguage: String
let genreIDS: [Int]
let popularity: Double
let firstAirDate: String
let voteAverage: Double
let voteCount: Int
let originCountry: [String]
enum CodingKeys: String, CodingKey {
case backdropPath = "backdrop_path"
case id
case originalName = "original_name"
case overview
case posterPath = "poster_path"
case mediaType = "media_type"
case adult, name
case originalLanguage = "original_language"
case genreIDS = "genre_ids"
case popularity
case firstAirDate = "first_air_date"
case voteAverage = "vote_average"
case voteCount = "vote_count"
case originCountry = "origin_country"
}
}
enum MediaType: String, Codable {
case tv = "tv"
}
필요한 값만 남기고 다 삭제해도 좋습니다
네트워킹
네트워킹 하는 코드도 TMDB REQUEST에서 친절하게 알려줍니다
저는 좀 수정해서 사용했어요
전역에서 사용할 수 있는 싱글턴 인스턴스를 생성
static let shared = NetworkingManager() // Singleton 인스턴스
private init() {}
이렇게 하면 매번 생성하지 않고 접근해서 사용할 수 있게 됩니다
URL 만들기
TMDB docs를 보니 path parmas 과 query params 이 있었는데
time_window 값을 변경 할 수 있었습니다 쿼리값으로 넣으면 되지 않나? 했는데 그냥 기본 url 뒤에 변경해서 넣어야 하더군요!
기본 url에 {tiem_window} 이런식으로 적혀 있었습니다
let url = URL(string: "https://api.themoviedb.org/3/trending/tv/week")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [
URLQueryItem(name: "language", value: "ko-KR"),
]
components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems
그래서 week 로 넣어서 생성해 만들어줬어요
언어는 한국어로 받아올게요! 첨에 kr-KR 이라고 써서 오류났는데 ㅋㅋㅋ ko-KR 로 적어야 함미다,,
URL 요청하기
var request = URLRequest(url: components.url!)
request.httpMethod = "GET" // 기본이 GET
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjNzM3YjYzYTEzMDRmZWE3NTljM2NiZjEzZjgzOWRiMSIsInN1YiI6IjY2MjVmNTMxMjIxYmE2MDE3YzE1NzJkNyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ._ptXiVZ5u4rTQuPcKsoNtkjgw8-QzR4spohqfWw4_mA"
]
print("requst \(request)") //https://api.themoviedb.org/3/trending/tv/week?language=ko-KR
데이터를 가져오기만 하니까 GET!
10초 이상 로딩 안될시 타임아웃이 되도록 설정했어요(사실 이거 TMDB에 기본으로 있던 코드 ㅋㅋ)
토큰도 넣어요 근데 이건 모두에게 제공되는 무료 토큰이라 이렇게 만천하에 공개되어도 상관 없지만 유료로 api 키나 토큰을 받아오게 되면 꼭꼭 잘 숨기십셔
로그를 찍어보면 전체 url 를 알 수 있슴다
지금까지는 사실 url를 만드는 과정에 불과 했습니다
Bearer 가 뭔지 궁금해서 찾아봤어요
Bearer 토큰은 토큰을 소유한 사람에게 액세스 권한을 부여하는 일반적인 토큰 클래스입니다. 액세스 토큰, ID 토큰, 자체 서명 JWT는 모두 Bearer 토큰입니다.
인증에 Bearer 토큰을 사용하려면 HTTPS와 같은 암호화된 프로토콜로 제공되는 보안이 필요합니다. Bearer 토큰을 가로채면 악의적인 행위자가 이를 사용하여 액세스 권한을 얻을 수 있습니다.
URLSession 사용하여 네트워크 통신
// URLSession을 사용하여 요청 수행
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
}
else if let data = data {
do {
let tvShow = try JSONDecoder().decode(Welcome.self, from: data)
// print(tvShow.results)
completion(tvShow.results)
}
catch {
print("Decode Error: \(error)")
}
}
}
// 네트워크 요청 시작
task.resume()
}
dataTask 로 네트워킹을 해볼게요
에러가 나면 바로 종료 되고 통신이 잘 되었다면 데이터를 받아올 수 있어요
JSONDecoder 로 아까 만든 데이터모델로 decode 해줍니다
근데 왜 TV 정보가 있는 Result 가 아니라 Welcome 을 디코드 할까요?
다시 데이터 모델을 보면
struct Welcome: Codable {
let page: Int
let results: [Result]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
// MARK: - Result
struct Result: Codable {
우리가 원하는건 TV 프로그램 목록 그 덩어리들이고
그건 Welcome 에 results 에 있습니다
그러므로 Welcome 을 디코딩 해야해요
그리고 task.resume() 으로 네트워킹 요청을 시작합니다
dataTask라는 작업은 URLSession에 작업을 부여하는 과정으로 일시적으로 정지가 된 상태입니다!
그렇기 때문에 꼭 task를 시작한다는 resume() 작업 꼭 실행해야합니다
@escaping
근데 디코딩한 데이터는 어떻게 쓸까요
네트워킹은 비동기적으로 실행되고 비동기적으로 작업을 수행하면 코드가 순차적으로 실행되지 않기 때문에 해당 함수에서 값을 다른 변수에 저장하거나 return 하는 과정이 불가능합니다.
그래서 @escaping 을 사용해줍니다
쉽게 말해서 요청한 url 에서 데이터를 다 가져오는게 끝!!나면 그 데이터를 넘겨준다고 생각하면 됩니다
Result 에 있는 모든 목록을 원하므로 배열로 달라고 해야해요
func fetchTV(completion: @escaping ([Result]) -> Void) {
import Foundation
class NetworkingManager {
static let shared = NetworkingManager() // Singleton 인스턴스
private init() {}
func fetchTV(completion: @escaping ([Result]) -> Void) {
let url = URL(string: "https://api.themoviedb.org/3/trending/tv/week")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [
URLQueryItem(name: "language", value: "ko-KR"),
]
components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET" // 기본이 GET
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjNzM3YjYzYTEzMDRmZWE3NTljM2NiZjEzZjgzOWRiMSIsInN1YiI6IjY2MjVmNTMxMjIxYmE2MDE3YzE1NzJkNyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ._ptXiVZ5u4rTQuPcKsoNtkjgw8-QzR4spohqfWw4_mA"
]
print("requst \(request)") //https://api.themoviedb.org/3/trending/tv/week?language=ko-KR
// URLSession을 사용하여 요청 수행
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
}
else if let data = data {
do {
let tvShow = try JSONDecoder().decode(Welcome.self, from: data)
// print(tvShow.results)
completion(tvShow.results)
}
catch {
print("Decode Error: \(error)")
}
}
}
// 네트워크 요청 시작
task.resume()
}
}
이제 사용해 봅시다
리스트 담을 빈배열 생성
var TVShowList: [Result] = []
데이터 넣어주기!!!!!!!!라고 하는게 맞나
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TVShowList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: MovieTableViewCell.identifier, for: indexPath) as? MovieTableViewCell else { return UITableViewCell() }
let item = TVShowList[indexPath.row]
cell.title.text = item.name
cell.image.loadFromURL("https://image.tmdb.org/t/p/w500\(item.posterPath)")
return cell
}
}
테이블뷰에다가 그릴겁니다
ui 그리는거나 커스텀셀 만드는 부분은 생략할게요
배열에 담긴 갯수만큼 그려줄거라 TVShowList.count 해주구
let item = TVShowList[indexPath.row]
cell.title.text = item.name
텍스트 바꾸기,,
그리고 이미지 넣기는 넘 어려웠는데
extension UIImageView {
func loadFromURL(_ urlString: String) {
guard let url = URL(string: urlString) else { return }
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
}
여러가지 방법이 있지만 이렇게 해보았습니다
1. UIImageView 를 확장하여 새로운 메서드를 생성합니다
2. 파라미터에ㅔ url 를 String 으로 넣을거예요
3. 유효하지 않을 경우 때문에 옵셔널 바인딩을 해줍니다
4. 이미지로드를 백그라운드에서 해주기 위해 DispatchQueue.global().async 요걸 해주구
5. url 에서 데이터를 가져온뒤 가져온 데이터를 UIImage로 변환합니다.
6. UI는 메인스레드에서 그리므로 DispatchQueue.main.async
아 숨차
이미지를 다른 방식으로도 가져올 수 있는데 그건 다음에 해볼게요
안전존까지만 그렸더니 테이블뷰 색상이 보이는게 킹받군여
'iOS Swift > Study' 카테고리의 다른 글
Swift 버튼에 시스템 이미지 크기 바꾸기 (0) | 2024.05.04 |
---|---|
Swift codebase UI - ToDOList UserDefaults 로 데이터 저장하기 (4) | 2024.05.03 |
Swift Delegate 패턴 예제로 알아보기 (6) | 2024.04.30 |
Swift codebase UI 그리기 내 버튼 왜 클릭 안돼 (6) | 2024.04.30 |
Swift codebase UI 스크롤뷰가 있는 화면 하단에 버튼 고정하기 (0) | 2024.04.25 |
댓글