首页 > 解决方案 > 在 UICollectionView 中找到值为“isUserInteractionEnabled == true”的第一个单元格

问题描述

大家好,首先我很抱歉,如果我的问题可能不是很清楚,我将提供任何澄清。

我有一个UIView类(TimeSelectorView),其中包含一个UICollectionView填充有var data: [Section<TimeSelModel>] = [] .

我插入了另一个名为的数组var reservationsData: [(Int, Int)] = [],其中包含用户选择的单元格。

如果reservationsData array包含的值等于var data array,则有问题的单元格一定是不可点击的。

到目前为止一切正常,但我有一个问题..我想在第一个可用的单元格上设置一个初始选择,其值为isUserInteractionEnabled == true

如何找到在isUserInteractionEnabled == true方法之外拥有的单元格的第一个可用索引

func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell?

我知道在方法init(frame: CGRect)中,collectionView尚未创建,所以我无法获取单元格索引,但此时我该如何解决?

我需要当用户打开应用程序时,他们可以看到第一个具有值的单元格可用的选择isUserInteractionEnabled == true

你能帮我理解这个吗?


时间模型.swift

import Foundation

struct Section<T> { let dataModel: [T] }

struct TimeSelModel {
    let hour: Int
    let minute: Int
    var time: String { "\(hour):\(minute)" }
}

let dataSec0 = [
    TimeSelModel(hour: 09, minute: 30),
    TimeSelModel(hour: 10, minute: 00),
    TimeSelModel(hour: 10, minute: 30),
    TimeSelModel(hour: 11, minute: 00),
    TimeSelModel(hour: 11, minute: 30),
    TimeSelModel(hour: 15, minute: 00),
    TimeSelModel(hour: 15, minute: 30),
    TimeSelModel(hour: 16, minute: 00),
    TimeSelModel(hour: 16, minute: 30),
    TimeSelModel(hour: 17, minute: 00)
]

let dataSec1 = [
    TimeSelModel(hour: 12, minute: 00),
    TimeSelModel(hour: 12, minute: 30),
    TimeSelModel(hour: 13, minute: 00),
    TimeSelModel(hour: 14, minute: 00),
    TimeSelModel(hour: 14, minute: 30),
    TimeSelModel(hour: 17, minute: 30),
    TimeSelModel(hour: 18, minute: 00),
    TimeSelModel(hour: 18, minute: 30),
    TimeSelModel(hour: 19, minute: 00)
]

TimeSelectorView.swift

private var data: [Section<TimeSelModel>] = []
private var reservationsData: [(Int, Int)] = []
private var listern: ListenerRegistration!

override init(frame: CGRect) {
    super.init(frame: frame)
    commonInit()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    commonInit()
}

private func commonInit() -> Void {
    fetchData()
    getReservationData(for: .currentDay)
    selectAvaiability()
}

private func fetchData() -> Void {
     data = [Section(dataModel: dataSec0), Section(dataModel: dataSec1)] }

private func getSection() -> Int {
    let interval1 = Date().checkTimeBetweenInterval(from: (9, 0), to: (11, 30))
    let interval2 = Date().checkTimeBetweenInterval(from: (14, 30), to: (17, 0))
   
    return interval1 || interval2 ? 0 : 1
}
    

private func selectAvaiability() -> Void {
     let section = getSection()

     // Select the first cell with time = or > to the current time and which does not have a value of isUserInteraceEnabled == false
    //cv.selectItem(at: IndexPath(item: ????, section: section), animated: false, scrollPosition: [])
}

// MARK: - UICollectionView Datasource
// MARK: -

extension TimeSelView: UICollectionViewDataSource {
    
    func numberOfSections(in collectionView: UICollectionView) -> Int { data.count }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        data[section].dataModel.count }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimeSelCell.cellID, for: indexPath) as! TimeSelCell
        
        var cellData = data[indexPath.section].dataModel
        cell.dataModel = cellData[indexPath.item]

        if isCurrentDate {
            
            cell.status =
                Date().checkTime(hour: cellData[indexPath.item].hour, minute: cellData[indexPath.item].minute) || Date().isClosingDay ||
                isAReservations(data: (cellData[indexPath.item].hour, cellData[indexPath.item].minute)) ? .disabled : .enabled
        
         }
            
        return cell

        }

    private func isAReservations(data: (Int, Int)) -> Bool {
        reservationsData.contains(where: { $0.0 == data.0 && $0.1 == data.1}) }

 }

extension TimeSelView {
    
    // MARK: Query Periods
    // Gestione del periodo da considerare per la query
    enum Periods {
        case currentDay, future
        
        var query: Query {
            
            let calendar = Calendar.current
            let components = calendar.dateComponents ([.year, .month, .day], from: Date ())
            let startToday = calendar.date (from: components)!
            let endToday = calendar.date (byAdding: .day, value: 1, to: startToday)!
            
            let rootQuery = Service.Database.reservationRoot
            
            switch self {
            
            case .currentDay: return rootQuery.whereField("date", isGreaterThan: startToday).whereField("date", isLessThan: endToday)
            case .future : return rootQuery.whereField("date", isGreaterThan: endToday)
                
            }
        }
    }
    
