평범한 컴공 대학생의 공부일지

영화 순위 Application 만들기 (2) 본문

iOS Swift

영화 순위 Application 만들기 (2)

Taram 2024. 5. 23. 12:52

※이 iOS 카테고리의 글 들은 학교 강의와 과제를 기반으로 작성한 것입니다.※


이번 게시글에서는 이전 게시글에서 다룬 영화 순위 Application 작업을 더 진행해 보겠습니다.


1) View Controller 코드 추가
    - prepare 메서드를 활용하여 DetailViewController의 movieName과 연결
    - ViewController는 UIViewController라는 부모 클래스를 사용하고 DetailViewController도 동일한 부모 클래스 사용 중
       이를 위해 as! as?를 사용하여 다운 캐스팅 후 사용 -> 현재 코드에서는 as! 를 사용하고 있지만 guard let ~ as?를 사용하는 것이
        더욱 안전합니다.

import UIKit

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 ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView!
    var movieData : MovieData?
    var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt="
    
    override func viewDidLoad() {
        super.viewDidLoad()
        table.dataSource = self
        table.delegate = self
        movieURL += makeYesterDayString()
        getData()
    }
    func makeYesterDayString() -> String {
        let calendar = Calendar.current
        if let yesterday = calendar.date(byAdding: .day, value: -1, to: Date()) {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "YYYYMMdd"
            return dateFormatter.string(from: yesterday)
        }
        return "날짜 변환에 실패하였습니다."
    }
    
    func getData(){
        guard let url = URL(string: movieURL) else {return}
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil { print(error!); return}
            guard let JSONdata = data else { return }
            print(JSONdata)
            // let dataString = String(data: JSONdata, encoding: .utf8)
            // print(dataString!)
            let decoder = JSONDecoder()
            do{
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                //print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                //print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
                self.movieData = decodedData
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
            } catch{
                print(error)
            }
            
        }
        task.resume()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        guard let mRank = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].rank else {return UITableViewCell()}
        guard let mName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm else {return UITableViewCell()}
        cell.movieName.text = "[\(mRank)위 \(mName)]"
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aCount = Int(aCnt)!
            let result = numF.string(for: aCount)!+"명"
            cell.audiAccumulate.text = "어제 : \(result)"
        }
        if let aAcc = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aAcc1 = Int(aAcc)!
            let result = numF.string(for: aAcc1)!+"명"
            cell.audiCount.text = "누적 : \(result)"
        }
        //print(indexPath.description)
        return cell
    }
    
    //DetailViewController에 선택한 영화의 제목이 출력되도록 설정
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let dest = segue.destination as! DetailViewController
        let myIndexPath = table.indexPathForSelectedRow!
        let row = myIndexPath.row
        dest.movieName=(movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)!
        
    }
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "🍿박스오피스(영화진흥위원회제공:"+makeYesterDayString()+")🍿"
    }
    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "by Taram"
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //print(indexPath.description)
    }
    
}

 

2) DetailViewController Label 연결

import UIKit

class DetailViewController: UIViewController {
	// test코드
    @IBOutlet weak var nameLabel: UILabel!
    // test 코드 mycell에서 가져와 잘 출력되는지만 확인
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        nameLabel.text = movieName
    }
}

 

3) SegueWay 연결 구조

SegueWay 연결 구조

4) URL을 Optional로 지정 안할 시 오류 발생
     - URL의 주소가 유효하지 않을 경우 URL 생성이 항상 성공하는 것이 아니기 때문입니다.
        URL(string:) 생성자는 주어진 문자열이 유효한 URL 형식이 아닐 경우에 nil을 반환할 수 있습니다.
         따라서, 안전하게 URL 객체를 생성하려면 옵셔널로 처리하여 실패할 가능성을 고려하기 위해 Optional로 지정해 줍니다.

Optional 오류 발생

import UIKit
import WebKit

class DetailViewController: UIViewController {
    
    //WKWebView는 다른 Framework에 존재하여 따로 import를 해주어야 함
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        //WebView 테스트 코드
        //유효하지 않은 URL 주소를 처리하기 위해 guard let으로 처리
        guard let url = URL(string: "https://m.naver.com") else { return }
        let request = URLRequest(url:url)
        webView.load(request)
    }
}


5) % 인코딩

뤼튼에게 물어본 결과

import UIKit
import WebKit

class DetailViewController: UIViewController {
    
    //WKWebView는 다른 Framework에 존재하여 따로 import를 해주어야 함
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        
        //기존 한글이나 일본어 등은 Unicode형식으로 처리
        //한글은 UTF-8의 방식으로 맥에서는 3바이트로 처리하기 때문에 %인코딩을 사용
        //query뒤 %EC%98%81%ED%99%94%EC%88%9C%EC%9C%84 = 영화순위
    //https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84
        
