✅ 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))
}
}
}