尝试将 Unsplash 中的图片显示到 UICollectionView 中

Trying to show pictures from Unsplash into a UICollectionView

我正在尝试显示一些使用 UICollectionView 从 Unsplash 获取的图片。这个想法是显示一个带有图像和作者姓名的单元格。 目前我只能显示单元格的边框和作者姓名,无论我如何处理无法显示 UIImage 的问题。 我在各处都添加了打印输出(是的,我提到过我是初学者...)并且我可以打印 url 字符串和对图像对象的引用。 我想知道我在 UIImageView 中显示 UIImage 的方式是否有问题,如果出于某种原因在绘制 View 时 UIImage 尚未准备好(我知道我必须获取它,为什么我可以打印它?)。

这是我的代码:

ViewController.swift

import UIKit

class ViewController: UIViewController  {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        view.addSubview(titleView)
        view.addSubview(searchBarView)
        view.addSubview(collectionView)
        
        searchBarView.delegate = self
        collectionView.delegate = self
        collectionView.dataSource = self
        picsManager.delegate = self
        
        setupLayout()
        
        
        
    }
    
    var picsManager = PicsManager()
    var pics = PicsModel()
    
    func setupLayout() {
        NSLayoutConstraint.activate([
            titleView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            titleView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            titleView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            titleView.widthAnchor.constraint(equalTo: view.widthAnchor),

            searchBarView.topAnchor.constraint(equalTo: titleView.bottomAnchor, constant: 10),
            searchBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            searchBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
//            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.topAnchor.constraint(equalTo: searchBarView.bottomAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
        ])
        
    }
    
    let titleView: UILabel = {
        let tv = UILabel()
        tv.translatesAutoresizingMaskIntoConstraints = false
        // tv.font = UIFont.preferredFont(forTextStyle: .largeTitle)
        tv.font = UIFont(name: "Georgia-Italic", size: 44)
        tv.textColor = .systemBlue
        tv.textAlignment = .center
        tv.text = "Coins Live"
        tv.sizeToFit()
                
        return tv
    }()
    
    
    let searchBarView: UISearchBar = {
        let sb = UISearchBar()
        sb.translatesAutoresizingMaskIntoConstraints = false
        sb.searchBarStyle = .default
        sb.placeholder = "Search..."
        sb.sizeToFit()
        sb.isTranslucent = false
        sb.showsCancelButton = false
                
        return sb
    }()
    
    lazy var collectionView: UICollectionView = {
        
        let layout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 0,
                                           left: 10,
                                           bottom: 0,
                                           right: 10)
        layout.minimumInteritemSpacing = 10
        layout.minimumLineSpacing = 10
        layout.scrollDirection = .vertical
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .systemYellow
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
        
        return cv
    }()

    
}

//MARK: - Extension UICollectionView

extension ViewController:  UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
    
    // Dimensions of the cell
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//        return CGSize(width: collectionView.frame.width / 3.5, height: collectionView.frame.width / 3.5)
        return CGSize(width: collectionView.frame.width / 1.1, height: collectionView.frame.width / 1.1)
    }
    
    // How many cells
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return pics.pics.count
        
    }

    // Details of each cell
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
        cell.picLabel.text = pics.pics[indexPath.item].getUserName
//        METHOD ONE
//        let url = URL(string: pics.pics[indexPath.item].smallPicUrl)!
//        let data = try? Data(contentsOf: url)
//        let img = UIImage(data: data!)
//        cell.backgroundImageView.image = img
        
//        METHOD TWO
//        let task = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
//            if error != nil {
//                print("Error")
//                return
//            }
//
//            if let data = data {
//                DispatchQueue.main.async {
//                    cell.backgroundImageView = UIImageView(image: UIImage(data: data))
//                    print("cellForItemAt \(indexPath.item) : \(self.pics.pics[indexPath.item].smallPicUrl)")
//                }
//            }
//        })
//
//        task.resume()
        
//        METHOD THREE
        let urlString = pics.pics[indexPath.item].smallPicUrl
        cell.configure(with: urlString)
      
        return cell
    }
    
}

extension ViewController: UISearchBarDelegate {
    
