

사용할 방법은 네비게이션 컨트롤러와 segue다




//
// ViewController.swift
// Moviekkr
//
// Created by 1 on 2026/04/28.
//
import UIKit
// MARK: - 1. API 데이터 모델 (JSON 파싱용 구조체)
// 영화진흥위원회 JSON 데이터 구조와 똑같이 매칭되어야 합니다. (Codable 프로토콜 채택)
struct MovieData : Codable {
let boxOfficeResult : BoxOfficeResult
}
struct BoxOfficeResult : Codable {
let dailyBoxOfficeList : [DailyBoxOfficeList] // 일별 박스오피스 배열 (영화 10개)
}
struct DailyBoxOfficeList : Codable {
let movieNm : String // 영화 제목
let audiCnt : String // 당일 관객수
let audiAcc : String // 누적 관객수
let rank : String // 박스오피스 순위
}
// MARK: - 2. 메인 뷰 컨트롤러
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// 스토리보드의 테이블 뷰와 연결된 아웃렛 변수
@IBOutlet weak var table: UITableView!
// API 통신 결과(영화 데이터)를 저장할 변수 (초기값은 데이터가 없으므로 nil 옵셔널)
var movieData : MovieData?
// 영화 오픈 API 기본 주소 (맨 뒤에 날짜 'yyyyMMdd'만 붙여서 사용)
var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=eaa1c89ff79f8f288c9366ac031f7510&targetDt="
// 화면이 처음 메모리에 로드될 때 실행되는 시점
override func viewDidLoad() {
super.viewDidLoad()
// 테이블 뷰의 실제 행동(이벤트)과 보여줄 데이터 처리를 자기 자신(self)으로 지정
table.dataSource = self
table.delegate = self
// API 주소 뒤에 '어제 날짜 문자열'을 결합 (예: ...&targetDt=20260525)
movieURL += makeYesterdayString()
// 웹에서 영화 데이터 받아오는 함수 호출
getData()
}
// MARK: - 날짜 계산 함수
func makeYesterdayString() -> String {
// 1. 현재 날짜에서 하루(24시간)를 뺀 날짜 계산 (실패하면 오늘 날짜 사용)
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()
// 2. 날짜를 문자열로 바꾸기 위한 Formatter 생성
let formatter = DateFormatter()
// 3. 원하는 형식(년월일: yyyyMMdd) 지정
formatter.dateFormat = "yyyyMMdd"
// 4. 변환된 어제 날짜 문자열 반환
return formatter.string(from: yesterday)
}
// MARK: - 네트워킹 데이터 수신 함수 (URLSession)
func getData(){
// 문자열 주소를 실제 URL 타입으로 안전하게 변경 (실패하면 함수 종료)
guard let url = URL(string: movieURL) else { return }
// 기본 네트워크 세션 구성 사용
let session = URLSession(configuration: .default)
// 백그라운드에서 데이터를 받아오는 비동기 태스크(Task) 생성
let task = session.dataTask(with: url) { data, response, error in
// 에러가 발생했다면 바로 통신 중단
if error != nil { return }
// 전달받은 데이터가 정상적으로 존재하치 확인
guard let JSONdata = data else { return }
// [디버그용] 받아온 JSON 원본을 문자열로 바꿔서 콘솔창에 출력해보기
let dataString = String(data: JSONdata, encoding: .utf8)
print(dataString!)
// JSON 데이터를 스위프트 구조체로 바꿀 디코더 생성
let decoder = JSONDecoder()
do {
// JSON 데이터를 처음에 정의한 MovieData 구조체 형태로 파싱(변환)
let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
// [디버그용] 첫 번째 박스오피스 영화 제목 출력해보기
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
// 전체 화면 레이아웃 갱신을 위해 클래스 변수에 데이터 저장
self.movieData = decodedData
// ⚠️ 중요: UI 변경(테이블 뷰 새로고침)은 반드시 '메인 스레드'에서 처리해야 함
// (🚨 주의: .sync는 데드락 위험이 있어서 실무에선 .async를 주로 권장합니다!)
DispatchQueue.main.sync {
self.table.reloadData() // 테이블 뷰를 새로 그려서 UI에 반영!
}
} catch {
// 파싱에 실패했을 때 에러 내용 출력
print(error)
}
}
// 생성된 네트워킹 작업을 실제로 시작(실행)시키는 코드
task.resume()
}
// MARK: - 테이블 뷰 데이터소스 & 딜리게이트 (프로토콜 필수 구현)
// 1. 테이블 뷰의 행(Cell) 개수 지정
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// (💡 개선 제안했던 부분: 데이터 수집 전 앱 크래시 방지를 위해 movieData?...count ?? 0 권장)
return 10
}
// 2. 섹션의 개수 (여기선 단일 리스트이므로 1개)
func numberOfSections(in tableView: UITableView) -> Int {
// 기본값이 1이라 생략 가능하지만 명시적으로 작성됨
return 1
}
// 3. 특정 셀(행)을 터치했을 때 실행되는 함수
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 누른 셀의 위치(예: [0, 2] -> 0번째 섹션의 2번째 줄)를 콘솔에 출력
print(indexPath.description)
}
// 4. ⭐️ 각 줄마다 어떤 내용(텍스트, 디자인)의 셀을 보여줄지 결정하는 핵심 함수
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 재사용 큐에서 "myCell" 식별자를 가진 커스텀 셀(MyTableViewCell)을 가져옴
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
// --- [기존 임시 코드] 데이터 바인딩 ---
cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc
cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt
// --- 💡 [수정 내용] 영화 순위 + 영화 제목 결합하기 ---
if let movie = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row] {
let rank = movie.rank // "1", "2", "3"... (API 순위 문자열)
let name = movie.movieNm // 영화 제목
// 제목 레이블 텍스트를 "1위. 영화제목" 형태로 가공하여 조립
cell.movieName.text = "\(rank)위. \(name)"
} else {
// 네트워크가 아직 느려서 영화 데이터가 없는 경우의 예외 처리
cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
}
// --- 당일 관객 수 콤마(,) 포맷팅 ---
if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
let numF = NumberFormatter()
numF.numberStyle = .decimal // 3자리 단위 쉼표 스타일 지정
let aCount = Int(aCnt)! // "12345" 문자열을 숫자로 변경
let result = numF.string(for: aCount)! + "명" // "12,345명" 생성
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
}
// 5. 리스트의 상단 헤더 타이틀 내용 지정
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "🍿박스오피스(영화진흥위원회제공:" + makeYesterdayString() + ")🍿"
}
// 6. 리스트의 하단 푸터 타이틀 내용 지정
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "made by ggongs 김경래"
}
// MARK: - 3. 화면 전환 및 데이터 전달 (Segue 준비 작업)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 목적지 뷰 컨트롤러를 'DetailViewController' 타입으로 강제 형변환하여 타겟팅
let dest = segue.destination as! DetailViewController
// 현재 테이블 뷰에서 사용자가 클릭하여 선택된 상태인 셀의 인덱스 경로를 가져옴
let myIndexPath = table.indexPathForSelectedRow!
let row = myIndexPath.row // 클릭한 셀이 몇 번째 줄(인덱스)인지 추출
// 클릭한 줄에 해당하는 실제 영화 데이터의 '영화 제목(movieNm)'을 추출해서
// 다음 화면(DetailViewController)의 'movieName' 변수에 다이렉트로 던져줌!
dest.movieName = (movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)!
}
}
webkit import해도 오류 메시지가 나오는 경우 다음과 같이 설정

