未从 UIViewController 正确调用 UICollectionViewCell

UICollectionViewCell is not being correctly called from the UIViewController

我正在尝试为 UICollectionViewCell 创建一个自定义叠加层,当用户 select 图像时,它会放置一个带有数字(即订单)的灰色叠加层,用户 selected中的图像。当我 运行 我的代码时,我没有收到任何错误,但它似乎什么也没做。我添加了一些打印语句来帮助调试,当我 运行 代码时,我打印了 15 次“Count :0”。那是我在库中的图像数量。当我 select 第一行中的第一张图片时,我仍然如我所料得到“计数:0”,但是当我 select 下一张图片时,我得到的打印结果如下所示。计数似乎不起作用,但我不确定为什么。我究竟做错了什么?我无法弄清楚为什么计数错误,但我主要 issue/concern 我想解决的是为什么叠加层无法正常显示?

打印报表

Cell selected: [0, 0]
Count :0
Count :0
Count :0
Cell selected: [0, 4]
Count :0

视图控制器

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
            cell.setupView()
            print("Cell selected: \(indexPath)")
        }
 }
    
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
            cell.backgroundColor = nil
            cell.imageView.alpha = 1
        }
 }

自定义叠加层

lazy var circleView: UIView = {
     let view = UIView()
     view.backgroundColor = .black
     view.layer.cornerRadius = self.countSize.width / 2
     view.alpha = 0.4
     view.translatesAutoresizingMaskIntoConstraints = false
     return view
}()   
lazy var countLabel: UILabel = {
    let label = UILabel()
    let font = UIFont.preferredFont(forTextStyle: .headline)
    label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
    label.textAlignment = .center
    label.textColor = .white
    label.adjustsFontSizeToFitWidth = true
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}() 
    private func setup(){addSubview(circleView)
        addSubview(circleView)
        addSubview(countLabel)
        NSLayoutConstraint.activate([
            circleView.leadingAnchor.constraint(equalTo: leadingAnchor),
            circleView.trailingAnchor.constraint(equalTo: trailingAnchor),
            circleView.topAnchor.constraint(equalTo: topAnchor),
            circleView.bottomAnchor.constraint(equalTo: bottomAnchor),
            
            countLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            countLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
            countLabel.topAnchor.constraint(equalTo: topAnchor),
            countLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

TestCVCell: UICollectionViewCell

    override var isSelected: Bool {
        didSet { overlay.isHidden = !isSelected }
    }
    var imageView: UIImageView = {
        let view = UIImageView()
        view.clipsToBounds = true
        view.contentMode = .scaleAspectFill
        view.backgroundColor = UIColor.gray
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    var count: Int = 0 {
        didSet { overlay.countLabel.text = "\(count)" }
    }
    let overlay: CustomAssetCellOverlay = {
        let view = CustomAssetCellOverlay()
        view.isHidden = true
        return view
    }()
   func setupView() {
        addSubview(imageView)
        addSubview(overlay)
        print("Count :\(count)")
        NSLayoutConstraint.activate([
            overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
            overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
            overlay.leftAnchor.constraint(equalTo: imageView.leftAnchor),
            overlay.rightAnchor.constraint(equalTo: imageView.rightAnchor),
        ])
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = self.bounds
        setupView()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
        fatalError("init(coder:) has not been implemented")
    }

你的 var count: Int = 0 不应该在你的 CollectionView 委托中设置吗?


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
            cell.setupView()
            cell.count = indexPath.item
            print("Cell selected: \(indexPath)")
        }
 }

根据你的其他问题,我猜你正试图做这样的事情...

显示设备照片中的图像,并允许按顺序select离子:

并且,当您 de-select 一个细胞时 - 例如,de-selecting 我的第二个 select 离子 - 您想要 re-number 剩余 select 个离子:

为此,您需要跟踪阵列中的单元格 selection - 因为它们是生成的 - 因此您可以保持编号。

有几种方法可以解决这个问题...这是一个。

首先,我建议 re-naming 您的 count 属性 为 index,并且在设置值时,显示或隐藏 overlay :

var index: Int = 0 {
    didSet {
        overlay.countLabel.text = "\(index)"
        // hide if count is Zero, show if not
        overlay.isHidden = index == 0
    }
}

当您从 cellForItemAt 中取出一个单元格时,查看 indexPath 是否在我们的“跟踪”数组中并适当地设置单元格的 .index 属性(这也会 show/hide 叠加层)。

接下来,当您 select 一个单元格时:

  • indexPath 添加到我们的跟踪数组中
  • 我们可以设置 .index 属性 - 使用我们跟踪数组的计数 - 直接更新单元格的外观,因为它不会影响任何其他单元格

当你de-select一个单元格时,我们要做额外的工作:

  • 从我们的跟踪数组中删除 indexPath
  • 重新加载单元格,使它们 re-numbered

这是一个完整的示例 - 代码中有很多注释。

CircleView

class CircleView: UIView {
    // simple view subclass that keeps itself "round"
    //  (assuming it has a 1:1 ratio)
    override func layoutSubviews() {
        layer.cornerRadius = bounds.width * 0.5
    }
}

CustomAssetCellOverlay

class CustomAssetCellOverlay: UIView {
    lazy var circleView: CircleView = {
        let view = CircleView()
        view.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    lazy var countLabel: UILabel = {
        let label = UILabel()
        let font = UIFont.preferredFont(forTextStyle: .headline)
        label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
        label.textAlignment = .center
        label.textColor = .white
        label.adjustsFontSizeToFitWidth = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    private func setup(){addSubview(circleView)
        addSubview(circleView)
        addSubview(countLabel)
        NSLayoutConstraint.activate([
            
            // circle view at top-left
            circleView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4.0),
            circleView.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
            // circle view Width: 28 Height: 1:1 ratio
            circleView.widthAnchor.constraint(equalToConstant: 28.0),
            circleView.heightAnchor.constraint(equalTo: circleView.widthAnchor),
            
            // count label constrained ot circle view
            countLabel.leadingAnchor.constraint(equalTo: circleView.leadingAnchor),
            countLabel.trailingAnchor.constraint(equalTo: circleView.trailingAnchor),
            countLabel.topAnchor.constraint(equalTo: circleView.topAnchor),
            countLabel.bottomAnchor.constraint(equalTo: circleView.bottomAnchor),
            
        ])
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
}

TestCVCell

class TestCVCell: UICollectionViewCell {
    
    var imageView = UIImageView()
    
    var index: Int = 0 {
        didSet {
            overlay.countLabel.text = "\(index)"
            // hide if count is Zero, show if not
            overlay.isHidden = index == 0
        }
    }
    
    let overlay: CustomAssetCellOverlay = {
        let view = CustomAssetCellOverlay()
        view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
        view.isHidden = true
        return view
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        contentView.addSubview(imageView)
        contentView.addSubview(overlay)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        overlay.translatesAutoresizingMaskIntoConstraints = false
        
        // constrain both image view and overlay to full contentView
        NSLayoutConstraint.activate([
            
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            
            overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
            overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
            overlay.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
            overlay.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
            
        ])
        
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

TrackSelectionsViewController

class TrackSelectionsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate {

    var myCollectionView: UICollectionView!
    
    // array to track selected cells in the order they are selected
    var selectedCells: [IndexPath] = []
    
    // to load assests when needed
    let imgManager = PHImageManager.default()
    let requestOptions = PHImageRequestOptions()

    // will be used to get photos data
    var fetchResult: PHFetchResult<PHAsset>!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // set main view background color to a nice medium blue
        view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
        
        // request Options to be used in cellForItemAt
        requestOptions.isSynchronous = false
        requestOptions.deliveryMode = .opportunistic

        // vertical stack view for the full screen (safe area)
        let mainStack = UIStackView()
        mainStack.axis = .vertical
        mainStack.spacing = 0
        mainStack.translatesAutoresizingMaskIntoConstraints = false
        
        // add it to the view
        view.addSubview(mainStack)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            mainStack.topAnchor.constraint(equalTo: g.topAnchor, constant:0.0),
            mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
        
        // create a label
        let label = UILabel()
        
        // add the label to the main stack view
        mainStack.addArrangedSubview(label)

        // label properties
        label.textColor = .white
        label.textAlignment = .center
        label.text = "Select Photos"
        label.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
        
        // setup the collection view
        setupCollection()
        
        // add it to the main stack view
        mainStack.addArrangedSubview(myCollectionView)
        
        // start the async call to get the assets
        grabPhotos()
    }
    
    func setupCollection() {
        let layout = UICollectionViewFlowLayout()
        myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        myCollectionView.delegate = self
        myCollectionView.dataSource = self
        myCollectionView.backgroundColor = UIColor.white
        myCollectionView.allowsMultipleSelection = true
        myCollectionView.register(TestCVCell.self, forCellWithReuseIdentifier: "cvCell")
    }
    
    //MARK: CollectionView
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
            // add newly selected cell (index path) to our tracking array
            selectedCells.append(indexPath)

            // when selecting a cell,
            //  we can update the appearance of the newly selected cell
            //  directly, because it won't affect any other cells
            cell.index = selectedCells.count
        }
    }

    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {

        // when de-selecting a cell,
        //  we can't update the appearance of the cell directly
        //  because if it's not the last cell selected, the other
        //  selected cells need to be re-numbered

        // get the index of the deselected cell from our tracking array
        guard let idx = selectedCells.firstIndex(of: indexPath) else { return }

        // remove from our tracking array
        selectedCells.remove(at: idx)

        // reloadData() clears the collection view's selected cells, so
        
        // get a copy of currently selected cells
        let curSelected: [IndexPath] = collectionView.indexPathsForSelectedItems ?? []
        
        // reload collection view
        //  we do this to update all cells' appearance,
        //  including re-numbering the currently selected cells
        collectionView.reloadData()

        // save current Y scroll offset
        let saveY = collectionView.contentOffset.y
        
        collectionView.performBatchUpdates({
            // re-select previously selected cells
            curSelected.forEach { pth in
                collectionView.selectItem(at: pth, animated: false, scrollPosition: .centeredVertically)
            }
        }, completion: { _ in
            // reset Y offset
            collectionView.contentOffset.y = saveY
        })

    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        guard fetchResult != nil else { return 0 }
        return fetchResult.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! TestCVCell
        
        imgManager.requestImage(for: fetchResult.object(at: indexPath.item) as PHAsset, targetSize: CGSize(width:120, height: 120),contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
            cell.imageView.image = image
        })

        // get the index of this indexPath from our tracking array
        //  if it's not there (nil), set it to -1
        let idx = selectedCells.firstIndex(of: indexPath) ?? -1
        
        // set .count property to index + 1 (arrays are zero-based)
        cell.index = idx + 1
        
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.frame.width
        return CGSize(width: width/4 - 1, height: width/4 - 1)
    }
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        myCollectionView.collectionViewLayout.invalidateLayout()
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 1.0
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 1.0
    }
    
    //MARK: grab photos
    func grabPhotos(){
        DispatchQueue.global(qos: .background).async {
            let fetchOptions = PHFetchOptions()
            fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
            self.fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
            if self.fetchResult.count == 0 {
                print("No photos found.")
            }
            DispatchQueue.main.async {
                self.myCollectionView.reloadData()
            }
        }
    }
    
}

注意:这只是示例代码!!!不应考虑“生产就绪。”