

| 구분 | Action Segue | Manual Segue |
|---|---|---|
| 연결 방법 | UI 요소에서 바로 segue 연결 | ViewController끼리 segue 연결 후 Identifier 설정 |
| 실행 방식 | UI 액션 발생 시 자동 전환 | 코드로 performSegue 호출 시 직접 전환 |
| 코드 필요 | 별도 코드 필요 없음 | 코드로 전환해야 직접 호출 필요 |
| 주요 용도 | 단순히 UI로 바로 이동할 때 | 조건부/특정 상황에서만 이동할 때 |

func prepare(for segue: UIStoryboardSegue, sender: Any?)
override func prepare(for segue: UIStoryboardSegue, sender: Any?)

let urlKorString = "https://search.naver.com/search.naver?query="+movieName
let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!


// MapViewController.swift
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)
}
}



// MovieModels.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
}
// MovieService.swift
import Foundation
class MovieService {
static let shared = MovieService()
private let baseURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=<API_KEY>&targetDt="
func fetchDailyBoxOffice(completion: @escaping (Result<[DailyBoxOfficeList], Error>) -> Void) {
let urlStr = baseURL + MovieService.yesterdayString()
guard let url = URL(string: urlStr) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else { return }
do {
let movieData = try JSONDecoder().decode(MovieData.self, from: data)
let list = movieData.boxOfficeResult.dailyBoxOfficeList
completion(.success(list))
} catch {
completion(.failure(error))
}
}
task.resume()
}
/// 컨트롤러에서 날짜 때문에 반복적으로 사용하므로 static method로
static func yesterdayString() -> String {
let today = Date()
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd"
return formatter.string(from: yesterday)
}
}
MyTableViewCell.swift 그대로 사용 ViewController에서 Model과 View를 분리하고,
데이터 요청, 뷰 표시 역할로 명확히 한정합니다.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var table: UITableView!
// MARK: Model
var movies: [DailyBoxOfficeList] = []
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
fetchMovies()
}
private func fetchMovies() {
MovieService.shared.fetchDailyBoxOffice { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let movieList):
self?.movies = movieList
self?.table.reloadData()
case .failure(let error):
print("영화 정보 로딩 실패: \(error)")
}
}
}
}
// MARK: - UITableView
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as? MyTableViewCell else {
return UITableViewCell()
}
let movie = movies[indexPath.row]
cell.movieName.text = "[\(movie.rank)위] \(movie.movieNm)"
// 숫자 변환
if let aCnt = Int(movie.audiCnt) {
let numF = NumberFormatter()
numF.numberStyle = .decimal
cell.audiCount.text = "어제: \(numF.string(for: aCnt)! )명"
} else {
cell.audiCount.text = "어제: -명"
}
if let aAcc = Int(movie.audiAcc) {
let numF = NumberFormatter()
numF.numberStyle = .decimal
cell.audiAccumulate.text = "누적: \(numF.string(for: aAcc)! )명"
} else {
cell.audiAccumulate.text = "누적: -명"
}
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "🍿박스오피스(영화진흥위원회제공:" + MovieService.yesterdayString() + ")🍿"
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "made by gsmin"
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// nothing
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let dest = segue.destination as? DetailViewController,
let idx = table.indexPathForSelectedRow?.row else { return }
dest.movieName = movies[idx].movieNm
}
}
MyTableViewCell.swift, DetailViewController.swift,
MapViewController.swift는 변화 없음.
MovieModels.swift, MovieService.swiftStoryboard, MyTableViewCell.swiftViewController.swift, DetailViewController.swift, MapViewController.swift이렇게 하면 네트워크 로직의 재활용성이 높아지고(다른 뷰 컨트롤러에서도 호출 가능),
ViewController는 Model과 View 연결에만 집중된, 패턴의 본래 취지에 맞는 구조가 됩니다.
📁 Model
├─ MovieModels.swift (Codable)
└─ MovieService.swift (네트워크)
📁 View
├─ MyTableViewCell.swift
└─ Storyboard 파일
📁 Controller
├─ ViewController.swift (Table, Navigation)
├─ DetailViewController.swift
└─ MapViewController.swift