Kakao Book Search


import UIKit
import SwiftyJSON
import Alamofire
import Kingfisher
    
    
struct Book {
    let authors: String
    let datetime: String
    let price: Int
    let publisher: String
    let salePrice: Int
    let thumbnail: String
    let title: String
    
    var content1: String {  
        return "\(title)"
    }
    var content2: String {  
        return "\(authors) | \(publisher)"
    }
    var content3: String {  
        return datetime
    }
    var content4: String {  
        
        if (salePrice == -1 ) {
            return "절판"
        }
        else {
            let dcPercent = lround( ( (Double(price) - Double(salePrice) ) / Double(price) ) * 100)
            return "\(price) -> \(salePrice) ( \(dcPercent) % 할인 )"
        }
    }
}
enum sorting: String {
    case accuracy
    case latest
    
    var forMenu: String {
        switch self {
        case .accuracy : return "정확도순"
        case .latest : return "발간일순"
        }
    }
}
enum targeting: String {
    case all
    case title
    case publisher
    case person
    
    var forMenu: String {
        switch self {
        case .all : return "전체"
        case .title : return "제목"
        case .publisher : return "출판사"
        case .person : return "인명"
        }
    }
}
class KakaoBookViewController: UIViewController {
    @IBOutlet var searchBar: UISearchBar!
    @IBOutlet var bookTableView: UITableView!
    
    @IBOutlet var targetPullDownButton: UIButton!
    @IBOutlet var sortPullDownButton: UIButton!
    
    
    var bookList: [Book] = []   
    var pageCnt = 1             
    var isEnd = false           
    
    var sortingOption = sorting.accuracy
    var targetingOption = targeting.all
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchBar.delegate = self
        bookTableView.dataSource = self
        bookTableView.delegate = self
        bookTableView.prefetchDataSource = self
        
        bookTableView.rowHeight = 120
        
