【问题标题】:Find the first cell with value "isUserInteractionEnabled == true" in UICollectionView在 UICollectionView 中找到值为“isUserInteractionEnabled == true”的第一个单元格
【发布时间】:2021-06-03 00:12:10
【问题描述】:

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

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

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

如果reservationsDataarray包含的值等于var dataarray,则相关单元格不能点击。

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

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

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

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

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

你能帮我理解一下吗?


TimeModel.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
    }
}

【问题讨论】:

    标签: ios arrays swift uicollectionview uicollectionviewcell


    【解决方案1】:

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

    更好的模型是自定义结构

    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 }
    

    【讨论】:

    • 感谢 vadian 我正在尝试实施您的建议。我应该在哪里使用 firstIndex? “问模型”而不是视图是什么意思?
    • 我正在尝试在layoutSubviewinitWithFrame 中调用let firstSavedItem = slots.first{ $0.isSaved },但我无法获得结果...
    • 如果我调用cellForItemAt 它会选择isUserInteractionEnabled 等于true 的所有单元格,而不仅仅是第一个.. 我只需要选择第一个单元格isUserInteractionEnabled == true 不是全部.. 所以此时我不知道我必须在 UIView 类中的哪个位置找到 firstIndex
    • 我同意 CZ54,shouldSelect(itemAtisUserInteractionEnabled 好得多。应用启动时的默认状态是什么?
    • 我会解释 .. 在我的问题中,我使用了一个例子来试图清楚.. 我害怕我的问题不清楚。实际上,我已经有一个模型,我可以在其中管理数据并在 collectionView 中将其可视化,这给了我一些时间(我认为您过去曾帮助过我)。在我的 collectionView 中获取时间后,用户可以选择时间并进行预订。
    【解决方案2】:

    简短的回答是您可以遍历集合视图的可见单元格,然后获取您正在寻找的单元格的索引路径。这样的事情可能会奏效:

    func getThatSpecialIndexIndex(collectionView: UICollectionView) -> IndexPath? {
        let cellsThatHaveUserInteractionEnabled = collectionView.visibleCells.filter { $0.isUserInteractionEnabled }
        let indexPaths = cellsThatHaveUserInteractionEnabled.compactMap { collectionView.indexPath(for: $0) }
        return indexPaths.min { left, right -> Bool in
            if left.section < right.section { return true }
            else if left.section > right.section { return false }
            else { return left.row < right.row }
        }
    }
    

    但问题在于它只会包含“可见”单元格。集合视图旨在重用单元格。这意味着作为UIView 子类的单元格实际上可以在不同时间占据不同的索引,具体取决于用户与集合视图的交互方式。

    话虽如此,最好使用您的数据源来查找您的索引路径。从您的代码中可以看出:

    cell.isUserInteractionEnabled == savedSlot.contains(slotArray[indexPath.item])
    

    所以基本上看起来你应该能够使用重新创建索引路径

    let index = slotArray.firstIndex { savedSlot.contains($0) }
    let indexPath: IndexPath(section: 0, row: index)
    

    【讨论】:

      【解决方案3】:

      您不应该在 UICollectionViewCell 上使用“isUserInteractionEnabled”。

      请尝试查看collectionView(_:shouldSelectItemAt:)

      它可以让您动态地允许/禁止点击单元格。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-20
        • 1970-01-01
        • 2018-11-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多