        guard let url = URL(string: "https://m.naver.com") else { return }
        let request = URLRequest(url:url)
        webView.load(request)
    }
}

 

6) 역 persent(%) 인코딩

import UIKit
import WebKit

class DetailViewController: UIViewController {
    
    //WKWebView는 다른 Framework에 존재하여 따로 import를 해주어야 함
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        
        //기존 한글이나 일본어 등은 Unicode형식으로 처리
        //한글은 UTF-8의 방식으로 맥에서는 3바이트로 처리하기 때문에 %인코딩을 사용
        //query뒤 %EC%98%81%ED%99%94%EC%88%9C%EC%9C%84 = 영화순위
        //Chrome브라우저에서 URL을 가져올 경우
    //https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84
        
        //safari 브라우저에서 가져올 경우 한글로 잘 가져옴
        //한글이 들어간 url을 %인코딩을 해주어야 url을 인식할 수 있음
    let urlKorString = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=영화+순위"
        //url persent(%)인코딩
        let urlString= urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        guard let url = URL(string: urlString) else { return }
        let request = URLRequest(url:url)
        webView.load(request)
    }
}

 

  6-1) safari 브라우저에서 한글 URL로 가져올 수 있는 이유

 

7) 나무위키, 네이버, 유튜브 연결하기

     - DetailViewController에 작업
     - 각 나무위키, 네이버, 유튜브 연결이 손쉽게 가능
     - WebView를 사용했기 때문에 동영상도 시청 가능합니다.

import UIKit
import WebKit

class DetailViewController: UIViewController {
    
    //WKWebView는 다른 Framework에 존재하여 따로 import를 해주어야 함
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        
        //기존 한글이나 일본어 등은 Unicode형식으로 처리
        //한글은 UTF-8의 방식으로 맥에서는 3바이트로 처리하기 때문에 %인코딩을 사용
        //query뒤 %EC%98%81%ED%99%94%EC%88%9C%EC%9C%84 = 영화순위
        //Chrome브라우저에서 URL을 가져올 경우
    //https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84
        
        //safari 브라우저에서 가져올 경우 한글로 잘 가져옴
        //한글이 들어간 url을 %인코딩을 해주어야 url을 인식할 수 있음
        //query 뒤에는 검색 옵션이 붙기 때문에 movieName을 입력하면 클릭한 영화의 정보로 이동할 수 있음
        //let urlKorString = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query="+movieName
        
        //아래와 같이 나무위키 URL도 사용 가능
        //let urlKorString = "https://namu.wiki/w/"+movieName
        
        //유튜브도 가능
        let urlKorString = "https://www.youtube.com/results?search_query="+movieName
        
        //url persent(%)인코딩
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        guard let url = URL(string: urlString) else { return }
        let request = URLRequest(url:url)
        webView.load(request)
    }
}

실행 화면

8) Tab bar Controller 추가

 

9) 영화관 지도 ViewController 추가
      - ctrl 드래그 -> 새로운 ViewController에 연결 -> SequeWay 생성
      - MapViewController swift파일 생성 후 연결
      - 새로운 ViewController의 이름은 theather로 설정

 

9-1) Scene 구조

9-2) MapViewController의 코드
         - 위에서 작성한 DetailViewController의 구조와 같은 코드

import UIKit
import WebKit

class MapViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!
    override func viewDidLoad() {
        super.viewDidLoad()
        //네이버 지도에서 영화관 검색 시 주변 영화관을 보여주는 URL로 설정
        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)
    }

}

 

9-3) theatherScene 실행 화면

 

10) Label의 Radius 조정

 

11) App Icon 설정

     - App Icon은 1024x1024 사이즈로 조정

 

12) LaunchScreen(로딩 화면) 작업

13) 제작 결과
      - App Icon이 추가된 것을 확인

App Icon 확인

      - LaunchScreen(로딩 화면) 확인

로딩 화면

      - 메인 화면
        Tab Bar Controller의 2가지의 탭 -> BoxOffice : 영화 순위 및 누적, 어제 관객수 출력
                                                                   -> 영화 제목 클릭 시 해당 영화의 유튜브 검색으로 이동
                                                                         -> Back 버튼 클릭 시 이전 화면으로 변경

                                                            -> theather : 주변 영화관이 무엇이 있는지 출력
          

메인 화면 1
영화 제목 클릭 시 해당 영화 유튜브로 넘어감

         -> theather 화면