        designSorting(sortPullDownButton)
        designTargeting(targetPullDownButton)
    }
    
    
    
    func callRequest(_ query: String, _ pageCnt: Int) {
        
        
        guard let txt = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
        
        
        var target = ""
        if (targetingOption != .all) {
            target = "&target=\(targetingOption.rawValue)"
        }
        
        
        let sort = "&sort=\(sortingOption.rawValue)"
        
        
        let url = "https://dapi.kakao.com/v3/search/book?query=\(txt)&size=10&page=\(pageCnt)\(target)\(sort)"
        
        
        let header: HTTPHeaders = ["Authorization" : APIKey.kakao]
        
        
        AF.request(url, method: .get, headers: header)
            .validate(statusCode: 200...500)    
            .responseJSON { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                let statusCode = response.response?.statusCode ?? 500
                
                
                if (statusCode == 200) {
                    self.isEnd = json["meta"]["is_end"].boolValue
                    
                    for book in json["documents"].arrayValue {
                        let authors = book["authors"][0].stringValue
                        let datetime = book["datetime"].stringValue
                        let price = book["price"].intValue
                        let publisher = book["publisher"].stringValue
                        let salePrice = book["sale_price"].intValue
                        let thumbnail = book["thumbnail"].stringValue
                        let title = book["title"].stringValue
                        
                        let newBook = Book(authors: authors, datetime: datetime, price: price, publisher: publisher, salePrice: salePrice, thumbnail: thumbnail, title: title)
                        
                        self.bookList.append(newBook)
                    }
                    
                    self.bookTableView.reloadData();
                }
                
                else {
                    print("에러났어용")
                    print(json)
                }
            case .failure(let error):
                print(error)
            }
        }
    }
    
    
    
    
        
        
        
        
        
    
    func designSorting(_ sender: UIButton) {
        sender.setTitle(sortingOption.forMenu, for: .normal)
        
        let op1 = UIAction(title: sorting.accuracy.forMenu) { _ in
            self.sortingOption = sorting.accuracy
            sender.setTitle(self.sortingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        let op2 = UIAction(title: sorting.latest.forMenu) { _ in
            self.sortingOption = sorting.latest
            sender.setTitle(self.sortingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        
        let buttonMenu = UIMenu(title: "정렬 기준을 고르세요", children: [op1, op2])
        sender.menu = buttonMenu
    }
    func designTargeting(_ sender: UIButton) {
        sender.setTitle(targetingOption.forMenu, for: .normal)
        
        let op1 = UIAction(title: targeting.all.forMenu) { _ in
            self.targetingOption = targeting.all
            sender.setTitle(self.targetingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        let op2 = UIAction(title: targeting.title.forMenu) { _ in
            self.targetingOption = targeting.title
            sender.setTitle(self.targetingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        let op3 = UIAction(title: targeting.publisher.forMenu) { _ in
            self.targetingOption = targeting.publisher
            sender.setTitle(self.targetingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        let op4 = UIAction(title: targeting.person.forMenu) { _ in
            self.targetingOption = targeting.person
            sender.setTitle(self.targetingOption.forMenu, for: .normal)
            self.pageCnt = 1
            self.bookList.removeAll()
            self.callRequest(self.searchBar.text!, self.pageCnt)
        }
        
        let buttonMenu = UIMenu(title: "검색 필드를 고르세요", children: [op1, op2, op3, op4])
        sender.menu = buttonMenu
    }
}
extension KakaoBookViewController: UITableViewDelegate, UITableViewDataSource, UITableViewDataSourcePrefetching {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return bookList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "KakaoBookTableViewCell") as! KakaoBookTableViewCell
        
        
        if let url = URL(string: bookList[indexPath.row].thumbnail) {
            cell.mainImageView.kf.setImage(with: url)
        }
        cell.firstLabel.text = bookList[indexPath.row].content1
        cell.secondLabel.text = bookList[indexPath.row].content2
        cell.thirdLabel.text = bookList[indexPath.row].content3
        cell.fourthLabel.text = bookList[indexPath.row].content4
        
        return cell
    }
    
    
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        print("===== 프리패치 \(indexPaths) =====")
        
        for indexPath in indexPaths {
            if (bookList.count-1 == indexPath.row &&
                pageCnt < 15 &&
                !isEnd ) {
                pageCnt += 1
                callRequest(searchBar.text!, pageCnt)
            }
        }
    }
    
    
    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        print("===== 취소여 \(indexPaths) =====")
    }
}
extension KakaoBookViewController: UISearchBarDelegate {
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        pageCnt = 1
        bookList.removeAll()
        
        guard let query = searchBar.text else {return }
        
        
        if query == "" {
            bookTableView.reloadData()
        }
        else {
            callRequest(searchBar.text!, pageCnt)
        }
    }
}
Papago Translator

import UIKit
import SwiftyJSON
import Alamofire
class Translate2ViewController: UIViewController {
    
    
    @IBOutlet var firstTextField: UITextField!
    @IBOutlet var secondTextField: UITextField!
    @IBOutlet var firstTextView: UITextView!
    @IBOutlet var secondTextView: UITextView!
    @IBOutlet var translateButton: UIButton!
    
    let firstPickerView = UIPickerView()
    let secondPickerView = UIPickerView()
    
    
    let dict = [
        "Korean": "ko",
        "English": "en",
        "Japanese": "ja",
        "Chinese - CN": "zh-CN",
        "Chinese - TW": "zh-TW",
        "Vietnam": "vi",
        "Indonesia": "id",
        "Thailand": "th",
        "German": "de",
        "Russian": "ru",
        "Spanish": "es",
        "Italy": "it",
        "Franch": "fr"
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        design(firstTextField)
        design(secondTextField)
        design(firstTextView)
        design(secondTextView)
        design(translateButton)
        
        translateButton.setTitle("Translate!", for: .normal)
        
        firstPickerView.delegate = self
        firstPickerView.dataSource = self
        secondPickerView.delegate = self
        secondPickerView.dataSource = self
        
        firstTextField.inputView = firstPickerView
        secondTextField.inputView = secondPickerView
        
        firstTextField.placeholder = "언어를 선택하세요"
        secondTextField.placeholder = "언어를 선택하세요"
        
        firstTextView.text = ""
        secondTextView.text = ""
        secondTextView.isEditable = false
    }
    
    func design(_ sender: UIView) {
        sender.layer.borderWidth = 1
    }
    
    
    
    @IBAction func tranlateButtonTapped(_ sender: UIButton) {
        if ( firstTextField.text == "" || secondTextField.text == "") {
            return;
        }
        
        
        
        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": dict[firstTextField.text!]!,
            "target": dict[secondTextField.text!]!,
            "text": firstTextView.text!
        ]
        
        
        AF.request(url, method: .post, parameters: parameter, headers: header)
            .validate()
            .responseJSON { response in
                switch response.result {
                case .success(let value):
                    let json = JSON(value)
                    print("JSON: \(json)")
                    
                    self.secondTextView.text = json["message"]["result"]["translatedText"].stringValue
                case .failure(let error):
                    print(error)
                }
            }
    }
    
    
    @IBAction func tapgesture(_ sender: UITapGestureRecognizer) {
        view.endEditing(true)
    }
}
extension Translate2ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        dict.keys.count
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        if (pickerView == firstPickerView) {
            firstTextField.text = String( Array(dict.keys)[row] )
        } else {
            secondTextField.text = String( Array(dict.keys)[row] )
        }
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return String( Array(dict.keys)[row] )
    }
}
TMDB Movie Search


URL + Extension
import Foundation
extension URL {
    
    static let baseURL = "https://api.themoviedb.org/3/"
    
    static func makeEndPointString(_ endpoint: String) -> String {
        return baseURL + endpoint
    }
}
enum Endpoint {
    case movieTrend
    case movieGenre
    case movieDetail
    case imagePrefix
    
    var requestURL: String {
        switch self {
        case .movieTrend :  return URL.makeEndPointString("trending/movie/week?language=en-US")
        case .movieGenre :  return URL.makeEndPointString("genre/movie/list")
        case .movieDetail : return URL.makeEndPointString("movie/") 
        case .imagePrefix : return "https://image.tmdb.org/t/p/w500/"
        }
    }
}
APIManager
import Foundation
import SwiftyJSON
import Alamofire
class APIManager {
    
    static let shared = APIManager()
    private init() {}
    
    let header: HTTPHeaders = ["Authorization" : APIKey.tmdb]
    
    func callRequest(_ type: Endpoint, _ movieID: Int, completionHandler: @escaping (JSON) -> () )  {
        
        var url = type.requestURL
        if (type == .movieDetail) {
            url += "\(movieID)/credits"
        }
        
        AF.request(url, method: .get, headers: header)
            .validate()
            .responseJSON { response in
                switch response.result {
                case .success(let value) :
                    let json = JSON(value)
                    completionHandler(json)
                    
                case .failure(let error) :
                    print(error)
            }
            
        }
    }
}
MainViewController
import UIKit
import SwiftyJSON
import Alamofire
class MainViewController: UIViewController {
    
    var movieList: [MovieForMain] = []
    
    
    @IBOutlet var mainTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        mainTableView.delegate = self
        mainTableView.dataSource = self
        
        let nib = UINib(nibName: "MovieTableViewCell", bundle: nil)
        mainTableView.register(nib, forCellReuseIdentifier: "MovieTableViewCell")
        
        mainTableView.rowHeight = 400
        
        callRequest(callRequest2)
    }
    
    
    func callRequest2() {
        
        APIManager.shared.callRequest(.movieGenre, 0) { json in
            
            for (index, movie) in self.movieList.enumerated() {
                print(index, movie.genre)
                self.movieList[index].genreString.removeAll()
                
                for movieGenre in movie.genre {
                    
                    for g in json["genres"].arrayValue {
                        
                        if g["id"].intValue == movieGenre {
                            self.movieList[index].genreString.append(g["name"].stringValue)
                        }
                        print(index, self.movieList[index].genreString)
                    }
                }
            }
            
            self.mainTableView.reloadData()
        }
    }
    
    
    func callRequest(_  completion: @escaping () -> Void) {
        
        APIManager.shared.callRequest(.movieTrend, 0) { json in
            for item in json["results"].arrayValue {
                print(item)
                
                let date = item["release_date"].stringValue
                
                var genre: [Int] = []
                for g in item["genre_ids"].arrayValue {
                    genre.append(g.intValue)
                }
                
                let mainImage = item["poster_path"].stringValue
                let backImage = item["backdrop_path"].stringValue
                let rate = item["vote_average"].doubleValue
                let title = item["title"].stringValue
                
                let id = item["id"].intValue
                let overView = item["overview"].stringValue
                
                let newMovie = MovieForMain(id: id, date: date, genre: genre, genreString: [], mainImage: mainImage, backImage: backImage, rate: rate, title: title, overview: overView)
                
                self.movieList.append(newMovie)
            }
            
            
            completion()
            
            self.mainTableView.reloadData()
        }
    }
    
}
extension MainViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return movieList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MovieTableViewCell") as! MovieTableViewCell
        
        
        cell.designCell(movieList[indexPath.row])
        
        return cell;
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
        
        vc.movieID = movieList[indexPath.row].id
        vc.movieName = movieList[indexPath.row].title
        vc.overView = movieList[indexPath.row].overview
        
        vc.mainImageLink = movieList[indexPath.row].mainImage
        vc.backImageLink = movieList[indexPath.row].backImage
        
        tableView.reloadRows(at: [indexPath], with: .automatic)
        
        navigationController?.pushViewController(vc, animated: true)
    }
}
DetailViewController
import UIKit
import Alamofire
import SwiftyJSON
class DetailViewController: UIViewController {
    
    
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var backImageView: UIImageView!
    @IBOutlet var mainImageView: UIImageView!
    
