



Navigation Bar 와 Relationship
Storyboard Entry Point
Identity inspector 확인하기







다운 캐스팅 없이 사용 -> 다음과 같은 에러 발생함
오류 수정 후 (다운캐스팅 후)
자료형은 옵셔널이기 때문에 풀어줘야 함








ViewController.swift
import UIKit
let movie = ["야당", "마인크래프트", "썬더볼츠", "진격의 거인", "야당2"]
struct MovieData : Codable { //
let boxOfficeResult : BoxOfficeResult
}
struct BoxOfficeResult : Codable {
let dailyBoxOfficeList : [DailyBoxOfficeList]
}
struct DailyBoxOfficeList : Codable {
let movieNm : String
let audiCnt : String
let audiAcc : String
let rank : String
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var table: UITableView!
var movieData : MovieData?
var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=a98a0bc5260826880d584b21ac65109d&targetDt="
// url 주소 변수 추가
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self // self 두 줄 없으면 동작 안 함
table.dataSource = self
movieURL = movieURL + makeYesterdayString() // 어제 날짜를 불러오기 위한 함수 만들고 호출하기
getData()
}
func makeYesterdayString() -> String {
let y = Calendar.current.date(byAdding:.day, value : -1, to : Date())!
let dateF = DateFormatter()
dateF.dateFormat = "yyyyMMdd"
let day = dateF.string(from: y)
return day
}
func getData() { // 옵셔널 형이라 풀어줘야 된다
guard let url = URL(string: movieURL) else { return } // guard let : 거짓일 경우를 먼저 작성
let session = URLSession(configuration: .default) // .default 를 가장 많이 사용
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
} // if 문을 이용하여 에러처리
guard let JSONdata = data else { return }
let dataString = String(data: JSONdata, encoding: .utf8)
// utf8 방식으로 인코딩된 데이터를 String형으로 변경하여 자료의 크기가 아닌 자료 자체를 출력
// print(dataString!) // 전체 영화 순위 데이터
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
// MovieData는 자료형이기 때문에 값도 써줘야 된다 // + 예외처리도 해줘야 한다.
self.movieData = decodedData // 중요!
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm) // 영화 이름
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc) // 누적 관객수
DispatchQueue.main.async {
self.table.reloadData()
// (중요!) UiView 에서 동작하는 소스는 background에서 동작하면 안 되고 메인 쓰레드에서 동작해야 된다
}
} catch {
print(error)
}
}
task.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let dest = segue.destination as! DetailViewController
// dest.movieName = "영화제목 아무거나"
let myIndexPath = table.indexPathForSelectedRow!
let row = myIndexPath.row
dest.movieName = (movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)! // 옵셔널 풀기
// print(row)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10 // 5칸짜리 테이블 뷰
}
// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell // 다운캐스팅
//
// cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
// // print(indexPath.description) // 추가 (현재 보이는 셀들의 인덱스를 개발자만 보이도록 출력)
// cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc
// cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt
// return cell
// }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
guard let mRank = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].rank else {return UITableViewCell()}
guard let mName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm else {return UITableViewCell()}
cell.movieName.text = "[\(mRank)위] \(mName)"
if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
let numF = NumberFormatter()
numF.numberStyle = .decimal
let aCount = Int(aCnt)!
let result = numF.string(for: aCount)!+"명"
cell.audiCount.text = "어제: \(result)"
}
if let aAcc = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
let numF = NumberFormatter()
numF.numberStyle = .decimal
let aAcc1 = Int(aAcc)!
let result = numF.string(for: aAcc1)!+"명"
cell.audiAccumulate.text = "누적: \(result)"
}
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "박스오피스(영화진흥위원회 제공: " + makeYesterdayString() + ")"
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "made by CDH"
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.description) // 특정 셀을 클릭(선택) 했을 때 해당하는 row 값을 개발자만 보이도록 출력
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // 섹션의 수 설정
}
}
변환 후
Model/Movie.swift
import Foundation
struct MovieData: Codable {
let boxOfficeResult: BoxOfficeResult
}
struct BoxOfficeResult: Codable {
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
struct DailyBoxOfficeList: Codable {
let movieNm: String
let audiCnt: String
let audiAcc: String
let rank: String
}
Service/MovieService.swift
import Foundation
class MovieService {
private let baseURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json"
private let apiKey = "a98a0bc5260826880d584b21ac65109d"
func fetchMovies(for date: String, completion: @escaping (MovieData?) -> Void) {
let urlStr = "\(baseURL)?key=\(apiKey)&targetDt=\(date)"
guard let url = URL(string: urlStr) else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard error == nil, let jsonData = data else {
print(error ?? "Unknown error")
completion(nil)
return
}
do {
let decodedData = try JSONDecoder().decode(MovieData.self, from: jsonData)
completion(decodedData)
} catch {
print("JSON decode error:", error)
completion(nil)
}
}
task.resume()
}
}
Controller/ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var table: UITableView!
private let movieService = MovieService()
private var movieData: MovieData?
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
fetchData()
}
private func fetchData() {
let date = getYesterdayString()
movieService.fetchMovies(for: date) { [weak self] data in
guard let self = self, let data = data else { return }
self.movieData = data
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
private func getYesterdayString() -> String {
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd"
return formatter.string(from: yesterday)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? DetailViewController,
let indexPath = table.indexPathForSelectedRow else { return }
let movie = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row]
destination.movieName = movie?.movieNm
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let movie = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row],
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as? MyTableViewCell else {
return UITableViewCell()
}
cell.configure(with: movie)
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "박스오피스(영화진흥위원회 제공: \(getYesterdayString()))"
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "made by CDH"
}
}