순서랑 코드만 간단히 적어본다.
공공 API를 어떻게 갖고 오나? (공공 API를 GET 하는 방법)
먼저 스위프트로 API를 갖고 오는 코드 구현 URL 구조체 생성 URL은 옵셔널 바인딩 처리를 해야 한다. 주소는 아이튠즈 API 갖고 왔다. https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTu
leral123-it.tistory.com
API 주소와 같은 문자열은 별도의 파일을 만들어 관리한다.
이때 주로 열거형, 구조체를 통해 구분한다.
// MARK: API 주소 문자열 묶음
enum MovieApi {
static let requestUrl = "https://itunes.apple.com/search?"
static let mediaParam = "media=movie"
}
데이터를 갖고 오는 함수는 분리해서 작성한다.
일전에 작성한 함수를 참조했다.
네트워크에서 발생할 수 있는 에러를 정의한다.
이 때 정의한 이유는 Result를 통해 데이터를 갖고 오기 위함이다
enum NetworkError: Error {
case networkingError
case dataError
case parseError
}
네트워크를 요청하는 함수를 생성한다. 이를 통해 영화 데이터를 가져온다.
// 네트워크 요청하는 함수 (영화 데이터 가져오기)
func fetchMovie(searchTerm: String, completion: @escaping NetworkCompletion) {
let urlString = "\(MovieApi.requestUrl)\(MovieApi.mediaParam)&term=\(searchTerm)"
print(urlString)
performRequest(with: urlString) { result in
completion(result)
}
}
위에서 네트워크를 요청하는 함수 내에서 실제로 Request 하는 함수를 구현한다.
여기서 error를 먼저 확인하는 이유는, 에러 발생 유무에 따라 코드를 계속 실행시킬지 말지 정하기 위함이다.
// 실제 Request하는 함수 (비동기 실행 ▶️ 클로저 방식으로 끝난 시점을 전달 받도록 설계)
private func performRequest(with urlString: String, completion: @escaping NetworkCompletion) {
print(#function)
guard let url = URL(string: urlString) else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
completion(.failure(.networkingError))
return
}
guard let safeData = data else {
completion(.failure(.dataError))
return
}
// 메서드 실행해서, 결과를 받는다.
if let movies = self.parseJSON(safeData) {
print("Parse 실행")
completion(.success(movies))
} else {
print("Parse 실패")
completion(.failure(.parseError))
}
}
task.resume()
}
그리고 최종적으로 받아온 데이터를 확인하는 함수를 구현한다.
private func parseJSON(_ movieData: Data) -> [Movie]? {
// 성공
do {
// ⭐️ JSON Data -> MovieData 구조체
let movieData = try JSONDecoder().decode(MovieData.self, from: movieData)
return movieData.results
// 실패
} catch {
print(error.localizedDescription)
return nil
}
}
ViewController 내부에 API 를 통해 받아올 데이터를 표시할 테이블 뷰를 하나 생성한다.
테이블 뷰를 만들 때 순서는 다음과 같다.
1. 테이블 뷰 변수를 하나 생성한다.
2. 테이블을 사용하기 위해 dataSource, delegate 를 선언한다.
3. 오토레이아웃을 설정한다.
4. 테이블에 사용할 테이블 셀을 생성한다.
5. dataSource, delegate 선언으로 필요한 함수를 구현한다.
먼저 Constants 에 아래 코드를 생성한다.
// MARK: - 사용할 Cell 문자열 묶음
public struct cell {
static let movieCellIdentifier = "MovieCell"
static let movieCollectionViewCellIdentifier = "MovieCollectionViewCell"
private init() {}
}
테이블 뷰를 선언한다.
private let movieTableView = UITableView()
dataSource, delegate 선언 및 테이블 셀을 등록한다.
func setupMovieTableView() {
movieTableView.dataSource = self
movieTableView.delegate = self
movieTableView.register(MovieTableViewCell.self, forCellReuseIdentifier: cell.movieCellIdentifier)
}
테이블에 대한 오토 레이아웃을 선언한다.
func setupMovieTableViewConstraints() {
view.addSubview(movieTableView)
movieTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
movieTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
movieTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0),
movieTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0),
movieTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
])
}
viewDidLoad() 메서드 내에 아래 함수를 구현한다.
추가로 초기 화면에 나오게 할 데이터 함수를 구현한다.
override func viewDidLoad() {
super.viewDidLoad()
setupDatas()
setupMovieTableView()
setupMovieTableViewConstraints()
setupSearchBar()
setupNaviBar()
}
// MARK: - 데이터 셋업
func setupDatas() {
networkManager.fetchMovie(searchTerm: "marvel") { result in
print(#function)
switch result {
case.success(let movieDatas):
self.movieArray = movieDatas
print(movieDatas)
DispatchQueue.main.async {
self.movieTableView.reloadData()
}
case.failure(let error):
print(error.localizedDescription)
}
}
}
테이블 셀을 구현한다.
import UIKit
final class MovieTableViewCell: UITableViewCell {
var imageUrl: String? {
didSet {
loadImage()
}
}
// MARK: - UI 설정
var movieMainImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
var stackView: UIStackView = {
let sv = UIStackView()
sv.axis = .vertical
sv.distribution = .fill
sv.alignment = .fill
sv.spacing = 3
return sv
}()
var movieNameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .bold)
label.numberOfLines = 1
return label
}()
var movieDescription: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .regular)
label.numberOfLines = 3
return label
}()
var movieReleasedDate: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .bold)
label.numberOfLines = 1
return label
}()
// 셀이 재사용되기 전에 호출되는 메서드
override func prepareForReuse() {
super.prepareForReuse()
self.movieMainImageView.image = nil
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
setupStackView()
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// stackView에 영화제목, 감독, 설명 포함
func setupStackView() {
self.contentView.addSubview(movieMainImageView)
self.contentView.addSubview(stackView)
stackView.addArrangedSubview(movieNameLabel)
stackView.addArrangedSubview(movieDescription)
stackView.addArrangedSubview(movieReleasedDate)
}
// MARK: - 제약 조건 설정
func setConstraints() {
movieMainImageViewConstraints()
// movieDescriptionConstraints()
setStackViewConstraints()
}
// 이미지 제약조건 설정
func movieMainImageViewConstraints() {
NSLayoutConstraint.activate([
movieMainImageView.heightAnchor.constraint(equalToConstant: 100),
movieMainImageView.widthAnchor.constraint(equalToConstant: 100),
movieMainImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 5),
movieMainImageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor)
])
}
func movieDescriptionConstraints() {
movieDescription.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
movieDescription.heightAnchor.constraint(equalToConstant: 18)
])
}
// 스택뷰 제약조건 설정
func setStackViewConstraints() {
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: movieMainImageView.trailingAnchor, constant: 5),
stackView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5),
stackView.topAnchor.constraint(equalTo: movieMainImageView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: movieMainImageView.bottomAnchor)
])
}
// URL ===> 이미지를 셋팅하는 메서드
private func loadImage() {
guard let urlString = self.imageUrl, let url = URL(string: urlString) else { return }
DispatchQueue.global().async {
guard let data = try? Data(contentsOf: url) else { return }
guard urlString == url.absoluteString else { return }
DispatchQueue.main.async {
self.movieMainImageView.image = UIImage(data: data)
}
}
}
}
최종적으로 대리자 선언에 따른 함수를 구현한다.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.movieArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = movieTableView.dequeueReusableCell(withIdentifier: cell.movieCellIdentifier, for: indexPath) as? MovieTableViewCell else { return MovieTableViewCell() }
cell.imageUrl = movieArray[indexPath.row].artworkUrl100
cell.movieNameLabel.text = movieArray[indexPath.row].movieName
cell.movieDescription.text = movieArray[indexPath.row].shortDescription
cell.movieReleasedDate.text = movieArray[indexPath.row].releaseDateString
cell.selectionStyle = .none
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
}
'UIKit > 기본' 카테고리의 다른 글
서치바로 원하는 검색어에 맞춰 공공 API 정보 얻어오기 (0) | 2024.04.20 |
---|---|
공공 API를 어떻게 갖고 오나? (공공 API를 GET 하는 방법) (0) | 2024.04.09 |
Filemanager로 CRUD 해보기 (1) | 2024.04.04 |
네비게이션 바 + 탭 바 적용 (0) | 2024.03.29 |
Drawing Cycle (+ Layout Cycle) (0) | 2024.03.28 |