iOS 프로그래밍 실무 12주차

2025. 5. 21. 16:42📱 모바일 프로그래밍/iOS 프로그래밍 실무

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