如何创建一个 class 来显示来自数组的一行中的多个 UIImageView?

How to create a class that shows multiple UIImageViews in a line from an Array?

我 运行 在尝试在集合视图单元格中创建可视元素时遇到了一个奇怪的错误。 class 获取一组 UserModel 并将所有个人资料图片排成一行。如果数组中有超过 4 个用户,最后一个图像视图是带有“+N”的模糊图像,让用户知道还有更多用户无法显示在屏幕上。

实际行为:

预期行为:

当您转到另一个屏幕并返回时,图像会自行更正。

这是我为获得以下结果而创建的 class。

import Foundation
import UIKit

class UserStack: UIView{
    
    var userArray: [UserModel]!
    var size: CGFloat = 50
    
    init(userArray: [UserModel]){
        super.init(frame: .zero)
        self.userArray = userArray
        
        if self.userArray.count <= 4{
            fourOrLess()
        }else{
            fiveOrMore()
        }
    }
    
    func fourOrLess(){
        var spacing: CGFloat = 0
        userArray.forEach { user in
            let imageView = UIImageView()
            guard let url = user.imageURL else {return}
            
            
            imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
                if let e = e{
                    print("DEBUG: Error setting userStack - \(e)")
                    return
                }
                
                self.addSubview(imageView)
                self.bringSubviewToFront(imageView)
                
                imageView.translatesAutoresizingMaskIntoConstraints = false
                imageView.contentMode = .scaleAspectFill
                imageView.layer.cornerRadius = self.size * 0.4
                imageView.layer.masksToBounds = true
                imageView.layer.borderWidth = 0.75
                imageView.layer.borderColor = UIColor.white.cgColor
                
                ///LAYOUT
                imageView.setDimensions(height: self.size, width: self.size)
                imageView.centerY(inView: self)
                imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
                
                //DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                    spacing += (self.size - 10)
               // }
            }
        }
    }
    
    func fiveOrMore(){
        var count: Int = 1
        var spacing: CGFloat = 0
        
        userArray.forEach { user in
        if count < 4{
            count += 1
            let imageView = UIImageView()
            guard let url = user.imageURL else {return}
            
            
            imageView.sd_setImage(with: URL(string: url)) { i, e, c, u in
                if let e = e{
                    print("DEBUG: Error setting userStack - \(e)")
                    return
                }
                
                self.addSubview(imageView)
                self.bringSubviewToFront(imageView)
                
                imageView.translatesAutoresizingMaskIntoConstraints = false
                imageView.contentMode = .scaleAspectFill
                imageView.layer.cornerRadius = self.size * 0.4
                imageView.layer.masksToBounds = true
                imageView.layer.borderWidth = 0.75
                imageView.layer.borderColor = UIColor.white.cgColor
                
                ///LAYOUT
                imageView.setDimensions(height: self.size, width: self.size)
                imageView.centerY(inView: self)
                imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
                
               
                    spacing += (self.size - 10)
                    
                }
            
        
        }else{
            ///NUMBER VIEW
            let numberImageView = UIImageView()
            let blurView = UIView()
            let numberLabel = UILabel()
            guard let _URL = userArray.last?.imageURL else {return}
            numberImageView.sd_setImage(with: URL(string: _URL)) { i, e, c, u in
                if let e = e{
                    print("DEBUG: Error setting userStack - \(e)")
                    return
                }
                
                self.addSubview(numberImageView)
                
                numberImageView.translatesAutoresizingMaskIntoConstraints = false
                numberImageView.contentMode = .scaleAspectFill
                numberImageView.layer.cornerRadius = self.size * 0.4
                numberImageView.layer.masksToBounds = true
                //numberImageView.layer.borderWidth = 0.75
                //numberImageView.layer.borderColor = UIColor.white.cgColor
                
                numberImageView.addSubview(blurView)
                
                blurView.translatesAutoresizingMaskIntoConstraints = false
                blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
                blurView.layer.cornerRadius = self.size * 0.4
                blurView.layer.masksToBounds = true
                blurView.layer.borderWidth = 0.75
                blurView.layer.borderColor = UIColor.white.cgColor
                
                blurView.addSubview(numberLabel)
                
                numberLabel.text = "+\(self.userArray.count - 3)"
                numberLabel.font = .poppinsMedium(size: 14)
                numberLabel.textColor = .white
                
                ///LAYOUT
                numberImageView.setDimensions(height: self.size, width: self.size)
                numberImageView.centerY(inView: self)
                numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
                numberImageView.bringSubviewToFront(blurView)
                
                blurView.setDimensions(height: self.size, width: self.size)
                blurView.center(inView: numberImageView)
                numberLabel.center(inView: blurView)
                
                
                
                }
            }
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
} 

UIHelper 函数:

extension UIView {
    func anchor(top: NSLayoutYAxisAnchor? = nil,
                left: NSLayoutXAxisAnchor? = nil,
                bottom: NSLayoutYAxisAnchor? = nil,
                right: NSLayoutXAxisAnchor? = nil,
                paddingTop: CGFloat = 0,
                paddingLeft: CGFloat = 0,
                paddingBottom: CGFloat = 0,
                paddingRight: CGFloat = 0,
                width: CGFloat? = nil,
                height: CGFloat? = nil) {
        
        translatesAutoresizingMaskIntoConstraints = false
        
        if let top = top {
            topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }
        
        if let left = left {
            leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
        }
        
        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
        }
        
        if let right = right {
            rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
        }
        
        if let width = width {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }
        
        if let height = height {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
    
    func center(inView view: UIView, yConstant: CGFloat? = 0) {
        translatesAutoresizingMaskIntoConstraints = false
        centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yConstant!).isActive = true
    }
    
    func centerX(inView view: UIView, topAnchor: NSLayoutYAxisAnchor? = nil, paddingTop: CGFloat? = 0) {
        translatesAutoresizingMaskIntoConstraints = false
        centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        
        if let topAnchor = topAnchor {
            self.topAnchor.constraint(equalTo: topAnchor, constant: paddingTop!).isActive = true
        }
    }
    
    func centerY(inView view: UIView, leftAnchor: NSLayoutXAxisAnchor? = nil,
                 paddingLeft: CGFloat = 0, constant: CGFloat = 0) {
        
        translatesAutoresizingMaskIntoConstraints = false
        centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
        
        if let left = leftAnchor {
            anchor(left: left, paddingLeft: paddingLeft)
        }
    }
  
    
    func setDimensions(height: CGFloat, width: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        heightAnchor.constraint(equalToConstant: height).isActive = true
        widthAnchor.constraint(equalToConstant: width).isActive = true
    }
}

如何使这个 class 更可靠,以便每次都能获得预期的结果?

我在想也许可以制作一个 UIImageView 数组,然后 return 它到我的集合视图单元格。

你不应该在没有某种形式的同步的情况下在循环中调用异步方法,否则你会产生奇怪的副作用。我敢打赌,这就是您结果不一致的原因。

先下载所有图片,然后在您的视图中显示它们。

您可以使用 combine 非常轻松地获取所有图像。

struct ImageLoader {
    let urls: [URL]

    func publish() -> AnyPublisher<[UIImage], Error> {
        urls.publisher
            .flatMap {
                URLSession.shared.dataTaskPublisher(for: [=10=])
            }
            .mapError { [=10=] as Error }
            .compactMap { [=10=].data }
            .compactMap { UIImage(data: [=10=]) }
            .collect()
            .eraseToAnyPublisher()
    }
}

您可以像这样使用 ImageLoader:

var subscriptions = Set<AnyCancellable>()
func fetchImages(_ urls: [URL], completion: @escaping ([UIImage]) -> Void) {
    ImageLoader(urls: urls)
        .publish()
        .receive(on: DispatchQueue.main)
        .sink { result in
            print("Result: \(result)")
        } receiveValue: { images in
            completion(images)
        }
        .store(in: &subscriptions)
}

至于您的 UserStack,您可以这样做:

class UserStack2: UIView {

    var size: CGFloat = 50

    init(images: [UIImage]) {
        super.init(frame: .zero)
        addImages(images)
    }

    func createImageView(with image: UIImage, spacing: CGFloat) {
        let imageView = UIImageView(image: image)

        self.addSubview(imageView)
        self.bringSubviewToFront(imageView)

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        imageView.layer.cornerRadius = self.size * 0.4
        imageView.layer.masksToBounds = true
        imageView.layer.borderWidth = 0.75
        imageView.layer.borderColor = UIColor.white.cgColor

        imageView.setDimensions(height: self.size, width: self.size)
        imageView.centerY(inView: self)
        imageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
    }

    func createPlaceHolderView(withValue value: Int, spacing: CGFloat) {
        let numberImageView = UIImageView()
        let blurView = UIView()
        let numberLabel = UILabel()

        self.addSubview(numberImageView)

        numberImageView.translatesAutoresizingMaskIntoConstraints = false
        numberImageView.contentMode = .scaleAspectFill
        numberImageView.layer.cornerRadius = self.size * 0.4
        numberImageView.layer.masksToBounds = true
        //numberImageView.layer.borderWidth = 0.75
        //numberImageView.layer.borderColor = UIColor.white.cgColor

        numberImageView.addSubview(blurView)
        blurView.translatesAutoresizingMaskIntoConstraints = false
        blurView.backgroundColor = .init(white: 0.2, alpha: 0.9)
        blurView.layer.cornerRadius = self.size * 0.4
        blurView.layer.masksToBounds = true
        blurView.layer.borderWidth = 0.75
        blurView.layer.borderColor = UIColor.white.cgColor
        blurView.addSubview(numberLabel)

        numberLabel.text = "+\(value)"
        //                numberLabel.font = .poppinsMedium(size: 14)
        numberLabel.textColor = .white

        numberImageView.setDimensions(height: self.size, width: self.size)
        numberImageView.centerY(inView: self)
        numberImageView.anchor(left: self.leftAnchor, paddingLeft: spacing)
        numberImageView.bringSubviewToFront(blurView)

        blurView.setDimensions(height: self.size, width: self.size)
        blurView.center(inView: numberImageView)
        numberLabel.center(inView: blurView)
    }

    func addImages(_ images: [UIImage]) {

        let maxImages = 4
        var spacing: CGFloat = 0

        images.prefix(maxImages).forEach { image in
            createImageView(with: image, spacing: spacing)
            spacing += (self.size - 10)
        }

        if images.count > maxImages {
            createPlaceHolderView(withValue: images.count - maxImages, spacing: spacing)
        }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}