theather 화면

14) 각 코드 공유 및 Scene 구조

- ViewController

import UIKit

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 ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView!
    var movieData : MovieData?
    var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt="
    
    override func viewDidLoad() {
        super.viewDidLoad()
        table.dataSource = self
        table.delegate = self
        movieURL += makeYesterDayString()
        getData()
    }
    func makeYesterDayString() -> String {
        let calendar = Calendar.current
        if let yesterday = calendar.date(byAdding: .day, value: -1, to: Date()) {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "YYYYMMdd"
            return dateFormatter.string(from: yesterday)
        }
        return "날짜 변환에 실패하였습니다."
    }
    
    func getData(){
        guard let url = URL(string: movieURL) else {return}
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error != nil { print(error!); return}
            guard let JSONdata = data else { return }
            print(JSONdata)
            // let dataString = String(data: JSONdata, encoding: .utf8)
            // print(dataString!)
            let decoder = JSONDecoder()
            do{
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                //print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
                //print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audiAcc)
                self.movieData = decodedData
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
            } catch{
                print(error)
            }
            
        }
        task.resume()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        guard let mRank = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].rank else {return UITableViewCell()}
        guard let mName = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm else {return UITableViewCell()}
        cell.movieName.text = "[\(mRank)위 \(mName)]"
        if let aCnt = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aCount = Int(aCnt)!
            let result = numF.string(for: aCount)!+"명"
            cell.audiAccumulate.text = "어제 : \(result)"
        }
        if let aAcc = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aAcc1 = Int(aAcc)!
            let result = numF.string(for: aAcc1)!+"명"
            cell.audiCount.text = "누적 : \(result)"
        }
        //print(indexPath.description)
        return cell
    }
    
    //DetailViewController에 선택한 영화의 제목이 출력되도록 설정
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let dest = segue.destination as! DetailViewController
        let myIndexPath = table.indexPathForSelectedRow!
        let row = myIndexPath.row
        dest.movieName=(movieData?.boxOfficeResult.dailyBoxOfficeList[row].movieNm)!
        
    }
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "🍿박스오피스(영화진흥위원회제공:"+makeYesterDayString()+")🍿"
    }
    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "by Taram"
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //print(indexPath.description)
    }
    
}

 

- MyTableViewCell

import UIKit

class MyTableViewCell: UITableViewCell {

    @IBOutlet weak var movieName: UILabel!
    @IBOutlet weak var audiAccumulate: UILabel!
    @IBOutlet weak var audiCount: UILabel!
    override func awakeFromNib() {
        
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

 

- DetailViewController

import UIKit
import WebKit

class DetailViewController: UIViewController {
    
    //WKWebView는 다른 Framework에 존재하여 따로 import를 해주어야 함
    @IBOutlet weak var webView: WKWebView!
    var movieName = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        
        //기존 한글이나 일본어 등은 Unicode형식으로 처리
        //한글은 UTF-8의 방식으로 맥에서는 3바이트로 처리하기 때문에 %인코딩을 사용
        //query뒤 %EC%98%81%ED%99%94%EC%88%9C%EC%9C%84 = 영화순위
        //Chrome브라우저에서 URL을 가져올 경우
    //https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84
        
        //safari 브라우저에서 가져올 경우 한글로 잘 가져옴
        //한글이 들어간 url을 %인코딩을 해주어야 url을 인식할 수 있음
        //query 뒤에는 검색 옵션이 붙기 때문에 movieName을 입력하면 클릭한 영화의 정보로 이동할 수 있음
        //let urlKorString = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query="+movieName
        
        //아래와 같이 나무위키 URL도 사용 가능
        //let urlKorString = "https://namu.wiki/w/"+movieName
        
        //유튜브도 가능
        let urlKorString = "https://www.youtube.com/results?search_query="+movieName
        
        //url persent(%)인코딩
        let urlString = urlKorString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        guard let url = URL(string: urlString) else { return }
        let request = URLRequest(url:url)
        webView.load(request)
    }
}

 

- MapViewController

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

}

 

- 전체 구조


이번 박스오피스 영화 순위 애플리케이션을 만들어보며 실 생활에서 자주 사용되는 애플리케이션의 구조를 이해하는데 큰 도움이 되었습니다. LaunchScreen, SegueWay, Webkit View, Tab Bar Controller 등을 사용하면서 계속 머릿속에 제가 자주 사용하는 앱의 구조를 생각하게 되었습니다. 이처럼 배워나가면서 실 생활과 이어지는 부분을 떠올리며 코딩을 하니 더욱 재밌게 느껴진 하루였습니다.
감사합니다 :)