Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Swift】UICollectionView/APIから画像URLを取得して画像を表示

APIから画像URLを取得して画像をダウンロードし、UICollectionViewに表示するサンプルを実装してみました。

 

APIはPixabay APIを使用しています。

https://pixabay.com/ja/

※2019/11 追記

以下記事でページネーションを実装しました。

www.hfoasi8fje3.work

 

■開発環境

Xcode 10.3

・Swift 5.0.1

 

■サンプルのイメージ

f:id:hfoasi8fje3:20190822223846p:plain
 

■実装手順

1.Nukeをインストール

pod 'Nuke'

 

2.Main.storyboardでUICollectionViewを配置し、上下左右の制約を0にする

※Collection View Cellは削除

f:id:hfoasi8fje3:20190821223657p:plain

 

3.Collection View Flow Layoutを画像の通りに設定

f:id:hfoasi8fje3:20190821224251p:plain

 

4.画面左上のFile>New>File>Cocoa Touch ClassでUICollectionViewCellを追加

※Also create XIB fileにチェックを入れる

f:id:hfoasi8fje3:20190821225020p:plain

 

5.CollectionViewCell.xibにUIImageViewとUILabelを配置

※UIImageViewの制約は上下左右0、UILabelの制約は左と下が0、高さ15、幅30以上

f:id:hfoasi8fje3:20190821230051p:plain

 

※その他設定

・UIImageViewのContent ModeをAspect Fillに変更

・UILabelのFontをSystem 11.0に変更

・UILabelの背景の色は以下画像の通り設定

f:id:hfoasi8fje3:20190821230319p:plain

 

6.5で配置したUIImageViewとUILabelをCollectionViewCell.swiftに関連づける

CollectionViewCell.swift

import UIKit

class CollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var label: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

 

7. ViewController.swiftに以下実装

ViewController.swift

import UIKit
import Nuke

struct Item: Codable {
    var hits: [Hits]
    struct Hits: Codable {
        var user: String
        var previewURL: String
        var webformatURL: String
    }
}

class ViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    private var items: Item?
    private let preheater = ImagePreheater()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell")
        
        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.prefetchDataSource = self
        
        self.setUpPixabayItems()
        
        let refreshControl = UIRefreshControl()
        self.collectionView.refreshControl = refreshControl
        refreshControl.addTarget(self, action: #selector(ViewController.refresh(sender:)), for: .valueChanged)
    }
    
    @objc func refresh(sender: UIRefreshControl) {
        self.setUpPixabayItems()
        sender.endRefreshing()
    }
    
    private func setUpPixabayItems() {
        self.getPixabayItems(completion: { (item) in
            self.items = item
            DispatchQueue.main.async {
                self.collectionView.reloadData()
            }
        })
    }
    
    private func getPixabayItems(completion: @escaping (Item) -> ()) {
        if let url = URL(string: "https://pixabay.com/api/?key={KEY}&image_type=photo") {
            let task = URLSession.shared.dataTask(with: url) { data, response, error in
                if let data = data {
                    
                    do {
                        let decoder = JSONDecoder()
                        let items = try decoder.decode(Item.self, from: data)
                        completion(items)
                    } catch {
                        print("Serialize Error")
                    }
                } else {
                    print(error ?? "Error")
                }
            }
            task.resume()
        }
    }
}

// セル選択時の処理
extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let url = URL(string: self.items?.hits[indexPath.row].webformatURL ?? "") {
            UIApplication.shared.open(url)
        }
    }
}

// セルの大きさ
extension ViewController:  UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        let numberOfCell: CGFloat = 3
        let cellWidth = UIScreen.main.bounds.size.width / numberOfCell - 2
        return CGSize(width: cellWidth, height: cellWidth)
    }
}

extension ViewController: UICollectionViewDataSource {
    // セルの数
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.items?.hits.count ?? 0
    }
    
    // セルの設定
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
        
        cell.imageView.image = nil
        cell.label.text = nil
        
        let item = self.items?.hits[indexPath.row]
        cell.label.text = item?.user
        let previewUrl = URL(string: item?.previewURL ?? "")!
        Nuke.loadImage(with: previewUrl, into: cell.imageView)
        
        return cell
    }
}

extension ViewController: UICollectionViewDataSourcePrefetching {
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
        let urls = indexPaths.map { URL(string: self.items?.hits[$0.row].previewURL ?? "") }
        preheater.startPreheating(with: urls as! [URL])
    }
    
    func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
        let urls = indexPaths.map { URL(string: self.items?.hits[$0.row].previewURL ?? "") }
        preheater.stopPreheating(with: urls as! [URL])
    }
}

 

■参考リンク

https://developer.apple.com/documentation/uikit/uicollectionview

https://pixabay.com/api/docs/

https://qiita.com/abouch/items/aca979b71ad792478687