지도 띄우기
import UIKit
import WebKit
class MapViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlKorString = "https://map.naver.com/p/search/영화관"
let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else{return}
let request = URLRequest(url: url)
webView.load(request)
}

gemini api키 발급
https://ai.google.dev/gemini-api/docs/api-key?hl=ko
마음이 많이 가라앉고 슬프실 때군요. 그럴 때는 억지로 밝은 분위기의 영화를 보며 감정을 돌리는 것도 방법이지만, 오히려 내 마음과 닮은 슬픔을 마주하며 펑펑 울거나 조용한 위로를 건네받는 것이 큰 힘이 되기도 합니다.
지금의 마음 상태에 따라 골라 보실 수 있도록 서로 다른 결의 위로를 담은 영화 3편을 추천해 드릴게요.
1. 감정을 쏟아내고 펑펑 울고 싶을 때
🎬 어바웃 타임 (About Time)
- 장르: 로맨스 / 드라마 / 판타지
- 줄거리: 성인이 되자마자 가문 대대로 내려오는 '시간 여행 능력'이 있다는 비밀을 알게 된 주인공 팀. 그는 첫눈에 반한 메리와의 사랑을 이루기 위해 능력을 사용하기 시작합니다. 하지만 완벽한 인생을 위해 시간을 되돌릴수록 예기치 못한 일들이 발생합니다.
- 위로 포인트: 단순한 로맨스 영화를 넘어, 인생과 가족, 그리고 '두 번 다시 돌아오지 않을 오늘 하루'의 소중함을 일깨워주는 영화입니다. 후반부 아버지가 건네는 조언과 두 사람의 마지막 산책 장면은 가슴을 먹먹하게 만들며 참았던 눈물을 터뜨리게 해줄 거예요.
2. 상실감에 공감하며 조용한 위로가 필요할 때
🎬 맨체스터 바이 더 씨 (Manchester by the Sea)
- 장르: 드라마
- 줄거리: 아파트 관리인으로 일하며 세상과 단절된 채 외롭게 살아가는 리. 어느 날 형의 갑작스러운 사망 소식을 듣고 고향 '맨체스터 바이 더 씨'로 돌아오게 됩니다. 그곳에서 조카의 후견인이 된 그는, 자신이 그토록 도망치고 싶었던 과거의 거대한 아픔과 다시 마주하게 됩니다.
- 위로 포인트: 억지로 "힘내!", "이겨낼 수 있어"라고 말하지 않는 영화입니다. 슬픔을 억지로 극복하려 하지 않고, 그 슬픔을 안고 살아가는 것 또한 삶의 한 방식임을 묵묵히 보여줍니다. 내 슬픔이 너무 커서 세상에 홀로 남겨진 것 같을 때, 깊은 연대감과 조용한 위로를 건넵니다.
3. 따뜻한 온기로 마음을 채우고 싶을 때
🎬 리틀 포레스트 (Little Forest)
- 장르: 드라마 / 힐링
- 줄거리: 임용고시 낙방, 고단한 아르바이트, 인간관계에 지쳐 서울 생활을 뒤로하고 고향으로 돌아온 혜원. 그곳에서 오랜 친구들을 만나고, 사계절 동안 직접 농사지은 작물로 정성스레 한 끼 한 끼를 만들어 먹으며 겨울, 봄, 여름, 가을을 보냅니다.
- 위로 포인트: 자극적인 사건 없이, 제철 음식을 만들고 사계절이 흘러가는 모습을 보는 것만으로도 마음이 차분해집니다. 마음이 허기지고 지쳤을 때, "잠시 쉬어가도, 조금 달라도 괜찮다"는 따뜻한 온기를 채워주는 영화입니다.
방의 불을 조금 어둡게 켜두고, 따뜻한 차 한 잔이나 편안한 이불을 준비해서 마음이 이끄는 영화를 한 편 골라보세요. 울고 싶다면 참지 말고 마음껏 우셔도 좋습니다. 지금의 슬픈 감정도 결국은 지나갈 바람일 테니까요. 조금이나마 마음이 편안해지시길 바랄게요.
'iOS앱개발' 카테고리의 다른 글
| iOS실무 13주차 (0) | 2026.06.02 |
|---|---|
| iOS실무 11주차 (0) | 2026.05.19 |
| iOS실무 10주차 (0) | 2026.05.12 |
| iOS실무 9주차 (0) | 2026.04.28 |
| iOS실무 7주차 (0) | 2026.04.14 |