Movie App
무비앱
영화설명 이미지, 설명, 제목, 가격 눌렀을때 동영상 재생까지 하는 기능을 가진 어플이다.
우선
테이블 뷰와 이미지 뷰, 서치바 그리고 4개의 라벨을 오토레이아웃을 통해 적절하게 배치한다.
@IBOutlet weak var movietableview: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
movietableview.delegate = self
movietableview.dataSource = self
searchBar.delegate = self
// 연결하지않으면 작동 x
//delegate는 이벤트, datasource는데이터 연결 함수들
}
메인 컨트롤뷰에 이런식으로 델리게이트와 데이터소스를 연결한다.
새로운 클래스를 만들어 각각 라벨과 이미지뷰에 대한 내용을 담는다
import UIKit
class Moviecell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var datalabel: UILabel!
@IBOutlet weak var descriptionlabel: UILabel!
@IBOutlet weak var pricelabel: UILabel!
@IBOutlet weak var movieImageView: UIImageView!
}
테이블 뷰 셀에 identifier와 커스텀 클래스를 연결시켜주어야한다.
그 후,
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //row의 갯수
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //셀 모양
let cell = tableView.dequeueReusableCell(withIdentifier: "Moviecell", for: indexPath) as! Moviecell //객체생성 규격
return cell
}
}
extension ViewController: UISearchBarDelegate{
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
}
}
익스텐션을 통해 객체 생성 및 필수적으로 설정해주어야하는 요소들에 대해서 설정한다.
( 아직 밑에 서치바에 대한 내용은 추가하지 않았다.)
저 버튼을 삭제하면 오류가 발생하지 않는다. 아마 13.0버전이후로는 지원하지 않는 기능인 거 같다.
영화들의 여러 정보들을 가져오기 위해선 API 정보들을 포스트맨이라는 앱을 통해서 가져올 필요가 있다.
포스트맨에서 애플 공식 API를 지원하는 사이트 주소를 입력하면
이런식의 화면을 볼 수 있다. 여기서 아래 바디 부분을 주목하여 정보들을 가져올 생각을 하면 된다.
이런 정보들을 기반으로 필요한 것들을 새로운 구조체에 입력한다.
import Foundation
struct moviemodel: Codable { //JSON 코딩 디코딩
let resultCount: Int
let results: [Result]
}
struct Result: Codable {
let trackName: String?
let previewUrl: String?
let artworkUrl100: String?
let shortDescription: String?
let longDescription: String?
let trackPrice: Double?
let currency: String?
}
codable은 코딩 및 디코딩이 가능한 데이터의 형태를 말한다.
그리고 메인 뷰컨트롤러 쪽에서 API정보들을 가져올 수 있게끔 코드를 입력한다. 앞서, 맨 앞부분에
var movieModel: moviemodel?
로 구조체를 인스턴스화 시키는 과정이 필요하다.
func requestMovieAPI(){
let sessiongConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessiongConfig)
var components = URLComponents(string: "https://itunes.apple.com/search")
let term = URLQueryItem(name: "term", value: "marvel") //키 밸류 제공
let media = URLQueryItem(name: "media", value: "movie")
components?.queryItems = [term, media]
guard let url = components?.url else{
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET" //안되면 POST
let task = session.dataTask(with: request) { data, response, error in
print( (response as! HTTPURLResponse).statusCode )
if let hasData = data{
//디코딩
do{
self.movieModel = try JSONDecoder().decode(moviemodel.self, from: hasData)
print(self.movieModel ?? "no data")
DispatchQueue.main.async {
self.movietableview.reloadData()
}
}catch {
print(error)
}
}
}
task.resume()
session.finishTasksAndInvalidate()
}
약간은 복잡한 것들이 많은데 대부분 규격을 볼 수 있을 정도로 정형화된 형식인거 같다. 키, 밸류 형태로 가져오게 된다.
그리고 중간에 data를 디코딩 하는 개념도 필요하다.
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //row의 갯수
return self.movieModel?.results.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //셀 모양
let cell = tableView.dequeueReusableCell(withIdentifier: "Moviecell", for: indexPath) as! Moviecell //객체생성 규격
cell.titleLabel.text = self.movieModel?.results[indexPath.row].trackName
cell.descriptionlabel.text = self.movieModel?.results[indexPath.row].shortDescription
let currency = self.movieModel?.results[indexPath.row].currency ?? ""
let price = self.movieModel?.results[indexPath.row].trackPrice?.description ?? ""
cell.pricelabel.text = currency + price
return cell
}
}
그리고 익스텐션 부분에서 row의 갯수를 API에서 받아온 영화의 갯수로 조정하고, 타이틀 및 디스크립션, 가격등을 API에서 받아온 results 값들로 변경한다.
그러면 이런식으로 애플에 API에 movie, title, price description 등을 받아올 수 있다.
다음으로 이미지와 날짜를 받아오기 위해서
func loadImage(urlString: String, completion: @escaping (UIImage?) -> Void) {
let sessiongConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessiongConfig)
if let hasURL = URL(string: urlString) {
var request = URLRequest(url: hasURL)
request.httpMethod = "GET"
session.dataTask(with: request) { data, response, error in
print( (response as! HTTPURLResponse).statusCode )
if let hasData = data{
completion(UIImage(data: hasData))
return
}
}.resume()
session.finishTasksAndInvalidate()
}
completion(nil) //실패했을때를 대비
}
앞선 타이틀과 가격을 불러왔을때와 비슷하게 함수를 설계한다.
그리고 다른 클래스에서 구조체에서도 변화가 필요했다.
struct Result: Codable {
let image: String?
let trackName: String?
let previewUrl: String?
let shortDescription: String?
let longDescription: String?
let trackPrice: Double?
let currency: String?
let releaseDate: String?
enum CodingKeys: String, CodingKey {
case image = "artworkUrl100"
case trackName
case previewUrl
case shortDescription
case longDescription
case trackPrice
case currency
case releaseDate
}
}
enum을 이용하지않으면 이미지파일을 불러오지 못했다. 이유는 명확하게 알 수 없으나 열거형식으로 나열하는게 더 안전하고 가시성도 좋은듯 하다.
그리고 익스텐션 쪽 셀모양을 결정짓는 함수에서
if let hasURL = self.movieModel?.results[indexPath.row].image {
self.loadImage(urlString: hasURL){ image in
DispatchQueue.main.async {
cell.movieImageView.image = image
}
함수를 실행시켜 이미지를 뿌려준다.
날짜를 받아오는 부분에서는
우선 API쪽에서 날짜의 형식을 먼저 확인해야 한다. 우선 이번 형식은
이렇게 나와있는데 이것은 ISO8601DateFormatter 형식이고 이것을 원하는 형식으로 변화 시키기 위해선 인스턴스화 시킨후 임의로 조정할 필요가 있다.
if let datestring = self.movieModel?.results[indexPath.row].releaseDate {
let formatter = ISO8601DateFormatter() //날짜를 받는형식
if let isoDate = formatter.date(from: datestring){
let myformatter = DateFormatter()
myformatter.dateFormat = "yyyy년 MM월 dd일"
let datestring = myformatter.string(from: isoDate)
cell.datalabel.text = datestring
}
}
이런식으로 할 수 있고 각각 셀끼리의 크기 조절은
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200 //높이 조절
heightForRowAt으로 설정 가능하다.
이제 영화를 눌렀을때 영상과 세부 설명이 나오게끔 해야한다.
그러기위해 cocoatouchclass로
import UIKit
class DetailViewController: UIViewController {
var movieResult: MovieResult? //Result를 MovieResult로 변경하였음
@IBOutlet weak var moviecontainner: UIView!
@IBOutlet weak var Tiltlelabel: UILabel!{
didSet{
Tiltlelabel.font = UIFont.systemFont(ofSize: 24, weight: .medium)
}
}
@IBOutlet weak var descriptionlabel: UILabel!{
didSet{
descriptionlabel.font = .systemFont(ofSize: 16, weight: .light)
}
}
override func viewDidLoad() { //메모리에 올린 후 행동 화면이 그려지고 난 후
super.viewDidLoad()
Tiltlelabel.text = movieResult?.trackName
descriptionlabel.text = movieResult?.longDescription
}
}
스토리보드를 따로 만든후 연결하고 코드를 구성한다. 여기서 주의해야할 점은 화면이 그려지기 전에 IBOutlet값을 변경하려한다면 오류가 발생한다. 그렇기에 라이프사이클 함수상 화면이 그려지고 난 후에 하는 것이 적절하다.
그리고 메인 뷰 컨트롤러에서
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailVC = UIStoryboard(name: "DetailviewController", bundle: nil).instantiateViewController(identifier: "DetailViewController") as! DetailViewController //인스턴스화
detailVC.movieResult = self.movieModel?.results[indexPath.row] //화면을 띄운 후, DetaulVC에 movieresult로 가져옴
self.present(detailVC, animated: true) {
}
함수를 정의해 클릭했을때 액션에 대해서 설정한다. 타입캐스팅은 필수이다. 그리고 스토리보드에대해서 detailVC를 인스턴스화 시키는 것이다.
동영상을 재생 하기 위해선 디테일 뷰 컨트롤러에서
import AVKit을 준수하고
func makeplayerAndplay(urlString: String){
if let hasurl = URL(string: urlString){
player = AVPlayer(url: hasurl)
let playerLayer = AVPlayerLayer(player: player)
moviecontainner.layer.addSublayer(playerLayer)
//layer는 절대 좌표 개념만 있고 오토레이아웃 개념이 없다. 동영상은 layer다.
playerLayer.frame = moviecontainner.bounds
self.player?.play()
}
}
생각보다 간단하게 moviecontainer 크기에 맞게 재생시킬 수 있다.
정지버튼과 플레이 버튼을 만드는 것은
우선 스토리보드에서
이런식으로 버튼을 만들고
@IBAction func play(_ sender: Any) {
self.player?.play()
}
@IBAction func stop(_ sender: Any) {
self.player?.pause()
}
이렇게 설정해주면 된다.
그리고 최종적으로 화면을 띄우면 된다.
override func viewDidAppear(_ animated: Bool) { //화면의 크기를 가져옴
if let hasURL = movieResult?.previewUrl{
makeplayerAndplay(urlString: hasURL)
}
}
중요한점은 viewDidAppear부분에서 실행시켜야 한다. 라이프 사이클 함수와 관련있는 부분이다. 화면의 크기를 가져오는 부분에서 실행시켜야 알맞은 크기의 영상을 실행시킬 수 있다.
url은
포스트맨에 이런식으로 지정되어있다.
결론적으로 이런 화면을 얻을 수 있다.