Reputation: 2773
Hello everyone first of all I apologize if my question may not be very clear I will be available for any clarification.
I have a UIView
class (TimeSelectorView) which contains a UICollectionView
which is populated with var data: [Section<TimeSelModel>] = []
.
I inserted another array called var reservationsData: [(Int, Int)] = []
which contains the cell selection by the user.
If the reservationsData
array
contains values equal to the var data
array
, the cells in question must not be clickable.
So far everything works but I have a problem .. I would like to set up an initial selection on the first available cell which has the value of isUserInteractionEnabled == true
How can I find the first available Index of the cell that owns isUserInteractionEnabled == true
outside the method
func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell?
I know that in the method init(frame: CGRect)
, the collectionView
has not been created yet so I can't get the cells index but at this point how can I solve?
I need that when the user opens the app they can see the selection available for the first cell with value isUserInteractionEnabled == true
Can you help me understand this?
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
}
}
Upvotes: 0
Views: 273
Reputation: 5588
You shouldn't messed up with "isUserInteractionEnabled" on a UICollectionViewCell.
Instead try to have a look on collectionView(_:shouldSelectItemAt:)
It will give you the possibility to dynamically allow/disallow the tap on the cell.
Upvotes: 0
Reputation: 16774
Short answer would be that you can iterate through visible cells of collection view and then get an index path of a cell that you are looking for. Something like this may do the trick:
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 }
}
}
But the problem with this is that it will only include "visible" cells. And collection view is designed to reuse cells. That means that a cell as subclass of UIView
can actually occupy different indices at different times depending on how user interacts with collection view.
That being said it is best to use your data source to find your index path. From your code it can be seen the following:
cell.isUserInteractionEnabled == savedSlot.contains(slotArray[indexPath.item])
So basically it seem like you should be able to recreate your index path by using
let index = slotArray.firstIndex { savedSlot.contains($0) }
let indexPath: IndexPath(section: 0, row: index)
Upvotes: 0
Reputation: 285082
Multiple arrays for the data source is pretty bad practice.
A better model is a custom struct
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
}
To find the first saved item ask the model, not the view
let firstSavedItem = slots.first{ $0.isSaved }
Upvotes: 1