    // Contents of the searchBar are sent, but only after a delay. This is to prevent sending
    // a request after every character. Better to wait until the user has finished typing.
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.reload(_:)), object: searchBar)
            perform(#selector(self.reload(_:)), with: searchBar, afterDelay: 0.5)
        }

        @objc func reload(_ searchBar: UISearchBar) {
            guard let query = searchBar.text, query.trimmingCharacters(in: .whitespaces) != "" else {
                print("nothing to search")
                return
            }

            if let term = searchBar.searchTextField.text {
                picsManager.getPics(term)
            } else {
                print("Invalid query.")
            }
        }
}

extension ViewController: PicsManagerDelegate {
    func didUpdatePics(_ pics: PicsModel) {
        DispatchQueue.main.async {
            self.pics = pics
            self.collectionView.reloadData()
//            print("~~~ ~~~ ~~~ ~~~ ~~~ ~~~")
//            print(pics.pics[0].smallPicUrl)
        }
    }
    
    
}


CollectionViewCell.swift

//
//  CollectionViewCell.swift
//  UIKit_ProgrammaticUI_SearchBar
//
//  Created by Matteo on 31/08/2021.
//

import UIKit

class CollectionViewCell: UICollectionViewCell {
    //MARK: - Init
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        setupLayout()
    }
    
    //MARK: - Properties
    lazy var roundedBackgroundView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.layer.cornerRadius = 10
        view.layer.borderWidth = 2
        view.layer.borderColor = UIColor.systemTeal.cgColor
        
        return view
    }()
    
    lazy var picLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont(name: "HelveticaNeue", size: 13)
        label.textColor = .systemIndigo
        
        return label
    }()
    
    lazy var backgroundImageView: UIImageView = {
        let iv = UIImageView()
        iv.translatesAutoresizingMaskIntoConstraints = false
        iv.layer.cornerRadius = 10
        iv.layer.borderWidth = 2
        iv.layer.borderColor = UIColor.systemTeal.cgColor
        
        return iv
    }()
    
}


//MARK: - Layout
extension CollectionViewCell {
    private func setupLayout() {
        // When configuring a view, you add any custom subview to self.contentView
        // Option + leftClick for more info!
        self.contentView.addSubview(roundedBackgroundView)
        roundedBackgroundView.addSubview(picLabel)
        
        NSLayoutConstraint.activate([
            roundedBackgroundView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5),
            roundedBackgroundView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -5),
            roundedBackgroundView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 5),
            roundedBackgroundView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5),
            
            picLabel.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor),
            picLabel.centerYAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -15)
        ])
    }
    
    func configure(with urlString: String) {
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url, completionHandler: { [weak self] data, _, error in
            guard let data = data, error == nil else { return }
            
            DispatchQueue.main.async {
                let image = UIImage(data: data)
                self!.backgroundImageView.image = image
                print("~~~~~ ~~~~~ ~~~~~")
                print(self?.backgroundImageView.image ?? "nothing to print here...")
            }
        }).resume()
    }
}

PicsManager.swift

//
//  PicsManager.swift
//  UIKit_ProgrammaticUI_SearchBar
//
//  Created by Matteo on 01/09/2021.
//

import Foundation

protocol PicsManagerDelegate {
    func didUpdatePics(_ pics: PicsModel)
}

struct PicsManager {
    
    var currentPage: Int = 1
    let picsURL = "https://api.unsplash.com/search/photos/"
    
    
    
    var delegate: PicsManagerDelegate?
    
    let API_Key = "Your API Key here"
    
    enum OrderBy {
        case latest
        case oldest
        case popular
    }
    
    var searchURL: String {
        return ("\(picsURL)?client_id=\(API_Key)&page=\(String(currentPage))")
    }
    
    func getPics(_ term: String) {
        let fullURL = "\(searchURL)&query=\(term)"
        self.performRequest(with: fullURL)
    }
    
