API 연속 호출
- 입력된 언어를 감지하고, 해당 언어를 영어로 번역하는 코드를 작성했다
- 먼저 언어 감지 api를 호출해서 결과를 저장한다
- 저장한 결과를 가지고 번역 api를 호출한다. target은 영어로 고정한다
맨 처음
- 단순하게 함수 두개 만들어서 실행시켰다
- 문제는 첫 번째 함수의 결과가 나오기 전에 두 번째 함수가 실행되어버리기 때문에 번역 작업을 할 수가 없었다
그 다음
- 첫 번째 함수의 api 호출에서
case .success
안에다가
두 번째 api 호출을 진행했다
- 정상적으로 기능 구현하는 건 성공했지만, 뭔가 깔끔해 보이지 않았다
func translate() {
guard let txt = firstTextView.text else { return }
let header: HTTPHeaders = [
"X-Naver-Client-Id" : APIKey.naver,
"X-Naver-Client-Secret" : APIKey.naverSecret
]
var langType = ""
let url2 = "https://openapi.naver.com/v1/papago/detectLangs"
let parameter2: Parameters = [
"query" : txt
]
AF.request(url2, method: .post, parameters: parameter2, headers: header)
.validate()
.responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
langType = json["langCode"].stringValue
let url = "https://openapi.naver.com/v1/papago/n2mt"
let parameter: Parameters = [
"source": langType,
"target": "en",
"text": txt
]
AF.request(url, method: .post, parameters: parameter, headers: header)
.validate()
.responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
self.secondTextView.text = json["message"]["result"]["translatedText"].stringValue
case .failure(let error):
print(error)
}
}
case .failure(let error):
print(error)
}
}
}
그 다다음
- 멘토님께 질문드렸을 때, completionHandler 키워드를 알려주셔서,
구글링해서 비슷하게 코드를 작성해보았다
- 훨씬 깔끔해진 것 같다
- 참고1, 참고2
func detectLang(completion: @escaping (String, String) -> Void) {
guard let txt = firstTextView.text else { return }
let header: HTTPHeaders = [
"X-Naver-Client-Id" : APIKey.naver,
"X-Naver-Client-Secret" : APIKey.naverSecret
]
var langType = ""
let url2 = "https://openapi.naver.com/v1/papago/detectLangs"
let parameter2: Parameters = [
"query" : txt
]
AF.request(url2, method: .post, parameters: parameter2, headers: header)
.validate()
.responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
langType = json["langCode"].stringValue
completion(langType, txt)
case .failure(let error):
print(error)
}
}
}
func translate(_ sender: String, _ sender2: String) {
let url = "https://openapi.naver.com/v1/papago/n2mt"
let header: HTTPHeaders = [
"X-Naver-Client-Id" : APIKey.naver,
"X-Naver-Client-Secret" : APIKey.naverSecret
]
let parameter: Parameters = [
"source": sender,
"target": "en",
"text": sender2 ]
AF.request(url, method: .post, parameters: parameter, headers: header)
.validate()
.responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
self.secondTextView.text = json["message"]["result"]["translatedText"].stringValue
case .failure(let error):
print(error)
}
}
}
@IBAction func translateButtonTapped(_ sender: UIButton) {
detectLang(completion: translate)
}
api 보낼 때
- 키나 쿼리에다가 억지로 큰따옴표 붙여놨는지 확인해보자
- 안붙여야 정상적으로 데이터를 받을 수 있었다
- 대량의 데이터와 리소스를 분할해서 가져온다. (주로 서버 데이터&리소스)
- 사용자의 스크롤 시점에 맞춰서 구현한다
종류
- 몇 번째 페이지에서 몇 개의 데이터를 보여줄지 쿼리스트링으로 전달
- 페이지 단위로 정보 요청하는 사이에 서버 데이터 변하면
중복 데이터가 나올 수 있음
- 서버의 데이터 변화가 적은 구조인 경우에 사용한다
- 클라이언트가 가지고 있는 마지막 데이터를 기준으로 다음 데이터 조회
- 사용자가 조회한 시점 이후에 최신 데이터 추가되어도 조회가 어려움
- 현재 페이지 기준 전/후의 데이터를 호출하는 방식이기 때문에
정보 건너뛰고 중간 페이지에 대한 값을 얻기 힘들다
구현 방법
1. TableView willDisplayCell Method
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
- 화면에 보이지 않는 셀에서도 호출될 수 있는 가능성이 있다 -> 권장 x
- 마지막 셀을 스크롤 할 때의 시점을 명확하게 파악하기 어려움
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
- 참고
- 스크롤뷰의 스크롤 상태를 감지할 수 있는 메서드
scrollView.contentSize.height
와
scrollView.content.offSet.Y
가
유사해지는 지점을 노린다
Example
- 처음엔 count를 100으로 설정하고,
scrollView.contentSize.height
와
scrollView.content.offSet.Y
의 차이가 1000까지 줄어들었을 때,
count
를 100 늘려준다
- 근데
scrollView.contentSize.height
는 처음부터 고정일 줄 알았는데,
얘도 스크롤할수록 조금조금씩 늘어나다가 max를 찍는 것 같다
import UIKit
class ScrollTestViewController: UIViewController {
@IBOutlet var mainTableView: UITableView!
var count = 100
override func viewDidLoad() {
super.viewDidLoad()
mainTableView.delegate = self
mainTableView.dataSource = self
}
}
extension ScrollTestViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ScrollTestTableViewCell") as! ScrollTestTableViewCell
cell.mainLabel.text = "\(indexPath.row)"
return cell
}
}
extension ScrollTestViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentSize.height - scrollView.contentOffset.y < 1000) {
print("=============== 카운트 증가!! ===============")
count += 100
mainTableView.reloadData()
}
print(scrollView.contentSize.height, scrollView.contentOffset.y)
}
}
3. UITableViewDataSourcePrefetching Protocol
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [indexPath]) {
}
- 서버 통신과 같은 비동기 상황에서 pagination 쉽게 구현
- 용량이 큰 이미지를 다운받아서 테이블뷰에 보여줄 때 효과적이다
- 셀이 화면에 보이기 전, 필요한 리소스 다운
(cellForRowAt이 호출되기 전에 미리 필요한 데이터 로딩)
- cellForRowAt이 호출되면 prefetchRowAt에서 미리 로딩해둔 리소스와 데이터 표현
- 사용자가 보고있는 셀 이후에 표시될 셀을 판단하고 호출
- 사용자가 스크롤하지 않으면 호출x
- 두 번째 파라미터를 통해 배열로 전달
구조체 배열 돌 때(?)
- for loop을 항상 인덱스로만 사용해왔어서 실수가 좀 있었다
- struct Movie 타입의 배열이 있다고 했을 때, 해당 배열의 원소에 접근하고, 그 원소의 구조체 요소 값을 바꾸려고 할 때는 반드시 인덱스로 접근해야 한다
- 이게 뭔소린지 나도 잘 이해가 안되는데, 코드로 보면 바로 느낌이 온다
- 이거때문에 진짜 돌아버릴 뻔 했당
바보 코드
for item in list {
if (item.title == title) {
item.like.toggle()
}
}
- 이 짓을 아무리 해도 list 배열에 있는 애들은 변화가 없을 것이다.
- 왜냐하면 item 값만 계속 토글하고 있으니까...
- item은 list랑 상관이 없는 친구니까...
인덱스로 접근
for i in 0...list.count-1 {
if list[i].title == title {
list[i].like.toggle()
}
}
- 코드가 조금 정신없긴 하지만, 아직까지 인덱스로 접근하는게 맘 편한건 사실이다..
enumerated()
for (index, item) in list.enumerated() {
if item.title == title {
list[index].like.toggle()
}
}
- 둘 다 쓸 수 있는
enumerated()
메서드가 있다
- 좋다 이거