카테고리 없음

iOS프로그래밍실무(12/15)

59date 2025. 5. 21. 16:42

시험 나옴

 

✅ Action Segue란?

  • 스토리보드에서 UI 요소(버튼, 셀 등)에 연결해 사용하는 자동 전환 방식입니다.
  • 사용자가 해당 UI 요소를 **터치하면 자동으로 화면 전환(segue)**이 발생합니다.
  • 별도의 코드 없이 간단한 화면 이동을 구현할 때 많이 사용합니다.

🟦 예시:
버튼을 스토리보드에서 다른 ViewController로 드래그 연결하면, 버튼을 누를 때 자동으로 화면 전환이 실행됨.


✅ Manual Segue란?

  • 코드에서 직접 호출해서 화면 전환을 실행하는 방식입니다.
  • 스토리보드에 segue는 만들어두되, 자동으로 실행되지 않고
    필요한 시점에 코드로 performSegue(withIdentifier:sender:)를 호출해 실행합니다.
  • 조건문, 인증, 데이터 확인 등 복잡한 로직에 따라 화면을 전환할 때 유용합니다.

🟩 예시:
로그인 버튼을 눌렀을 때, ID/비밀번호가 맞으면 performSegue(...)로 다음 화면으로 이동.

 

 

Navigation Controller를 추가하면
ViewController는 내비게이션 구조의 일부로 바뀌고
상단 바 + 뒤로 가기 기능이 생기며
→ 화면 전환도 Push 방식 중심으로 자연스럽게 연결할 수 있게 됨

 

✅ prepare(for:sender:)란?

prepare(for:sender:)는
Segue를 통해 화면이 전환되기 직전에 호출되는 메서드

이 메서드를 오버라이드해서,
다음 화면(도착지 ViewController)으로 데이터를 전달할 수 있음.

 

 

// MARK: - Model

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
}


// MARK: - View

// MyTableViewCell.swift (View는 그대로 유지하되 역할 명확히)
import UIKit

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


// MARK: - Controller

// ViewController.swift
import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var table: UITableView!
    var viewModel = MovieViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
        viewModel.fetchMovies {
            DispatchQueue.main.async {
                self.table.reloadData()
            }
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toDetail",
           let dest = segue.destination as? DetailViewController,
           let indexPath = table.indexPathForSelectedRow {
            dest.movieName = viewModel.getMovieName(at: indexPath.row)
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfMovies
    }

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

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

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

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


// MARK: - ViewModel (중간 계층)

class MovieViewModel {
    private(set) var movieData: MovieData?
    var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=744bde0394fa07263720dce92b14a31d&targetDt="

    var numberOfMovies: Int {
        return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
    }

    var yesterdayString: String {
        let y = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        return formatter.string(from: y)
    }

    func fetchMovies(completion: @escaping () -> Void) {
        movieURL += yesterdayString
        guard let url = URL(string: movieURL) else { return }
        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else {
                print("Fetch error: \(error?.localizedDescription ?? "unknown")")
                return
            }
            do {
                let decoded = try JSONDecoder().decode(MovieData.self, from: data)
                self.movieData = decoded
                completion()
            } catch {
                print("Decoding error: \(error)")
            }
        }.resume()
    }

    func movie(at index: Int) -> DailyBoxOfficeList {
        return movieData!.boxOfficeResult.dailyBoxOfficeList[index]
    }

    func getMovieName(at index: Int) -> String {
        return movie(at: index).movieNm
    }

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


// MARK: - DetailViewController.swift

import UIKit
import WebKit

class DetailViewController: UIViewController {
    @IBOutlet weak var nameLable: UILabel!
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        nameLable.text = movieName
        navigationItem.title = movieName
        let urlString = ("http://namu.wiki/w/" + movieName).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
        if let url = URL(string: urlString) {
            webView.load(URLRequest(url: url))
        }
    }
}