    func performRequest(with urlString: String) {
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
            
            let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
                if error != nil {
                    print("Error: \(error!)")
                    return
                }
                
                if let data = data {
                    print("calling parseJSON")
                    self.parseJSON(data)
                    
//                    let dataString = String(data: data, encoding: .utf8)
//                    print(dataString!)
                    
                } else {
                    print("No data!!")
                }
                
                
            })
            
            task.resume()
        }
    }
    
    func parseJSON(_ picsData: Data) {
        let pics = PicsModel()
        
        do {
            let decodedData = try JSONDecoder().decode(PicsData.self, from: picsData)
            print("Decoded data:\n\(decodedData.results[0].urls.small)")
            var downloadedPics: [Result] = []
            downloadedPics += decodedData.results
//            print("~~~ ~~~ ~~~")
//            print(downloadedPics)
            for item in decodedData.results {
                let pic = PicDetails(userName: item.user.name, title: item.alt_description, smallPicUrl: item.urls.small, largePicUrl: item.urls.full)
                pics.pics.append(pic)
            }
            
            self.delegate?.didUpdatePics(pics)
            
            
        } catch let error {
            print("Error decoding data: \(error)")
        }
        
    }
}

PicsData.swift

//
//  PicsData.swift
//  UIKit_ProgrammaticUI_SearchBar
//
//  Created by Matteo on 01/09/2021.
//

import Foundation

struct PicsData: Decodable {
    let total: Int
    let total_pages: Int
    let results: [Result]
}

struct Result: Decodable {
    let id: String
    let alt_description: String
    let likes: Int
    let user: User
    let urls: URLS
}

struct User: Decodable {
    let id: String
    let name: String
}

struct URLS: Decodable {
    let thumb: String
    let small: String
    let regular: String
    let full: String
    let raw: String
}

PicsModel.swift

//
//  PicsModel.swift
//  UIKit_ProgrammaticUI_SearchBar
//
//  Created by Matteo on 05/09/2021.
//

import UIKit

class PicsModel {
    var pics: [PicDetails] = []
    
}

struct PicDetails {
    var userName: String
    var title: String
    var smallPicUrl: String
    var largePicUrl: String
    
//    var smallPicData: UIImageView {
//        return self.getPicData(smallPic)
//    }
    
//    var getShortUserName: String {
//        var nameString = userName
//        nameString = nameString.trimmingCharacters(in: .whitespacesAndNewlines)
//        let start = nameString.index(nameString.startIndex, offsetBy: 0)
//        let end = nameString.index(nameString.startIndex, offsetBy: 9)
//        let range = start...end
//        nameString = String(nameString[range])
//        return nameString
//    }
    
    var getUserName: String {
        var nameString = userName
        nameString = nameString.trimmingCharacters(in: .whitespacesAndNewlines)
        return nameString
    }
    
//    func getPicData(_ urlString: String) -> UIImageView {
//        let img = UIImageView()
//
//        DispatchQueue.global(qos: .userInitiated).async {
//            if let url = URL(string: urlString) {
//                let session = URLSession(configuration: .default)
//                let task = session.dataTask(with: url, completionHandler: { data, response, error in
//                    if error != nil {
//                        img.backgroundColor = .systemRed
//                    }
//
//                    if let data = data {
//                        DispatchQueue.main.async {
//                            img.image = UIImage(data: data)
//                        }
//                    }
//                })
//                task.resume()
//            }
//        }
//        return img
//    }
}


欢迎来到 Whosebug,Mabus!

你错过了一个导致错误的小东西,如果你在 CollectionViewCell class 中查看 setupLayout 方法,你会注意到你忘记添加 backgroundImageView到单元格的内容视图。

我在 setupLayout 方法中添加了一些代码行只是为了演示出了什么问题,所以请忽略单元格的美感:3

private func setupLayout() {
        self.contentView.addSubview(roundedBackgroundView)
        roundedBackgroundView.addSubview(picLabel)
        roundedBackgroundView.addSubview(backgroundImageView)
        
        NSLayoutConstraint.activate([
            roundedBackgroundView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5),
            roundedBackgroundView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -5),
            roundedBackgroundView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 5),
            roundedBackgroundView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5),
            backgroundImageView.centerXAnchor.constraint(equalTo: roundedBackgroundView.centerXAnchor),
            backgroundImageView.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
            picLabel.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor),
            picLabel.centerYAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -15)
        ])
    }

这就是您的应用目前的样子: