나의 발자취
[UIKit] Alamofire, Azure AI Translate API 이용해서 간단 번역 기능 앱 만들기 (1) 본문
거창한건 아니고 그냥 기능 구현 해보는 정도의 수준으로 단기간 안에 끝나는 것이다.ㅎ
어제 한시간동안 하고 자긴 했는데 너무 졸렸음..
뷰 구성
단일 뷰로 해서 UITableView를 사용하여 구성해줄 것이다.
Make Sure That
Table View Cell Identifier = 'cell'이든 자기맘대로 설정
모델 구현 (Models.swift)
지금 Postman에서 내가 사용할 번역기의 JSON input/output 데이터 구조를 살펴보자.
1. 이 아이를 넣으면,
이런 식으로 반환이 될 것이다.
따라서 우리가 만드는 Swift Model 구조는 이렇게 될것이다.
2. 그리고 아웃풋 중 "translations" 안쪽 { } 부분을 보면
똑같이 이렇게 구성하면 된다.
3. 그리고 위에 방금 다룬 output의 상위 구조를 보면 방금 위에 짠 Translation 구조체 데이터는 "translations"라고 하는 [ ] <꺾은 대괄호에 싸여있는 구조다.
따라서 [] 대괄호 안에 방금 위의 구조체 Translation 을 담아준다.
import Foundation
// user input
struct Text: Codable {
let text: String
}
// translated output
struct Translation: Codable {
let text: String
let to: String
}
// root output
struct Document: Codable {
let translations: [Translation]
}
API endpoint 및 필수값 변수 선언
API 호출 시 필요한 필수값들과 함께 endpoint, apiKey를 넣어줄것이다.
func searchBarSearchButtonClicked
ViewDidLoad() 밑에 익스텐션으로 구성해준다.
body variable?
를 보면, 유저가 body에 담아서 보내는 JSON 객체는 우리가 만든 model.swift에서 "Text 구조체 안의 text 변수"들의 모음으로 되어있으며, 딕셔너리(below right capture on line 1, 9) 안에 담겨져 있는 것을 알 수 있다.
따라서 let body = [Text(text: text)] 로 body 변수를 정의를 해줄 수 있다.
extension MainTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else { return }
let strURL = "\(endpoint)&to=\(targetLanguage)"
let body = [Text(text: text)]
}
}
let body = [Text(text: text)] 는 아래 세 줄과 같다.
var body: [Text] = []
let txt = Text(text: text)
body.append(txt)
추가로 text를 더 받는 경우에는 위 코드를 써주면 된다.
AF를 이용해서 body를 JSON 객체로 보내기
1. url 구조 짜기
일단 앞서 url 구조를 짜줄 것이다. 위에 이어서 코드를 작성한다.
// url structure 짜기
guard let url = URL(string: strURL) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
URL()은 옵셔널이라 guard문으로 옵셔널 언래핑을 해주고,
request에는 헤더 옵션도 넣어서 요청을 해야하므로 let 이 아닌 var로 해준다.
2. body JSONify
그다음, body를 인코딩해서 JSONify하는 작업을 해준다.
이 때는 앞에 try! 구문을 적어주면 된다.
더 간결하게 let json 이라는 새로운 변수 대신 요청할 때의 기본 Instance Property인 .httpBody를 사용해준다.
// json encoding for body
request.httpBody = try! JSONEncoder().encode(body)
3. header 작업
마지막으로 header를 위한 key-value값을 넣어준다.
// add key value for header
request.addValue(subscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key")
request.addValue(region, forHTTPHeaderField: "Ocp-Apim-Subscription-Region")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
중요 ⭐️
Alamofire request
Alamofire에서 제공하는 두 개의 생성자를 써서, 총 두 가지 방법을 써볼것이다.
1) AF.responseDecodable()
2) AF.request()
첫번째 방법: AF.responseDecodable()
위에 import Alamofire 선언은 필수. 그래야 인스턴스 속성들이 잘 뜬다.
.responseDecodable()을 Opt+Return을 쳐서 기본 인자값들을 살펴보자.
우선 of: 인자에 (Decodable & Sendable).Type 인 것을 알 수 있다.
이 두 개의 의미는 무엇인가??
(Decodable & Sendable). 여기에 오는 인자값은 두 개의 프로토콜(Decodable, Sendable)을 따라야 한다는 것이다. (Sendable 프로토콜을 따르지 않아도 오류는 나지는 않는다.)
그리고 Type.
Type. 이다.
그 말은? "타입"이 와야한다는 것이다.
따라서, 일단 (Decodable & Sendable) 먼저 처리를 해주기 위해, responseDecodable()이었으므로 response로 오는 데이터(구조체)들의 프로토콜에 Sendable을 따르도록 추가해준다.
Model.swift로 가서 Sendable 프로토콜 추가
그리고, 다시 돌아와서 response JSON 데이터 구조를 보자.
{"translations":...} 는 지금 우리가 Model.swift에서 만든 구조체의 document다.
그런데 이들은 지금, line 1을 보면 [ ] 이렇게 Array 안에 담겨있다. (여러 개가 올 수 있다는 뜻) -> [Document]로 받아야한다.
또한, 위에서, 분명히 (Decodable & Sendable).Type 이라고 했다. "타입" 이 와야한다고 했다. -> .self 로 타입형을 만들어준다.
따라서 아래와 같이 받을 수 있다.
처리를 해주기 위해 위에 translations 변수 선언
case 나누기
.success 케이스에서, (let documents)를 보면
document들이 response 객체로 반환되므로 document"s"로 선언을 해주어야 한다는 점. <중요
AF.request(request).responseDecodable(of: [Document].self) { response in
switch response.result {
case .success(let documents):
self.translations = documents.first?.translations
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print(error.localizedDescription)
}
}
테이블뷰 셀 속성 설정하기
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return translations?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
guard let translation = translations?[indexPath.row] else { return cell }
var config = cell.defaultContentConfiguration()
config.text = "\(translation.to) : \(translation.text)"
cell.contentConfiguration = config
return cell
}
최종 코드
//
// MainTableViewController.swift
// AI Translate
//
// Created by Lia An on 11/5/24.
//
import UIKit
import Alamofire
class MainTableViewController: UITableViewController {
let endpoint = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0"
let subscriptionKey = 구독키
let region = "eastus"
let targetLanguage = "en,ja,fr,zh,es"
var translations: [Translation]?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return translations?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
guard let translation = translations?[indexPath.row] else { return cell }
var config = cell.defaultContentConfiguration()
config.text = "\(translation.to) : \(translation.text)"
cell.contentConfiguration = config
return cell
}
}
extension MainTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else { return }
let strURL = "\(endpoint)&to=\(targetLanguage)"
let body = [Text(text: text)]
// url structure 짜기
guard let url = URL(string: strURL) else { return }
var request = URLRequest(url: url)
// POST request
request.httpMethod = "POST"
// json encoding for body
request.httpBody = try! JSONEncoder().encode(body)
// add key value for header
request.addValue(subscriptionKey, forHTTPHeaderField: "Ocp-Apim-Subscription-Key")
request.addValue(region, forHTTPHeaderField: "Ocp-Apim-Subscription-Region")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// Alamofire request
AF.request(request).responseDecodable(of: [Document].self) { response in // response body 구조
switch response.result {
case .success(let documents): // 변수 설정 document's'
self.translations = documents.first?.translations
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
시뮬레이션으로 결과를 확인해본다!
두번째 방법: AF.request()
header
header부터 달라진다.
헤더를 좀 더 간편하게 선언해준다.
// headers
let headers: HTTPHeaders = [
"Ocp-Apim-Subscription-Key": subscriptionKey,
"Ocp-Apim-Subscription-Region": region,
"Content-Type": "application/json"
]
그리고 AF.request()의 기본 생성자가 어떤 인자값들을 받는지 살펴본다.
alamo라는 변수에 담았다. 훨씬 더 간결하다.
let alamo = AF.request(strURL, method: .post, parameters: body, encoder: JSONParameterEncoder.default, headers: headers)
이어서 alamo에 접근해서 responseDecodable()로 아까와 같은 switch문을 그대로 재사용한다.
alamo.responseDecodable(of: [Document].self) { response in
switch response.result {
case .success(let documents): // 변수 설정 document's'
self.translations = documents.first?.translations
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print(error.localizedDescription)
}
}
최종 코드
//
// MainTableViewController.swift
// AI Translate
//
// Created by Lia An on 11/5/24.
//
import UIKit
import Alamofire
class MainTableViewController: UITableViewController {
let endpoint = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0"
let subscriptionKey = 구독키
let region = "eastus"
let targetLanguage = "en,ja,fr,zh,es"
var translations: [Translation]?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return translations?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
guard let translation = translations?[indexPath.row] else { return cell }
var config = cell.defaultContentConfiguration()
config.text = "\(translation.to) : \(translation.text)"
cell.contentConfiguration = config
return cell
}
}
extension MainTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else { return }
let strURL = "\(endpoint)&to=\(targetLanguage)"
let body = [Text(text: text)]
// headers
let headers: HTTPHeaders = [
"Ocp-Apim-Subscription-Key": subscriptionKey,
"Ocp-Apim-Subscription-Region": region,
"Content-Type": "application/json"
]
// Alamofire request
let alamo = AF.request(strURL, method: .post, parameters: body, encoder: JSONParameterEncoder.default, headers: headers)
alamo.responseDecodable(of: [Document].self) { response in
switch response.result {
case .success(let documents): // 변수 설정 document's'
self.translations = documents.first?.translations
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
'앱 개발 > iOS' 카테고리의 다른 글
[FE] 당근마켓 아니고 양파마켓 만들기 (!!!!!!WIP!!!!!!!) (0) | 2024.11.06 |
---|---|
[UIKit] Alamofire, Azure AI Translate API 이용해서 간단 번역 기능 앱 만들기 (2) (3) | 2024.11.05 |
[XCode] Debug Type Indicator (0) | 2024.11.01 |
[iOS] 우아콘 2024 집중형 멘토링세션 후기 (3) | 2024.10.30 |
[SwiftUI] Apple 공식 SwiftUI Tutorials: Landmarks 프로젝트 클론 (0) | 2024.10.30 |