//
// ViewController.swift
// MovieYYC
//
// Created by 소프트웨어컴퓨터 on 2026/05/04.
//
import UIKit
let movie = ["슈퍼 마리오 갤럭시", "악마는 프라다를 입는다 2", "살목지", "프로젝트 헤일메리", "짱구", "왕과 사는 남자", "란 12.3", "내 이름은", "사랑의 하츄핑 특별판", "극장판 반짝반짝 달님이: 싱어롱 파티"]
let accumulatedAudience: [String] = [
"685993",
"736041",
"2636101",
"2649590",
"298506",
"16779516",
"199098",
"191975",
"7546",
"5950"
]
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
// let rank = indexPath.row + 1
// let name = movie[indexPath.row]
// let audience = accumulatedAudience[indexPath.row]
// let formatter = NumberFormatter()
// formatter.numberStyle = .decimal
//
// if let number = Int(audience),
// let formatted = formatter.string(from: NSNumber(value: number)) {
// cell.movieName.text = "[\(rank)등] \(name): \(formatted)명"
// }
cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
@IBOutlet weak var table: UITableView!
var movieData : MovieData?
var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=&targetDt="
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
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
table.delegate = self
table.dataSource = self
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}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
guard let JSONdata = data else {return}
let dataString = String(data: JSONdata, encoding: .utf8)
print(dataString!)
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
self.movieData = decodedData
DispatchQueue.main.async {
self.table.reloadData()
}
}catch{
print(error)
}
}
task.resume()
}
}
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Welcome {
let boxOfficeResult: BoxOfficeResult
}
// MARK: - BoxOfficeResult
struct BoxOfficeResult {
let boxofficeType, showRange: String
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList {
let rnum, rank, rankInten: String
let rankOldAndNew: RankOldAndNew
let movieCD, movieNm, openDt, salesAmt: String
let salesShare, salesInten, salesChange, salesAcc: String
let audiCnt, audiInten, audiChange, audiAcc: String
let scrnCnt, showCnt: String
}
enum RankOldAndNew: String {
case old
}
import UIKit
// MARK: - 데이터 모델 (Codable: JSON ↔ Swift 구조체 자동 변환)
// JSON 최상위 구조에 대응하는 구조체
struct MovieData: Codable {
let boxOfficeResult: BoxOfficeResult // JSON 키 "boxOfficeResult"와 이름 일치 필수
}
// 박스오피스 결과 구조체
struct BoxOfficeResult: Codable {
let dailyBoxOfficeList: [DailyBoxOfficeList] // 영화 목록 배열
}
// 개별 영화 정보 구조체 (JSON에서 필요한 필드만 선택적으로 선언 가능)
struct DailyBoxOfficeList: Codable {
let movieNm: String // 영화명
let audiCnt: String // 당일 관객수
let audiAcc: String // 누적 관객수
let rank: String // 순위
}
// MARK: - ViewController
// UITableViewDelegate: 테이블뷰 동작(선택, 높이 등) 처리
// UITableViewDataSource: 테이블뷰에 표시할 데이터 제공
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// 스토리보드에서 연결한 테이블뷰
@IBOutlet weak var table: UITableView!
// 네트워크에서 받아온 영화 데이터 저장용 프로퍼티
// Optional: 데이터를 받아오기 전까지는 nil 상태
var movieData: MovieData?
// 영진위 박스오피스 API URL (뒤에 날짜 문자열을 붙여서 완성)
var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=...&targetDt="
// MARK: - UITableViewDataSource 필수 메서드 ①
// 테이블뷰의 행(row) 개수 반환
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10 // 박스오피스 순위는 항상 10개
}
// MARK: - UITableViewDataSource 필수 메서드 ②
// 각 행에 표시할 셀을 구성해서 반환
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// dequeueReusableCell: 재사용 큐에서 셀을 꺼냄
// 스크롤 시 화면 밖으로 나간 셀을 새로 만들지 않고 재사용 → 메모리 절약
// as! MyTableViewCell: 커스텀 셀 클래스로 다운캐스팅
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
// indexPath.row: 현재 행 번호 (0부터 시작)
// Optional chaining(?.)으로 movieData가 nil이면 nil 반환 → 앱 크래시 방지
cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
return cell
}
// 섹션 개수 반환 (박스오피스는 1개 섹션으로 충분)
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - 뷰 생명주기
override func viewDidLoad() {
super.viewDidLoad()
// 테이블뷰의 delegate, dataSource를 이 ViewController가 담당하도록 연결
table.delegate = self
table.dataSource = self
// URL 완성: 기본 URL + 어제 날짜 문자열 (예: "20260510")
movieURL += makeYesterdayString()
// 네트워크 요청 시작
getData()
}
// MARK: - 어제 날짜를 yyyyMMdd 형식 문자열로 반환
func makeYesterdayString() -> String {
// Calendar.current: 현재 기기의 캘린더
// byAdding: .day, value: -1 → 오늘에서 1일 빼기
// Date(): 현재 날짜/시간
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd" // yyyy: 연도, MM: 월, dd: 일
return formatter.string(from: yesterday) // Date → String 변환
}
// MARK: - 네트워크 데이터 요청
func getData() {
// 1단계: String → URL 타입 변환 (failable initializer이므로 guard let 사용)
guard let url = URL(string: movieURL) else { return }
// 2단계: URLSession 생성 (.default: 기본 설정의 세션)
let session = URLSession(configuration: .default)
// 3단계: dataTask 생성 - url로 GET 요청을 보내고 응답을 클로저에서 처리
// 클로저는 백그라운드 스레드에서 실행됨
let task = session.dataTask(with: url) { data, response, error in
// 네트워크 에러 체크 (인터넷 끊김, 타임아웃 등)
if error != nil {
print(error!)
return // 에러 발생 시 이후 코드 실행 중단
}
// data가 nil이면 종료, 있으면 JSONdata에 바인딩
guard let JSONdata = data else { return }
// Data → String 변환 (JSON 원문 확인용, 디버깅 시 주석 해제)
let dataString = String(data: JSONdata, encoding: .utf8)
// print(dataString!)
// JSONDecoder: JSON(Data 타입) → Swift 구조체로 변환
let decoder = JSONDecoder()
do {
// try: decode()는 실패할 수 있는 함수(throws)이므로 try 필요
// MovieData.self: 어떤 구조체로 변환할지 타입 전달
let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
// 디코딩 확인용 출력 (테스트 후 제거 가능)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiCnt)
// 받아온 데이터를 프로퍼티에 저장
self.movieData = decodedData
// UI 업데이트는 반드시 메인 스레드에서 실행해야 함
// dataTask 클로저는 백그라운드 스레드이므로 DispatchQueue.main.async로 전환
DispatchQueue.main.async {
self.table.reloadData() // 테이블뷰 전체 새로고침
}
} catch {
// 디코딩 실패 원인: JSON 키와 구조체 프로퍼티 이름 불일치,
// 타입 불일치, 필수 키 누락 등
print(error)
}
}
// 4단계: task 시작 (resume() 호출 전까지 task는 일시정지 상태)
task.resume()
}
}