    // MARK: - Reservation Query
    // Recuperiamo tutte le prenotazioni effettuate per un periodo specifico
    private func getReservationsDataFromQuery(periods: Periods, completed: @escaping () ->()) -> Void {
        
        // Effetuiamo una query in base al perido in cui ci troviamo ovvero giorno corrente oppure giorni futuri
        // Aggiungiamo un ascoltatore per monitorare tutti i cambiamenti che vengono effettuati al database durante le prenotazioni degli uitenti in modo tale da mantenere aggiornata la collectionView
        listern = periods.query.addSnapshotListener { (querySnapshot, error) in
            
            guard error == nil else  {
                print("Errore durante il recupero delle prenotazioni \(error!.localizedDescription)")
                return }
            
            for document in querySnapshot!.documents {
                                   
                // Una volta ottenuti i dati andiamo a recuperare solo i valori di orario che verranno aggiunti all'array che gestisce lo stato delle celle della collectionView
                if let timestamp = document.data()["date"] as? Timestamp {
                    let hour = timestamp.dateValue().component(.hour)
                    let minute = timestamp.dateValue().component(.minute)
                    // Aggiunta dei dati all'array
                    self.prepareReservationsData((hour, minute))
                    completed()

                }
            }
        }
    }
    
    // MARK: -
    private func getReservationData(for periods: Periods) -> Void {
        // Controlla lo stato dell'utente per rimuovere l'ascoltatore della query quando non abbiamo bisogno di interpellare il database altrimenti effettua la query
        NotificationCenter.default.addObserver(forName:.AuthStateDidChange, object: Auth.auth(), queue: nil) { _ in
            
            /// Service.currentUser == nil -> Rimuove l'ascoltatore
            /// Service.currentUser != nil -> Effettua la query e ricalcola la collectionView con i nuovi dati
            if  Service.currentUser == nil { self.listern.remove() }
            else {
                self.getReservationsDataFromQuery(periods: periods) {
                    self.cv.reloadData()
                }
            }
        }
    }
    
    private func prepareReservationsData( _ value: (Int, Int)) -> Void {
        reservationsData += [(value.0, value.1)]
    }
    
 }

TimeSelectorCell.swift

class TimeSelCell: UICollectionViewCell {
    
    static let cellID = "time.selector.cell.identifier"
    
    private let minuteLbl = UILabel(font: .systemFont(ofSize: 13), textColor: .white, textAlignment: .center)
    
    private let hourLbl = UILabel(font: .boldSystemFont(ofSize: 17), textColor: .white, textAlignment: .center)
    
    // Tiene traccia dello stato della cella
    /// "disabled" -> Le celle non sono utilizzabili e il valore all'interno è barrato
    /// "enabled" -> Le celle sono utilizzabili per la prenotazione
    
    enum Status { case disabled, enabled }

    var dataModel: TimeSelModel! {
        
        didSet {
            
            hourLbl.text = String(format: "%02d", dataModel.hour)
            minuteLbl.text = ":" + String(format: "%02d", dataModel.minute)
        }
    }
    
    var status: Status {
       
        didSet {
                        
            switch status {
            
            case .disabled :
                isUserInteractionEnabled = false
            case .enabled :
                isUserInteractionEnabled = true
                
            }
            
            hourLbl.attributedText = crossedOut(text: hourLbl.text!)
            minuteLbl.attributedText = crossedOut(text: minuteLbl.text!)

            contentView.alpha = isUserInteractionEnabled ? 1 : 0.5
        }
    }
    
    override var isSelected: Bool {
        
        didSet {
            
            UIView.animate(withDuration: 0.3) {
                self.backgroundColor = self.isSelected ? K.Colors.greenAlpha : .systemFill }
            self.layer.borderColor = self.isSelected ? K.Colors.green.cgColor : UIColor.clear.cgColor
            self.layer.borderWidth = self.isSelected ? 1 : 0
        }
    }
    
    var isPreviousDate: Bool = false
    var isCurrentDate: Bool = true
    
    override init(frame: CGRect) {
        self.status = .disabled

        super.init(frame: frame)
        
        backgroundColor = .systemFill
        layer.cornerRadius =  3
        
        contentView.addSubview(hourLbl)
        contentView.addSubview(minuteLbl)
        
        hourLbl.anchor(top: contentView.topAnchor, leading: contentView.leadingAnchor, bottom: contentView.centerYAnchor, trailing: contentView.trailingAnchor, padding: .init(top: 9, left: 0, bottom: -8, right: 0))

        minuteLbl.anchor(top: contentView.centerYAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, padding: .init(top: -5, left: 0, bottom: 7, right: 0))
        
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

private extension TimeSelCell {
    
    func crossedOut(text: String) -> NSMutableAttributedString {
        let attributeString: NSMutableAttributedString =  NSMutableAttributedString(string: "\(text)")
        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value:  isUserInteractionEnabled ? 0 : 1, range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
}

标签: iosarraysswiftuicollectionviewuicollectionviewcell

解决方案


数据源的多个数组是非常糟糕的做法。

更好的模型是自定义结构

struct Slot {
    let name : String
    var isSaved = false
}

var slots = [Slot(name: "slot1"), Slot(name: "slot2"), Slot(name: "slot3"), Slot(name: "slot4"), Slot(name: "slot5"), Slot(name: "slot6")]

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { slots.count }

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SlotCell.cellID, for: indexPath) as! SlotCell

    let slot = slots[indexPath.item]
    cell.lbl.text = slot.name
    cell.isUserInteractionEnabled = !slot.isSaved
    return cell
}
 

要找到第一个保存的项目,请询问模型,而不是视图

let firstSavedItem = slots.first{ $0.isSaved }

推荐阅读