如何从数组中创建一个在一行中显示多个 UIImageViews 的类?

Posted

技术标签:

【中文标题】如何从数组中创建一个在一行中显示多个 UIImageViews 的类?【英文标题】:How to create a class that shows multiple UIImageViews in a line from an Array? 【发布时间】:2021-09-24 00:48:33 【问题描述】:

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

实际行为:

预期行为:

一旦您转到另一个屏幕并返回,图像就会自行校正。

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

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
    

如何让这个类更可靠,以便每次都能得到预期的结果?

我在想也许制作一个 UIImageView 数组并将其返回到我的集合视图单元格。

【问题讨论】:

什么是UIImage方法sd_setImage()?它是从哪里来的,它有什么作用?向我们展示该代码,或指向它所来自框架的文档的链接。 这一行没有意义,应该会产生一个编译器错误:imageView.setDimensions(height: self.size, width: self.size)(但是你没有显示setDimensions的代码。你需要提供所有相关函数的代码您正在调用您发布的代码。 sd_setImage() 来自 SDWebImage cocoapod 我已在帖子中添加了 .setDimensions 扩展名 那么 sd_setImage 函数有什么作用呢?为什么需要关闭?闭包的所有这些参数是什么意思? (不要强迫你寻求帮助的人问你一堆问题,然后必须去寻找你正在使用的框架,阅读它们,然后做一堆工作。编辑你的问题到包括读者理解您的问题所需的所有信息。) 【参考方案1】:

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

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

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

struct ImageLoader 
    let urls: [URL]

    func publish() -> AnyPublisher<[UIImage], Error> 
        urls.publisher
            .flatMap 
                URLSession.shared.dataTaskPublisher(for: $0)
            
            .mapError  $0 as Error 
            .compactMap  $0.data 
            .compactMap  UIImage(data: $0) 
            .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")
    


【讨论】:

确定已更新。我还添加了一些说明,说明为什么不应该在循环中进行异步调用。你可能想检查一下。我的linkedin用户名是robertleecrabtree

以上是关于如何从数组中创建一个在一行中显示多个 UIImageViews 的类?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QML 中创建动态委托?

如何在 Google 表格中创建多个工作表引用的 INDIRECT 数组字符串?

如何在不在一行中的新行中创建循环项

回收器视图应该从提供给它的双数组中创建按钮。回收站视图显示为空。不知道为啥

如何从单引号数组中创建数组

如何在html中创建一个复选框,以使用jQuery隐藏/显示表中的多个列