Open API 기반 iOS앱 개발(5)

CDH·2025년 5월 21일

화면 전환

root view controller (중요)

  • UIWindow의 가장 첫 번째, 최상위 뷰 컨트롤러
  • 앱이 실행될 때 처음 사용자에게 보여지는 메인 뷰 컨트롤러

segue type과 Action Segue vs Manual Segue (참고)


Navigation Bar 와 Relationship

Storyboard Entry Point


Storyboard ID : DetailViewController

Identity inspector 확인하기



DetailViewController라는 class만들기

새로운 View Controller와 소스 작성할 class 연결


nameLabel.text에 대입할 movieName 프로퍼티 선언


Table의 cell과 Detail View Controller를 Segue연결


Segue의 identifier지정 : detailView


(중요!) func prepare(for segue: UIStoryboardSegue, sender: Any?)

  • iOS 개발에서 화면 전환(segue)이 발생하기 전에 데이터를 다음 화면에 전달할 수 있도록 해주는 메서드입니다. 주로 스토리보드를 사용하는 앱에서 ViewController 간 데이터 전달에 사용


UIViewController의 destination 에서 다운캐스팅(중요)

다운 캐스팅 없이 사용 -> 다음과 같은 에러 발생함

오류 수정 후 (다운캐스팅 후)



어떤 row를 눌렀는지 콘솔에 출력해 보기

자료형은 옵셔널이기 때문에 풀어줘야 함


segue를 이용하여 화면 전환시 값 전달


DetailViewController 에서 수정 후 최종적으로 출력


WKWebView Outlet지정 : webView


(개인적 중요!) 퍼센트 인코딩(percent encoding)


addingPercentEncoding(withAllowedCharacters:)


한글이 포함된 url 처리하기


AI 이용하여 MVC 모델로 리팩토링 하기

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"
    }
}

0개의 댓글