楽天APIでCD情報を取得　アーティスト名と曲名からYoutubeAPIで動画を検索と表示

#今回の内容

##完成形
* [動作確認用のYouTube動画](https://youtu.be/t7P-Mx-KIPo)

##簡単機能説明
* `UITextField`に入力された`アーティスト名`からCDを検索し、`UICollectionView`に表示します。
* 表示された`UICollectionViewCell`を選択すると、CDに入っている曲名を表示する`MusicDetailView`を`modal`で表示します。
* 表示された`MusicDetailView`内の`UITableView`に曲名が表示されているので、選択すると`VideoListView`を`modal`で表示します。
* `VideoListView`に`アーティスト名と曲名`でYoutubeから取得してきた動画の一覧を複数表示します。
* 表示された一覧を選択すると動画を表示する為の`VideoPlayView`を`modal`で表示します。
* 表示された`VideoPlayView`で動画を再生します。

#コード

##Model

###VideoDetailGroup
```Swift:VideoDetailDatas
struct VideoDetailDatas{
    
    let thumbnailImageURL:String?
    let title:String?
    let channelTitle:String?
    let videoPlayerContents:VideoPlayerContents?
}

struct VideoPlayerContents{
    
    let videoId:String?
    let description:String?
}
```

```Swift:VideoAlamofireProcess
import Alamofire
import SwiftyJSON


class VideoAlamofireProcess{
    
    private var videoDetailResultArray = [VideoDetailDatas]()
}

extension VideoAlamofireProcess{
    
    public func getVideoDetailData(searchKeyword:String?,comletion: @escaping ([VideoDetailDatas]?,Error?) -> Void){
        
        print(searchKeyword as Any)
        
        guard let keyword = searchKeyword else { return }
        
        let apiKey = "https://www.googleapis.com/youtube/v3/search?&key=作成したAPIKey&part=snippet&q=\(keyword.urlEncoded)&maxResults=15"
        
        AF.request(apiKey, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {[self] response in
            
            switch response.result{
                
            case .success:
                
                let detailData = JSON(response.data as Any)
                videoDetailResultArray = []
                
                for dataCount in 0..<detailData["items"].count{
                    
                    if let getThumbnail = detailData["items"][dataCount]["snippet"]["thumbnails"]["medium"]["url"].string,
                       let getTitle = detailData["items"][dataCount]["snippet"]["title"].string,
                       let getChannelName = detailData["items"][dataCount]["snippet"]["channelTitle"].string,
                       let getVideoID = detailData["items"][dataCount]["id"]["videoId"].string,
                       let getDiscription = detailData["items"][dataCount]["snippet"]["description"].string{
                        
                        if detailData["items"][dataCount]["id"]["channelId"].string != getChannelName{
                            
                            videoDetailResultArray.append(VideoDetailDatas(thumbnailImageURL: getThumbnail,
                                                                           title: getTitle,
                                                                           channelTitle: getChannelName,
                                                                           videoPlayerContents: VideoPlayerContents(videoId: getVideoID,
                                                                                                                    description: getDiscription)))
                        }
                    }
                }
                comletion(videoDetailResultArray, nil)
                
            case .failure(let error):
                
                comletion(nil, error)
            }
        }
        
    }
}
```

###MusicDetailDataGroup

```Swift:MusicDetailData
struct MusicDetailData{
    
    let mediumImageUrl:String?
    let title:String?
    let artistName:String?
    let playList:PlayListDetail?
}

struct PlayListDetail{
    
    let playListContents:String?
}
```

```Swift:MusicAlamofireProcess
import Alamofire
import SwiftyJSON

class MusicAlamofireProcess{

    private var privateMusicDetailData = [MusicDetailData]()
}

extension MusicAlamofireProcess{
    
    public func getMusicDetailData(searchKeyWord:String?,completion: @escaping ([MusicDetailData]?,Error?) -> Void){
     
        guard let keyword = searchKeyWord else { return }
        
        let apiKey = "https://app.rakuten.co.jp/services/api/BooksCD/Search/20170404?format=json&artistName=\(keyword.urlEncoded)&applicationId=作成したアプリID"
        
        AF.request(apiKey, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {[self] response in
            
            switch response.result{
                
            case .success:
                
                let detailData = JSON(response.data as Any)
                
                privateMusicDetailData = []
                
                for dataCount in 0..<detailData["Items"].count{
                    
                    if let getMediumImageUrl = detailData["Items"][dataCount]["Item"]["mediumImageUrl"].string,
                       let getTitle = detailData["Items"][dataCount]["Item"]["title"].string,
                       let getArtistName = detailData["Items"][dataCount]["Item"]["artistName"].string,
                       let getPlayList = detailData["Items"][dataCount]["Item"]["playList"].string{
                        
                        privateMusicDetailData.append(MusicDetailData(mediumImageUrl: getMediumImageUrl,
                                                                      title: getTitle,
                                                                      artistName: getArtistName,
                                                                      playList: PlayListDetail(playListContents: getPlayList)))
                    }
                }
                completion(privateMusicDetailData, nil)
                
                
            case .failure(let error):
            
                completion(nil, error)
            }
        }
    }
}

extension String{
    
    var urlEncoded:String{
        
        let charset = CharacterSet.alphanumerics.union(.init(charactersIn: "/?-._~"))
        let remove = removingPercentEncoding ?? self
        
        return remove.addingPercentEncoding(withAllowedCharacters: charset) ?? remove
    }
}
```

##View

###Main.storyboard

<img width="150" alt="0534F408-55C2-48EC-901A-C5FFDE1351EA_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/2c64bf79-db2e-3886-ecf4-558ca6ed618a.jpeg">

###CollectionViewCustomCellGroup

<img width="150" alt="EE411338-8B2B-4BAA-94A5-FD72DE707971_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/d2e60a37-97ad-77c9-bd35-4de9a3cd2249.jpeg">


```Swift:CollectionViewCustomCell
import UIKit

class CollectionViewCustomCell: UICollectionViewCell {

    @IBOutlet weak var mediumImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var artistNameLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
   
        
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        mediumImageView.image = UIImage(named: "")
        titleLabel.text = ""
        artistNameLabel.text = ""
        
    }
}

```

###MusicDetailViewGroup

<img width="150" alt="CCF57D54-68A4-42F1-92E5-BD72D6040ADE_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/5841ecd2-eb35-2646-6ded-540e418ab173.jpeg">

```Swift:MusicDetailView!

import UIKit
import SDWebImage

class MusicDetailView: UIViewController {

    @IBOutlet weak var mediumImageView: UIImageView!
    @IBOutlet weak var playListTableVew: UITableView!
    
    public var playListCellContentsArray = [String]()
    public var artistName = String()
    public var mediumImageURL = String()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        playListTableVew.register(UINib(nibName: "PlayListTableViewCell", bundle: nil), forCellReuseIdentifier: "PlayListCell")
        playListTableVew.delegate = self
        playListTableVew.dataSource = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        mediumImageView.sd_setImage(with: URL(string: mediumImageURL), completed: nil)
    }

}

extension MusicDetailView:UITableViewDelegate{
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        return tableView.frame.height / 8
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let videoListView = VideoListView()
        videoListView.getArtistName = artistName + " " + playListCellContentsArray[indexPath.row]
        videoListView.modalPresentationStyle = .automatic
        present(videoListView, animated: true, completion: nil)
    }
}

extension MusicDetailView:UITableViewDataSource{
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return playListCellContentsArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "PlayListCell", for: indexPath) as! PlayListTableViewCell
        
        cell.playListContentsLabel.text = "\(String(indexPath.row + 1))曲目. \(playListCellContentsArray[indexPath.row])"
            
        return cell
    }
 
 
}
```

###PlayListTableViewCell

<img width="300" alt="D567D47B-203E-4BA3-8ABB-071B7A9E2401_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/d2a0cd6e-c2f7-f188-daab-88488edc4aac.jpeg">

```Swift:
import UIKit

class PlayListTableViewCell: UITableViewCell{

    @IBOutlet weak var playListContentsLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
              
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
               
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        playListContentsLabel.text = ""
    }

}
```

###VideoListViewGroup

<img width="150" alt="4BA4F8E4-64CF-4E40-9508-CD5184470755_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/92040866-655b-7e6e-085c-4a84f336fc2a.jpeg">

```Swift:VideoListView
import UIKit

class VideoListView: UIViewController {

    @IBOutlet weak var videoListTableView: UITableView!
  
    private let videoAlamofireProcess = VideoAlamofireProcess()
    
    private var videoListContantArray = [VideoDetailDatas]()
    
    public var getArtistName = String()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        videoListTableView.register(UINib(nibName: "VideoListCell", bundle: nil), forCellReuseIdentifier: "VideoListCell")
        videoListTableView.delegate = self
        videoListTableView.dataSource = self
        
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        
        videoAlamofireProcess.getVideoDetailData(searchKeyword: getArtistName) {[self] result, error in
            
            if error != nil{
                
                return
            }
            
            showAnimation(showContent: "5", animationTime: 4, targetView: view) {[self] judgeBool in
                
                if judgeBool == false{
                    
                    return
                }
                videoListContantArray = []
                videoListContantArray = result!
                videoListTableView.reloadData()
            }
        }   
    }
    
}

extension VideoListView:UITableViewDelegate{
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        return tableView.frame.height / 2.2
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let videoPlayView = VideoPlayView()
        videoPlayView.playVideoID = videoListContantArray[indexPath.row].videoPlayerContents?.videoId
        videoPlayView.playVideoTitle = videoListContantArray[indexPath.row].title
        videoPlayView.playVideoDescription = videoListContantArray[indexPath.row].videoPlayerContents?.description
        videoPlayView.modalPresentationStyle = .automatic
        videoPlayView.presentationController?.delegate = self
        present(videoPlayView, animated: true, completion: nil)

    }
}


extension VideoListView:UITableViewDataSource{
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return videoListContantArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "VideoListCell", for: indexPath) as! VideoListCell
        
        cell.thumbnailImageView.sd_setImage(with: URL(string: videoListContantArray[indexPath.row].thumbnailImageURL!), completed: nil)
        cell.titleLabel.text = videoListContantArray[indexPath.row].title
        cell.channelNameLabel.text = videoListContantArray[indexPath.row].channelTitle
        
        return cell
    }
}

extension VideoListView:UIAdaptivePresentationControllerDelegate{
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        
       let viewPlayView = VideoPlayView()
        viewPlayView.videoView.removeFromSuperview()
    }
    
}
```

###VideoListCell

<img width="200" alt="EAD1C018-A6B7-464A-B4E6-A7FC811EE3CE_1_201_a.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/a9e75d26-00d9-325f-2e0a-7187a501a55f.jpeg">

```Swift:VideoListCell
import UIKit

class VideoListCell: UITableViewCell {

    @IBOutlet weak var thumbnailImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var channelNameLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()

    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        thumbnailImageView.image = UIImage(named: "")
        titleLabel.text = ""
        channelNameLabel.text = ""
    }
    
}
```

###VideoPlayViewGroup

<img width="150" alt="1A9904E3-263E-4329-AE39-4BC04021DC6A_4_5005_c.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/248ff334-e7d6-adff-94f9-731473f66645.jpeg">

```Swift:
import UIKit
import youtube_ios_player_helper

class VideoPlayView: UIViewController{

    @IBOutlet weak var videoOnView: UIView!
    @IBOutlet weak var videoTitleLabel: UILabel!
    @IBOutlet weak var videoDescriptionView: UITextView!
    
    public var videoView = YTPlayerView()
    
    public var playVideoID:String?
    public var playVideoTitle:String?
    public var playVideoDescription:String?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        videoTitleLabel.text = playVideoTitle
        videoDescriptionView.text = playVideoDescription
        videoView.frame = videoOnView.frame
        videoView.load(withVideoId: playVideoID!, playerVars: ["playersinline":1])
        videoOnView.addSubview(videoView)
        
        videoView.delegate = self
    }

}

extension VideoPlayView:YTPlayerViewDelegate{
    
    func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
            
        videoView.playVideo()
    }
}


extension VideoPlayView{
    
    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        super.dismiss(animated: flag, completion: completion)
        
        guard let presentationController = presentationController else { return }
        
        presentationController.delegate?.presentationControllerDidDismiss?(presentationController)
    }
}
```

###Lottie

```Swift:
import UIKit
import Lottie

extension UIViewController{
    
    func showAnimation(showContent:String?,animationTime:Double?,targetView:UIView,completion: @escaping (Bool) -> Void){
        
        guard let content = showContent else { completion(false); return }
        guard let time = animationTime else { completion(false); return }
        
        let underView = UIView(frame: CGRect(x: targetView.frame.maxX / 4, y: targetView.frame.maxY * 0.25, width: targetView.frame.width / 2, height: targetView.frame.width / 2))
        underView.layer.cornerRadius = 15.0
        underView.backgroundColor = .darkGray
        underView.alpha = 0.7
        targetView.addSubview(underView)
        
        let animationView = AnimationView()
        animationView.frame = CGRect(x: targetView.frame.maxX / 4, y: targetView.frame.maxY * 0.25, width: targetView.frame.width / 2, height: targetView.frame.width / 2)
        animationView.layer.cornerRadius = 15.0
        animationView.backgroundColor = .clear
        animationView.animation = Animation.named(content)
        animationView.contentMode = .scaleAspectFit
        animationView.loopMode = .loop
        animationView.play(completion: nil)
        targetView.addSubview(animationView)
    
        
        DispatchQueue.main.asyncAfter(deadline: .now() + time) {
            
            completion(true)
            animationView.stop()
            animationView.removeFromSuperview()
            underView.removeFromSuperview()
        }
    }
    
}
```

##Controller

```Swift:
import UIKit
import SDWebImage

class ViewController: UIViewController {

    @IBOutlet weak var searchTextField: UITextField!
    @IBOutlet weak var searchButton: UIButton!
    @IBOutlet weak var searchResultCollectionView: UICollectionView!
    
    private let musicAlamofireProcess = MusicAlamofireProcess()
    
    private var cellLayout = UICollectionViewFlowLayout()
    
    private var cellContentsArray = [MusicDetailData]()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        searchResultCollectionView.register(UINib(nibName: "CollectionViewCustomCell", bundle: nil), forCellWithReuseIdentifier: "MusicDetailCell")
        searchResultCollectionView.delegate = self
        searchResultCollectionView.dataSource = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        cellLayout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        searchResultCollectionView.collectionViewLayout = cellLayout
    }

    @IBAction func search(_ sender: UIButton) {
        
        musicAlamofireProcess.getMusicDetailData(searchKeyWord: searchTextField.text) {[self] result, error in
            
            if error != nil{
                
                return
            }
            
            showAnimation(showContent: "5", animationTime: 3.0, targetView: view) { resultBool in
                
                if resultBool == false{
                    
                    return
                }
                
                cellContentsArray = result!
                searchResultCollectionView.reloadData()
            }
        }
    }
    
}

extension ViewController:UICollectionViewDelegate{
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        let musicDetailView = MusicDetailView()
        musicDetailView.artistName = cellContentsArray[indexPath.row].artistName!
        musicDetailView.playListCellContentsArray = (cellContentsArray[indexPath.row].playList?.playListContents?.components(separatedBy: "###"))!
        musicDetailView.mediumImageURL = cellContentsArray[indexPath.row].mediumImageUrl!
        musicDetailView.modalPresentationStyle = .automatic
        present(musicDetailView, animated: true, completion: nil)
    }
}

extension ViewController:UICollectionViewDataSource{
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        return cellContentsArray.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicDetailCell", for: indexPath) as! CollectionViewCustomCell
        
        cell.mediumImageView.sd_setImage(with: URL(string: cellContentsArray[indexPath.row].mediumImageUrl!), completed: nil)
        cell.titleLabel.text = cellContentsArray[indexPath.row].title
        cell.artistNameLabel.text = cellContentsArray[indexPath.row].artistName
        
        return cell
    }
    
}

extension ViewController:UICollectionViewDelegateFlowLayout{
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        return CGSize(width: collectionView.frame.width / 2 - 10, height: collectionView.frame.height / 2 - 10)
    }
}
```

# 終わり
ご指摘、ご質問などありましたら、コメントまでお願い致します。