BoxOffice App


MVC

Model

import Foundation

// Codable을 준수하는 구조체 정의
// 영화 데이터를 담는 구조체
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 MovieDataManager {
    var movieData: MovieData?
    let movieURLBase = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d9dfbf5b0a8b138015b48bd3b9ecfe2b&targetDt="
    
    func fetchMovieData(completion: @escaping (MovieData?) -> Void) {
        let movieURL = movieURLBase + makeYesterdayString()
        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!)
                completion(nil)
                return
            }
            guard let JSONdata = data else {
                completion(nil)
                return
            }
            
            let decoder = JSONDecoder()
            do {
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                completion(decodedData)
            } catch {
                print(error)
                completion(nil)
            }
        }
        task.resume()
    }
    
    private func makeYesterdayString() -> String {
        let today = Date()
        let calendar = Calendar.current
        let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        return formatter.string(from: yesterday)
    }
}

View

import UIKit

class MyTableViewCell: UITableViewCell {
    @IBOutlet weak var movieName: UILabel!
    @IBOutlet weak var audiAccumulate: UILabel!
    @IBOutlet weak var audiCount: UILabel!
}

Controller

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView!
    var movieDataManager = MovieDataManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 테이블 뷰의 데이터 소스와 델리게이트 설정
        table.dataSource = self
        table.delegate = self
        
        // 데이터 가져오기 메소드 호출
        movieDataManager.fetchMovieData { [weak self] data in
            guard let self = self else { return }
            self.movieDataManager.movieData = data
            
            // 메인 스레드에서 테이블 뷰 리로드
            DispatchQueue.main.async {
                self.table.reloadData()
            }
        }
    }
    
    // 테이블 뷰의 섹션당 행의 수를 반환하는 메소드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return movieDataManager.movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
    }
    
    // 각 행에 대한 셀을 구성하는 메소드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        
        // 영화 순위, 이름, 누적 관객 수, 어제 관객 수 설정
        guard let movie = movieDataManager.movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row] else { return UITableViewCell() }
        
        cell.movieName.text = "[\(movie.rank)위] \(movie.movieNm)"
        
        if let aAcc = Int(movie.audiAcc) {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            cell.audiAccumulate.text = "누적 : \(numF.string(from: NSNumber(value: aAcc))!)명"
        }
        
        if let aCnt = Int(movie.audiCnt) {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            cell.audiCount.text = "어제 : \(numF.string(from: NSNumber(value: aCnt))!)명"
        }
        
        return cell
    }
    
    // 테이블 뷰의 섹션 수를 반환하는 메소드
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    // 테이블 뷰의 행이 선택되었을 때 호출되는 메소드
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 선택된 행에 대한 처리
    }
    
    // 테이블 뷰의 섹션 헤더 제목을 반환하는 메소드
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "🍿박스오피스(영화진흥위원회제공:" + movieDataManager.makeYesterdayString() + ")🍿"
    }
    
    // 테이블 뷰의 섹션 푸터 제목을 반환하는 메소드
    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "by choimu"
    }
    
    // 세그웨이를 준비하는 메소드
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let dest = segue.destination as! DetailViewController
        
        // 선택된 행의 인덱스 경로를 가져와서 영화 이름 전달
        let myIndexPath = table.indexPathForSelectedRow!
        let row = myIndexPath.row
        dest.movieName = (movieDataManager.movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)!
    }
}

Refactoring

``` Swift // // ViewController.swift // MovieCMU // // Created by Induk-cs on 2024/05/02. //

import UIKit

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? let movieURL = “https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d9dfbf5b0a8b138015b48bd3b9ecfe2b&targetDt=”

override func viewDidLoad() {
    super.viewDidLoad()
    table.dataSource = self
    table.delegate = self
    
    let yesterdayString = makeYesterdayString()
    getData(from: movieURL + yesterdayString)
}

func makeYesterdayString() -> String {
    let calendar = Calendar.current
    guard let yesterday = calendar.date(byAdding: .day, value: -1, to: Date()) else { return "" }
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyyMMdd"
    return formatter.string(from: yesterday)
}

func getData(from urlString: String) {
    guard let url = URL(string: urlString) else { return }
    let session = URLSession(configuration: .default)
    let task = session.dataTask(with: url) { data, response, error in
        if let error = error {
            print(error)
            return
        }
        guard let JSONdata = data else { return }
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
            self.movieData = decodedData
            DispatchQueue.main.async {
                self.table.reloadData()
            }
        } catch {
            print(error)
        }
    }
    task.resume()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as? MyTableViewCell else {
        return UITableViewCell()
    }
    let movie = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row]
    cell.movieName.text = "[\(movie?.rank ?? "")위] \(movie?.movieNm ?? "")"
    cell.audiAccumulate.text = "누적: \(formatNumber(movie?.audiAcc ?? ""))명"
    cell.audiCount.text = "어제: \(formatNumber(movie?.audiCnt ?? ""))명"
    return cell
}

func formatNumber(_ numberString: String) -> String {
    guard let number = Int(numberString) else { return numberString }
    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    return formatter.string(from: NSNumber(value: number)) ?? numberString
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // 선택된 셀에 대한 작업을 여기에 추가할 수 있습니다.
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return "🍿박스오피스(영화진흥위원회제공: \(makeYesterdayString()))🍿"
}

func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
    return "by choimu"
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let dest = segue.destination as? DetailViewController,
          let indexPath = table.indexPathForSelectedRow else { return }
    dest.movieName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm ?? ""
} }

class MyTableViewCell: UITableViewCell { @IBOutlet weak var movieName: UILabel! @IBOutlet weak var audiAccumulate: UILabel! @IBOutlet weak var audiCount: UILabel! }

class DetailViewController: UIViewController { var movieName: String?

override func viewDidLoad() {
    super.viewDidLoad()
    // movieName을 사용하는 추가 설정
} }

``’








출처


  • Smile Han의 iOS 프로그래밍 실무, 한성현(출판 예정), PPT로 제공
  • Do it! 스위프트로 아이폰 앱 만들기 입문(개정 7판)(이지스퍼블리싱,송호정, 이범근, 2023.1)
  • 핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍(제이펍, 닐 스미스, 2023.12)
    • https://www.techotopia.com/index.php/IOS_iPhone_iPad_eBooks
  • 스위프트 프로그래밍(Swift 5) 3판(한빛미디어, 야곰, 2019.10)
  • 꼼꼼한 재은 씨의 스위프트 기본편 (루비페이퍼, 이재은, 2018.05)
  • 꼼꼼한 재은 씨의 스위프트 실전편 (Swift) (루비페이퍼, 이재은, 2018.08)
  • 꼼꼼한 재은 씨의 Swift 문법편 (루비페이퍼, 이재은, 2017.12)