๊ณต๊ณต๋ฐ์ดํฐ ํฌํธ์์ ํ๊ตญํ๊ฒฝ๊ณต๋จ์์ ์ ๊ณตํ๋ ๋๊ธฐ์ง ์ ๋ณด๋ฅผ ํตํด์ ๊ฐ๋จํ๊ฒ ์ธก์ ์๋ณ ๋๊ธฐ์ง ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ ์ฑ์ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํ๋ค.
๊ตฌํํ๋ ค๋ ์ฑ์ ์ด 3๊ฐ์ ViewController๋ก,
- ์ง์ญ๋ช ์ ๊ฒ์ํ ์ ์๋ ํ์ด์ง
- ํด๋น ์ง์ญ์ ์ธก์ ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ฃผ๋ ํ์ด์ง
- ํน์ ์ธก์ ์์ ๋๊ธฐ์ง์ ์์ธ์ ๋ณด๋ฅผ ๋ณด์ฌ์ฃผ๋ ํ์ด์ง
๋ก ๊ตฌ์ฑ๋์ด ์๋ค.
์ฐ์ ์ง์ญ๋ช ์ ๊ฒ์ํ ์ ์๋ ํ์ด์ง์ธ ViewController๋ถํฐ ์ดํด๋ณด์.
//
// ViewController.swift
// Basic_07
//
// Created by ๊ณ ๋ on 2022/09/14.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var cityNameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tapFindButton(_ sender: UIButton) {
guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: "RegionTableViewController") as? RegionTableViewController else { return }
viewController.city = self.cityNameTextField.text ?? "๋์ "
self.navigationController?.pushViewController(viewController, animated: true)
}
}
push ๋ฐฉ์์ผ๋ก ํ๋ฉด ์ด๋์ ๊ตฌํํ์ผ๋ฉฐ, ๊ฒ์ํ๊ธฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ด๋ํ๋ ํ์ด์ง์ธ RegionTableViewController์ ์ ๋ ฅํ ์ง์ญ๋ช ์ ์ ๋ฌํ๊ธฐ ์ํด ViewController๋ฅผ ์ธ์คํด์คํํ๊ณ ๊ฐ์ ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค.
๋ค์์ ํด๋น ์ง์ญ์ ์ธก์ ์ ๋ชฉ๋ก์ด ์ถ๋ ฅ๋๋ RegionTableViewController์ด๋ค.
//
// RegionTableViewController.swift
// Basic_07
//
// Created by ๊ณ ๋ on 2022/09/14.
//
import UIKit
class RegionTableViewController : UIViewController {
var city: String = ""
var cities: [Item] = []
let apiKey: String = "...์๋ต"
@IBOutlet weak var regionTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.regionTableView.dataSource = self
self.regionTableView.delegate = self
self.getData(city: city)
}
func getData(city: String) {
let session: URLSession = URLSession(configuration: .default)
let addr: String = "https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList?serviceKey=\(apiKey)&returnType=json&numOfRows=100&pageNo=1&addr=\(city)"
// URL๋ก ๋ณํ
guard let url = URL(string: addr) else { return }
debugPrint(url)
session.dataTask(with: url) { data, response, error in
if let error {
print(String(describing: error))
}
if let data = data {
do {
let userResponse = try JSONDecoder().decode(UserResponse.self, from: data)
self.cities = userResponse.response.body.items
DispatchQueue.main.async {
self.regionTableView.reloadData()
}
} catch(let error) {
print(String(describing: error))
}
}
}.resume()
}
}
// Cell ์ ํ ์ ๋์
extension RegionTableViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: "RegionWeatherViewController") else { return }
self.navigationController?.pushViewController(viewController, animated: true)
}
}
// Cell ์ธ๋ถ ์ ๋ณด
extension RegionTableViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.cities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "RegionCell", for:indexPath) as? RegionCell else { return UITableViewCell() }
cell.getData(city: self.cities[indexPath.row])
return cell
}
}
๊ฐ๋จํ๊ฒ ์ค๋ช ํ์๋ฉด ํ๋จ์ extension์ UITableView๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ์ฑํํ Delegate์ DataSource์ด๋ค. ViewController๊ฐ viewDidLoad ๋๋ฉด getData ํจ์๋ฅผ ํธ์ถํ์ฌ API๋ฅผ ํตํด ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋๋ฐ ์ด ๋ถ๋ถ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
String์ URL๋ก ๋ณํํด์ฃผ๋ guard let ๊ตฌ๋ฌธ์์ return์ผ๋ก ๋น ์ง๋ ๊ฒ์ด์๋ค. return์ผ๋ก ์ธํด ํจ์๊ฐ ์ข ๋ฃ๋๋ฉด์ debugPrint ์ดํ๋ก๋ ์คํ๋์ง ์์๋ค.
1. ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก URL์ ์์ฑ
์ฒ์์๋ ๋ฌธ์ ๊ฐ ๋ฌด์์ธ์ง ํ์ ํ ์ ์์๋ค. ๋ณํ๋๊ธฐ ์ addr ๋ณ์๋ฅผ ์ถ๋ ฅํ๊ณ POSTMAN์์ ์ง์ ํธ์ถ์ ๋ดค๋๋ฐ ์ ์์ ์ผ๋ก ํธ์ถ๋๊ณ ์์๋ค. nil์ด ๋ฆฌํด๋๋ฉด์ ์์ฐ์ค๋ return์ผ๋ก ๋น ์ง๋ ๊ฒ์ธ๋ฐ ์์ธ์ ํ์ ํ ์ ์์๋ค... ๐ค
๊ธฐ์กด์ addr ๋์ , ์งง์ ๋ฌธ์์ด์ ๋ฃ์ด๋ดค๋๋ฐ debugPrint ์ดํ ์ฝ๋๋ค์ด ์ ์์ ์ผ๋ก ์คํ๋๋ฉด์ addr์ด ๋๋ฌด ๊ธด ํ์ธ๊ฐ? ์ฟผ๋ฆฌ์ ํ๋ผ๋ฏธํฐ๊น์ง ํ ๋ฒ์ ๋๋ ค๋ฐ์ ํ์ธ๊ฐ์ ๋ํ ๊ณ ๋ฏผ์ ํ๋ค.
๊ทธ๋์ addr์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ๋ค. ์ง๊ธ์ฒ๋ผ String์ ํ ๋ฒ์ URL๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ๊ณผ URLComponents์ URLQueryItem์ ํ์ฉํ์ฌ URL์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ํ์์ ๋ฐฉ๋ฒ์ผ๋ก ๋ฆฌํํ ๋ง์ ์งํํ๋ค.
// ########## ๋ณ๊ฒฝ ์ ##########
let session: URLSession = URLSession(configuration: .default)
let addr: String = "https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList?serviceKey=\(apiKey)&returnType=json&numOfRows=100&pageNo=1&addr=\(city)"
guard let url: URL = domain?.url else { return }
// ########## ๋ณ๊ฒฝ ํ ##########
var baseURL: URLComponents? = URLComponents(string: "https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList")
let returnType: URLQueryItem = URLQueryItem(name: "returnType", value: "json")
let numOfRows: URLQueryItem = URLQueryItem(name: "numOfRows", value: "100")
let pageNo: URLQueryItem = URLQueryItem(name: "pageNo", value: "1")
let addr: URLQueryItem = URLQueryItem(name: "addr", value: city)
let serviceKey: URLQueryItem = URLQueryItem(name: "serviceKey", value: apiKey)
baseURL?.queryItems = [returnType, numOfRows, pageNo, addr, serviceKey]
guard let encodedUrl = String(baseURL?.url) else { return }
ํ์ง๋ง ์ญ์ guard let ๊ตฌ๋ฌธ์์ return์ผ๋ก ๋น ์ง๋ฉด์ ํจ์๊ฐ ์ข ๋ฃ๋๋ค.
2. ์ธ์ฝ๋ฉ ๋ฌธ์ ?
๊ทธ๋ ๊ฒ ์ฝ์ง์ ๊ณ์ํ๋ค๊ฐ ๊ฐ์๊ธฐ ์ธ์ฝ๋ฉ์ด๋ผ๋ ๋จ์ด๊ฐ ๋ ์ฌ๋๋ค. ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ addr์ URL ๊ตฌ์กฐ์ฒด ๋ด๋ถ์ ์ผ๋ก ์ธ์ฝ๋ฉ์ ์งํํ๊ณ ์์ง ์์๊น๋ ์๊ฐ์ผ๋ก ๊ฒ์์ ํด๋ณด์๊ณ ์๋ ๊ณต์ ๋ฌธ์๋ฅผ ์ ํ ์ ์์๋ค.
๋ฌธ์์ด์์ ๋์ฒด๋์ง ์์ ๋ฌธ์๋ฅผ ์ ๋ฌํ์ฌ ์ด๋ฅผ ๋ฐํ์ผ๋ก ์ธ์ฝ๋ฉ์ ์งํํ๋ ํจ์์๋ค. ๊ณต์ ๋ฌธ์์ ๋์์๋ ๊ฒ์ฒ๋ผ urlQueryAllowed์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๊ณ ๋ค์ ํ๋ฒ url์ ์ถ๋ ฅํด๋ณด์๋ค.
guard let encodedStr = addr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
guard let url = URL(string: encodedStr) else { return }
ํ์ง๋ง ๋ณํ์ ๋์ง๋ง API ํธ์ถ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธธ๋ ๋ณํ๋ url์ ๋น๊ตํด๋ดค๋ค.
// ์ ์์ ์ธ url
https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList?serviceKey=DlsG82zYpBjL3dL6XB52XaI9n%2FLX37nb5v%2BjUrn9IxLT%2Fe78qeC6ChO9heFQeJwv%2BpclYR8ux0Q4e1stnKHE2Q%3D%3D&returnType=json&numOfRows=100&pageNo=1&addr=%EB%8C%80%EC%A0%84
// ๋ณํ๋ url
https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList?serviceKey=DlsG82zYpBjL3dL6XB52XaI9n%252FLX37nb5v%252BjUrn9IxLT%252Fe78qeC6ChO9heFQeJwv%252BpclYR8ux0Q4e1stnKHE2Q%253D%253D&returnType=json&numOfRows=100&pageNo=1&addr=%EB%8C%80%EC%A0%84
์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ๋ณด์ด๋๊ฐ? ๋ณํ๋ url์ %๊ฐ ํ๋ฒ ๋ ์ธ์ฝ๋ฉ ๋์ด %25๋ก ๋ณํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฌ์ค ์ด ๋ถ๋ถ์ ๋ํด์๋ ๊ณต์๋ฌธ์์์๋ ๋ค๋ฃจ๊ณ ์์๋ค.
%๋ก ์ธ์ฝ๋ฉ๋ ๋ฌธ์์ด์์ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด %๊ฐ ํ ๋ฒ ๋ ์ธ์ฝ๋ฉ๋๋ฏ๋ก ์ฃผ์ํ๋ผ๋ ๊ฒ์ด์๋ค. ๊ทธ๋์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ธ์ฝ๋ฉ๋์ง ์์ API KEY๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค. ์๋ํ๋ฉด ๊ณต๊ณต๋ฐ์ดํฐ ํฌํธ์์๋ ๋ ๊ฐ์ง ์ข ๋ฅ ํค๋ฅผ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ธฐ์กด์๋ ์์ ์ธ์ฝ๋ฉ๋ API KEY๋ฅผ ์ฌ์ฉํ๋๋ฐ ์ด๋ฅผ ๋์ฝ๋ฉ๋ API KEY๋ก ๋ณ๊ฒฝํ๋ค. ํ์ง๋ง ๋์ฝ๋ฉ๋ API KEY ์ญ์, URL ๋ณํ ๊ณผ์ ์์ ์ธ์ฝ๋ฉ์ด ์ ๋๋ก ๋์ง ์์ ์ญ์ API ํธ์ถ์ด ์ ์์ ์ผ๋ก ์งํ๋์ง ์์๋ค.
๊ฒฐ๊ตญ ๊ฐ๋จํ์ง๋ง ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ ์ ์ฉํ๊ธฐ๋ก ํ๋ค. ๋ฐ๋ก replacingOccurrences๋ฅผ ํ์ฉํ๋ ๊ฒ์ด์๋ค.
let encodedAndReplacedStr = encodedStr.replacingOccurrences(of: "%25", with: "%")
์ด๋ ๊ฒ ํด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐ...! ํ ์ ์๋ ์ค ์์์ง๋ง... ๋ค๋ฅธ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ๐ญ
3. SSH ๋ณด์ ์ค๋ฅ?
์ธ์ฆ์ ๊ด๋ จ ์ค๋ฅ ๊ฐ์๋ฐ ์ ํํ๋ ๋ชจ๋ฅด๊ฒ ๋ค. ์ฐพ์๋ณด๋๊น ๊ตฌ ๋ฒ์ ผ์ Xcode, Swift์์ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ์๋ฐ ๋ Xcode 14, Swift 5์ธ๋ฐ? ๊ทธ๋ฌ๋ ์ค ๋์ ๋น์ทํ ๋ฌธ์ ๋ฅผ ๊ฒช๋ ๋ถ์ ๋ฐ๊ฒฌํ๋ค. ์ด ๋ถ ์ญ์ ๊ณต๊ณต๋ฐ์ดํฐ ํฌํธ์์ ์ ๊ณตํ๋ ๋ค๋ฅธ API๋ฅผ ์ฌ์ฉํ๋ ์ค์ ๋ฐ์ํ ์ค๋ฅ์๋ค.
์ ๋ธ๋ก๊ทธ์์ ๋์์๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ์ง๋ง ๋ชจ๋ ์ฃผ์์ ๋ํด ํ์ฉ์ ํ๊ธฐ์ ๋ณด์์ ์ผ๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ฐ์ ์์ ๋ฐฉํธ์ผ๋ก ์ด๋ ๊ฒ ํด๊ฒฐํ๋ค. ์ถ์ธก์ผ๋ก๋ ๋๋ฉ์ธ ํํ์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ผ๊ณ ์๊ฐํ๋ค. (์๊ฐ์ ๊ฐ๊ณ ์ข ๋ ์ฐพ์๋ด์ผ๊ฒ ๋ค.)
์ต์ข ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
//
// RegionTableViewController.swift
// Basic_07
//
// Created by ๊ณ ๋ on 2022/09/14.
//
import UIKit
class RegionTableViewController : UIViewController {
var cities: [Item] = []
var city: String = ""
let apiKey: String = "...์๋ต"
@IBOutlet weak var regionTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.regionTableView.dataSource = self
self.regionTableView.delegate = self
self.getData(city: city)
}
func getData(city: String) {
let session: URLSession = URLSession(configuration: .default)
let addr: String = "https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList?serviceKey=\(apiKey)&returnType=json&numOfRows=100&pageNo=1&addr=\(city)"
guard let encodedStr = addr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
let encodedAndReplacingStr = encodedStr.replacingOccurrences(of: "%25", with: "%")
guard let url = URL(string: encodedAndReplacingStr) else { return }
session.dataTask(with: url) { data, response, error in
if let error {
print(String(describing: error))
}
if let data = data {
do {
let userResponse = try JSONDecoder().decode(UserResponse.self, from: data)
self.cities = userResponse.response.body.items
DispatchQueue.main.async {
self.regionTableView.reloadData()
}
} catch(let error) {
print(String(describing: error))
}
}
}.resume()
}
}
// Cell ์ ํ ์ ๋์
extension RegionTableViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: "RegionWeatherViewController") else { return }
self.navigationController?.pushViewController(viewController, animated: true)
}
}
// Cell ์ธ๋ถ ์ ๋ณด
extension RegionTableViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.cities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "RegionCell", for:indexPath) as? RegionCell else { return UITableViewCell() }
cell.getData(city: self.cities[indexPath.row])
return cell
}
}
4. ํด๊ฒฐ!
์ฐ์ ๋ด๊ฐ ์ํ๋ ๋๋ก ์ฑ์ด ์๋ํ๊ธฐ ์์ํ๋ค. ๋ช ๊ฐ์ง ์ข ๋ ์ฐพ์๋ด์ผ๊ฒ ์ง๋ง ๊ทธ๋๋ ํด๊ฒฐํด์ ๋ฟ๋ฏํ๋ค. ์ค๋ ์ฝ์ง์ ํตํด์ ์๊ฒ๋ ๊ฒ์ ๋ฌธ์์ด์ %๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์์ฑ์์ธ URL(string: )์ผ๋ก๋ URL์ ์์ฑํ ์ ์๊ณ , .addPercentEncoding์ ํตํด URL์ ์์ฑํด์ผํ๋ค๋ ๊ฒ์ด๋ค.
// ########## 1. nil์ด return ##########
let baseURL: String = "https://www.naver.com%"
guard let encodedURL: URL = URL(string: baseURL) else { return }
debugPrint(encodedURL)
// ########## 2. ์ ์์ ์ผ๋ก ๋ฌธ์์ด์ด return ##########
let baseURL: String = "https://www.naver.com%"
guard let baseAndPercentEncodingURL = baseURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
guard let encodedURL: URL = URL(string: baseAndPercentEncodingURL) else { return }
print(encodedURL)
๋ํ ์ ๋ฐฉ์๋๋ก ์งํํ๋ฉด %๊ฐ ํ๋ฒ ๋ ์ธ์ฝ๋ฉ ๋์ด %25๊ฐ ๋๋ค๋ ๊ฒ์ด๋ค.
์ฌ์ค ์ด๋ฒ์ ์ฌ์ฉํ ๊ณต๊ณต๋ฐ์ดํฐ API๋ ์ ์ ์๋๋ก์ด๋ ๊ฐ๋ฐ ๋น์์ ์ฌ์ฉํด๋ณธ ๊ฒฝํ์ด ์๋ API์๋ค. ์ฌ์ฉ ๊ฒฝํ์ด ์์๊ธฐ์ ์ด๋ฒ์ ์ ์ฉํ ๊ฒ์ด์๋๋ฐ ๋น์์๋ ๋ฌธ์ ์์ด ์ ์์ ์ผ๋ก ํธ์ถ๋์๋ค. iOS๊ฐ ํ์คํ ๋ณด์์ ์ผ๋ก ๋ ๊น๋ค๋ก์ด ๊ฒ ๊ฐ๋ค. ๊ด๋ จํด์ ์ข ๋ ์ฐพ์๋ด์ผ๊ฒ ๋ค.
๐ ์ฐธ๊ณ