    @IBOutlet var overTextView: UITextView!
    @IBOutlet var castTextView: UITextView!
    @IBOutlet var crewTextView: UITextView!
    
    
    var movieID: Int = 0
    var movieName: String = ""
    var overView: String = ""
    
    var mainImageLink: String = ""
    var backImageLink: String = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        print(movieID)
        
        titleLabel.text = movieName
        overTextView.text = overView
        
        castTextView.text = ""
        crewTextView.text = ""
        
        overTextView.isEditable = false
        castTextView.isEditable = false
        crewTextView.isEditable = false
        
        
        let urlMain = URL(string: Endpoint.imagePrefix.requestURL + mainImageLink)
        mainImageView.kf.setImage(with: urlMain)
        
        let urlBack = URL(string: Endpoint.imagePrefix.requestURL + backImageLink)
        backImageView.kf.setImage(with: urlBack)
        backImageView.contentMode = .scaleAspectFill
        
        
        
        APIManager.shared.callRequest(.movieDetail, movieID) { json in
            for person in json["cast"].arrayValue {
                self.castTextView.text += person["name"].stringValue
                self.castTextView.text += "\n"
            }
            
            for person in json["crew"].arrayValue {
                self.crewTextView.text += person["name"].stringValue
                self.crewTextView.text += "\n"
            }
        }
    }
}