[iOS] Swift 공부 - 22
▶ 퍼센트 인코딩
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
}
}
▶ 실행화면