프로그래밍/iOS

[iOS] Swift 공부 - 22

Cian 2024. 5. 23. 12:37

▶ 퍼센트 인코딩

URL에서 예약된 문자들을 16진수 값으로 변환하는 방식이다. 예약된 문자란 URL에서 특별한 의미를 갖는 문자들을 말하는데, 예를 들어 ?, /, #, % 등이 있다. 이런 문자들은 URL에서 다른 목적으로 사용되기 때문에 단순 데이터로 전송하려면 인코딩이 필요하다.

인코딩 방식은 %와 그 문자의 ASCII 코드값을 16진수로 표현한 두 자리 숫자를 조합하는 방식인데, 예를 들어 공백 문자는 ASCII 32번이므로 %20으로 인코딩되는 것이다.

하지만 알파벳과 숫자, 그리고 일부 안전한 문자들은 인코딩할 필요가 없는데, 안전한 문자의 예로는 -, _, ., ! 등이 있다.

이런 식으로 퍼센트 인코딩을 적용하면 URL에 모든 데이터를 안전하게 전송할 수 있게 된다. 디코딩 과정에서 서버가 %XX 형식의 16진수 값을 원래 문자로 변환하면 되는 되므로 이렇게 하면 통신 과정에서 데이터가 변형되는 것을 방지할 수 있다.

 

 

▶ segue

iOS 앱 개발에서 뷰 컨트롤러 간의 전환을 관리하는 개념이다.

Storyboard를 사용할 때 segue를 통해 한 뷰에서 다른 뷰로 이동하는 것을 시각적으로 정의할 수 있는데, 예를 들어 테이블 뷰에서 셀을 탭했을 때 상세 뷰로 전환하는 동작을 segue로 설정하는 것이다. Segue에는 여러 가지 전환 스타일이 있는데, Push, Modal, Popover 등이 있다. 각각의 전환 스타일은 화면 전환 애니메이션과 동작 방식이 다르다.

Storyboard 상에서 Control+드래그하여 segue를 생성할 수 있으며, segue에 identifier를 붙여서 코드에서 해당 segue를 참조할 수도 있다. 또한 Segue는 프로그래밍 방식으로도 구현할 수 있는데, 예시로 performSegueWithIdentifier 메서드를 호출하면 특정 segue를 실행할 수 있다. 이 때 prepare(for:sender:) 메서드를 오버라이드해서 전환할 때의 데이터 준비를 할 수 있다. Segue는 화면 전환을 쉽게 관리할 수 있게 해주지만, 한편으론 너무 복잡한 앱에서는 코드가 지저분해질 수 있다는 단점이 있다.

 

 

▶ 실습 코드

 

- ViewController

//
//  ViewController.swift
//  MovieLsw
//
//  Created by Induk-cs on 2024/05/02.
//

import UIKit

let name = ["범죄도시4", "쿵푸팬더4", "스턴트맨", "포켓몬스터: 성도지방 이야기, 최종장", "남은 인생 10년", "파묘", "극장판 실바니안 패밀리 프레야의 선물", "꼬마참새 리차드: 신비한 보석 탐험대", "챌린저스", "고스트버스터즈: 오싹한 뉴욕"]

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=0769158cced81e9cdf789de5f44f46b5&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 mName =
                movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm else { return UITableViewCell() }
        guard let mRank =
                movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].rank else { return UITableViewCell() }
        
        cell.movieName.text = "[\(mRank)위] \(mName)"
        // movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
        cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc
        // cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt
        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.audiCount.text = "어제 : \(result)"
            //cell.audiCount.text = "어제:\(aCnt)명"
        }
        if let aAcc = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc {
            let numF = NumberFormatter()
            numF.numberStyle = .decimal
            let aCount = Int(aAcc)!
            let result = numF.string(for: aCount)!+"명"
            cell.audiAccumulate.text = "누적 : \(result)"
            //cell.audiCount.text = "어제:\(aCnt)명"
        }
        return cell
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let dest = segue.destination as! DetailViewController
        dest.movieName = "Cian"
        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, titleForHeaderInSection section: String) -> String? {
        return "by cian"
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //print(indexPath.description)
    }
    
    
}

 

 

- DetailViewController

import UIKit
import WebKit

class DetailViewController: UIViewController {
    @IBOutlet weak var webView:WKWebView!
    var movieName = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = movieName
        
        let urlKorString = "https://www.youtube.com/results?search_query="+movieName
        
        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)
    }
}

 

 

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

}

 

 

 

▶ 실행화면