📱 모바일 프로그래밍/iOS 프로그래밍 실무
iOS 프로그래밍 실무 12주차
SA성아
2025. 5. 21. 16:42
root view controller
- UIWindow의 가장 첫 번째, 최상위 뷰 컨트롤러
- 앱이 실행될 때 처음 사용자에게 보여지는 메인 뷰 컨트롤러
- UINavigationController, UITabBarController 와 같은 컨테이너 뷰
컨트롤러가 주로 루트로 사용됨
Action Segue와 Manual Segue
구분 | Action Segue | Manual Segue |
정의 | Storyboard에서 버튼 등 UI 요소에 연결하여 자동 실행되는 segue | 코드에서 직접 호출하여 실행하는 segue (performSegue) |
연결 방식 | Control + 드래그로 UI 요소에서 다른 ViewController로 연결 | ViewController에서 코드로 performSegue(withIdentifier:) 호출 |
트리거 방식 | UI 요소를 사용자가 터치하면 자동 실행 | 특정 로직 또는 조건에 따라 프로그래머가 실행 |
사용 예시 | 버튼 클릭 시 다음 화면으로 이동 | 로그인 성공 후에만 다음 화면으로 이동 등 |
식별자 필요 여부 | 선택사항 (식별자 없이도 가능) | 식별자 필수 (performSegue(withIdentifier:)에 필요) |
Storyboard 의존도 | storyboard 설정만으로도 사용 가능 | storyboard에서 segue를 만든 후, 식별자를 통해 호출 |
Navigation Contoller 추가
- Navigation Controller에 연결된 View Controller에 navigation Item(<back) 생김
- Navigation Controller에 storyboard entry point 생김
- 앱 실행시 처음 보여주는 initial view가 바뀜
- Navigation Controller의 navigation stack 가장 하단의 view controller를 root view controller라 하고 View Controller와 segue가 연결되어 있음
Storyboard ID: DetailViewController <- Identity inspector 에서
func prepare(for segue: UIStoryboardSegue, sender: Any?)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// segue.identifier를 통해 어떤 segue인지 확인
// segue.destination을 통해 이동할 ViewController에 접근
}
🔸 예시: AViewController → BViewController 로 데이터 전달
1. 시나리오
- AViewController에서 사용자가 선택한 이름을 BViewController로 전달하고 싶을 때
2. Storyboard 설정
- AViewController에서 BViewController로 segue 연결
- segue에 식별자(identifier) 설정 (예: "toBVC")
3. BViewController.swift
class BViewController: UIViewController {
var receivedName: String? // A에서 전달받을 변수
}
4. AViewController.swift
class AViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toBVC" {
if let destinationVC = segue.destination as? BViewController {
destinationVC.receivedName = nameTextField.text
}
}
}
}
✅ 설명
부분 | 설명 |
segue.identifier | 어떤 segue가 실행되는지 식별합니다. 여러 segue가 있을 경우 구분할 수 있습니다. |
segue.destination | 전환될 목적지 ViewController입니다. 형변환해서 속성에 접근합니다. |
destinationVC.receivedName | 목적지 VC의 프로퍼티에 데이터 전달 (예: 이름 전달) |
제목을 누르면
이렇게 뜬다
import UIKit
import WebKit
class DetailViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var webView: WKWebView!
var movieName = ""
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = movieName
navigationItem.title = movieName
let urlKorString = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query="+movieName
let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
webView.load(request)
}
}
제목을 누르면 그 영화의 네이버 창으로 들어가진다
MVC 리팩토링
// MARK: - Model
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
}
class MovieService {
static let shared = MovieService()
private let baseURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=fecd5be7de01c30e13107d1967fe285d&targetDt="
func fetchMovies(for date: String, completion: @escaping (MovieData?) -> Void) {
guard let url = URL(string: baseURL + date) else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard error == nil, let data = data else {
completion(nil)
return
}
do {
let decodedData = try JSONDecoder().decode(MovieData.self, from: data)
completion(decodedData)
} catch {
print("Decoding error: \(error)")
completion(nil)
}
}
task.resume()
}
func makeYesterdayString() -> String {
let y = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let dateF = DateFormatter()
dateF.dateFormat = "yyyyMMdd"
return dateF.string(from: y)
}
}
// MARK: - View
import UIKit
class MyTableViewCell: UITableViewCell {
@IBOutlet weak var movieName: UILabel!
@IBOutlet weak var audiAccumulate: UILabel!
@IBOutlet weak var audiCount: UILabel!
}
// MARK: - Controller
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var table: UITableView!
var movieData: MovieData?
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
let dateString = MovieService.shared.makeYesterdayString()
MovieService.shared.fetchMovies(for: dateString) { [weak self] data in
guard let self = self else { return }
self.movieData = data
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let dest = segue.destination as? DetailViewController,
let row = table.indexPathForSelectedRow?.row else { return }
dest.movieName = movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm ?? ""
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 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 = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row] else {
return UITableViewCell()
}
cell.movieName.text = "[\(movie.rank)위] \(movie.movieNm)"
let numF = NumberFormatter()
numF.numberStyle = .decimal
if let aCount = Int(movie.audiCnt) {
cell.audiCount.text = "어제: \(numF.string(for: aCount)!)명"
}
if let aAcc = Int(movie.audiAcc) {
cell.audiAccumulate.text = "누적: \(numF.string(for: aAcc)!)명"
}
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "🎬BoxOffice(영화진흥위원회제공: " + MovieService.shared.makeYesterdayString() + ")"
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "made by 박성아"
}
}
// MARK: - DetailViewController
import UIKit
import WebKit
class DetailViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var webView: WKWebView!
var movieName = ""
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = movieName
navigationItem.title = movieName
let query = movieName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let urlStr = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=\(query)"
if let url = URL(string: urlStr) {
webView.load(URLRequest(url: url))
}
}
}
// MARK: - MapViewController
import UIKit
import WebKit
class MapViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlStr = "https://map.naver.com/p/search/영화관"
if let encoded = urlStr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: encoded) {
webView.load(URLRequest(url: url))
}
}
}