简体   繁体   中英

Select FirstIndex in UICollectionView

Hi all I'm working with a UICollectionView which shows 19 cells. The cells show a list of times

在此处输入图像描述


I manage my data through a model (TimeSelModel)

TimeSelModel.swift

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

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

let dataSec0 = [ // Dati della sezione 0
    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 = [ // Dati della sezione 1
    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)
]

var cellInteraction assigns the isUserInteractionEnable status of the cells of the collectionView

In the UIView class that contains the collectionView , I created a function for managing the interaction of the cells, that is, all the cells that contain a time lower than the current one must have cellInteraction == false

TimeSelView.swift

private var data: [Section<TimeSelModel>] = []


private func isPreviousTime(_ values: (Int, Int)) -> Bool {
    guard let time = Calendar.current.date(bySettingHour: values.0, minute: values.1, second: 0, of: Date()) else {
        // Non è stato possibile determinare l'orario prescelto
        return false
    }
    
    return time < Date()
}


// MARK: Update Cell Status
    private func updateCellStatus() {
        
        var sec: Int = 0
        var hour: Int = 0
        var minute: Int = 0
                        
        (0..<data.count).forEach { (section) in
            
            sec = section

            (0..<data[section].dataModel.count).forEach { (values) in
                
                hour = data[section].dataModel[values].hour
                minute = data[section].dataModel[values].minute
                
                data[section].dataModel[values].cellInteraction = isPreviousTime((hour, minute)) ? false : true
            }
        }
                
        cv.reloadData()
       
        // GET FIRST INDEX WHERE cellInteraction == TRUE

        if let index = data[sec].dataModel.firstIndex(where: { $0.cellInteraction }) {
            cv.selectItem(at: IndexPath(item: index, section: sec), animated: false, scrollPosition: [])

            print(sec)
            // SEC is always 1 .. this is wrong
        }
    }

As you can see from the image everything works fine but I have problems with the selection.

I need to get the firstIndex of the collectionView where cellInteraction == TRUE.

From the image I showed you should select the cell containing the time 11:30 instead continue to select the cell that contains 12:00 , this is because it always keeps returning section 1 inside the loop and I don't understand why

Where am I wrong at this point? why does it always return section 1 when it should (in this case) return section 0 and select the cell containing 11:30?


In the custom cell class I assign the value of cellInteraction to the isUserInteractionEnable of the cell

TimeSelCell.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)
    
    var dataModel: TimeSelModel! {
        didSet {
            
            hourLbl.text = String(format: "%02d", dataModel.hour)
            minuteLbl.text = ":" + String(format: "%02d", dataModel.minute)
            
            isUserInteractionEnabled = dataModel.cellInteraction
            contentView.alpha = dataModel.cellInteraction ? 1 : 0.5
        }
    }

You are only evaluating the last array in your data:

// MARK: Update Cell Status
private func updateCellStatus() {
    
    var sec: Int = 0
    var hour: Int = 0
    var minute: Int = 0
    
    (0..<data.count).forEach { (section) in
        
        // set sec equal to section, which will increment each
        //  time through this loop
        sec = section
        
        (0..<data[section].dataModel.count).forEach { (values) in
            
            hour = data[section].dataModel[values].hour
            minute = data[section].dataModel[values].minute
            
            data[section].dataModel[values].cellInteraction = isPreviousTime((hour, minute)) ? false : true
        }
    }
    
    cv.reloadData()

    // NOTE:
    //      
    // the var "sec" now equals data.count
    // 
    // 
    
    // GET FIRST INDEX WHERE cellInteraction == TRUE
    
    if let index = data[sec].dataModel.firstIndex(where: { $0.cellInteraction }) {
        cv.selectItem(at: IndexPath(item: index, section: sec), animated: false, scrollPosition: [])
        
        print(sec)
        // SEC is always 1 .. this is wrong
    }
}

So, when you exit that loop, you want to loop through the sections again, checking for the first match:

    cv.reloadData()

    // GET FIRST INDEX WHERE cellInteraction == TRUE

    var foundSection: Int = -1
    var foundIndex: Int = -1
    
    for section in 0..<data.count {
        if let index = data[section].dataModel.firstIndex(where: { $0.cellInteraction }) {
            foundSection = section
            foundIndex = index
            break
        }
    }

    // if section and index are "-1" (so you'll only need to check one of them)
    //  no object found with cellInteraction == true
    if foundSection == -1 {
        print("No objects found with cellInteraction == true!!!")
    } else {
        print("Found First -- Section:", foundSection, "Index:", foundIndex)
    }

Edit - after comments

To get the object with its hour/min closest to the current time (hour/min), create a new struct with the section, index, and time difference , then find the object with the smallest difference.

// MARK: Update Cell Status
private func updateCellStatus() {
    
    struct DiffStruct {
        var section: Int = 0
        var index: Int = 0
        var timeDiff: TimeInterval = 0
    }
    
    var tempArray: [DiffStruct] = []
    
    var hour: Int = 0
    var minute: Int = 0
    
    (0..<data.count).forEach { (section) in
        
        (0..<data[section].dataModel.count).forEach { (values) in
            
            hour = data[section].dataModel[values].hour
            minute = data[section].dataModel[values].minute
            
            // get a valid Date object
            if let time = Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: Date()) {
                // create a DiffStruct object, setting .timeDiff to
                //  this TimeSelModel's time minus the current time
                let ds = DiffStruct(section: section, index: values, timeDiff: time.timeIntervalSinceReferenceDate - curTime.timeIntervalSinceReferenceDate)
                // append it to the tempArray
                tempArray.append(ds)
                // set .cellInteraction to TRUE if .timeDiff is greater-than-or-equal to Zero
                data[section].dataModel[values].cellInteraction = ds.timeDiff >= 0
            } else {
                // could not create a Date from hour/minute
                //  so set .cellInteraction to false
                data[section].dataModel[values].cellInteraction = false
            }
        }
    }
    
    //cv.reloadData()

    // instead of:      
    //     GET FIRST INDEX WHERE cellInteraction == TRUE

    // we want to:
    //     Get the object where its time is CLOSEST to the current time
    //     without being LATER

    // use only Positive timeDiffs (so we're only looking at LATER times)
    let tmp: [DiffStruct] = tempArray.filter({ $0.timeDiff >= 0})
    // get the object with the smallest timeDiff
    if let first = tmp.min(by: { $0.timeDiff < $1.timeDiff } ) {
        print("Closest Match is Sec:", first.section, "Idx:", first.index)
    } else {
        print("No objects found with hour/min greater than current hour/min !!!")
    }

}

Edit 2 -- after more comments

I re-did the code to make it a little more clear what's going on (also avoided the extra .filter step):

// MARK: Update Cell Status
private func updateCellStatus(with curTime: Date) -> (section: Int, index: Int)? {
    
    struct DiffStruct {
        var section: Int = 0
        var index: Int = 0
        var timeDiff: TimeInterval = 0
    }
    
    var tempArray: [DiffStruct] = []
    
    (0..<data.count).forEach { (section) in
        
        (0..<data[section].dataModel.count).forEach { (index) in
            
            let thisModel = data[section].dataModel[index]
            
            let hour = thisModel.hour
            let minute = thisModel.minute
            
            // get a valid Date object
            if let time = Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: Date()) {
                
                // get difference between thisModel's time and curTime
                let diff = time.timeIntervalSinceReferenceDate - curTime.timeIntervalSinceReferenceDate
                
                if diff >= 0 {

                    // if diff is >= 0,
                    //  thisModel's time is LATER than curTime (or equal to)

                    // set .cellInteraction to TRUE if .timeDiff is greater-than-or-equal to Zero
                    data[section].dataModel[index].cellInteraction = true

                    // create a DiffStruct object
                    let ds = DiffStruct(section: section, index: index, timeDiff: diff)
                    
                    // append it to the tempArray
                    tempArray.append(ds)
                    
                } else {

                    // set .cellInteraction to FALSE
                    data[section].dataModel[index].cellInteraction = false
                    
                }

            } else {
                // could not create a Date from hour/minute
                //  so set .cellInteraction to false
                data[section].dataModel[index].cellInteraction = false
            }
            
        }
        
    }
    
    // we've udpated .cellInteraction for all models, so
    //cv.reloadData()
    
    // get the object with the smallest timeDiff
    if let first = tempArray.min(by: { $0.timeDiff < $1.timeDiff } ) {
        return (first.section, first.index)
    }
    
    // No objects found with hour/min greater than current hour/min !!!
    return nil

}

You can test that out like this:

class TestViewController: UIViewController {

    private var data: [Section<TimeSelModel>] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        data.append(Section(dataModel: dataSec0))
        data.append(Section(dataModel: dataSec1))
        
        // start at 09:23
        var currentTime: Date = Calendar.current.date(bySettingHour: 9, minute: 23, second: 0, of: Date())!
        
        for _ in 1...24 {
            
            let h = Calendar.current.component(.hour, from: currentTime)
            let m = Calendar.current.component(.minute, from: currentTime)
            
            if let p = updateCellStatus(with: currentTime) {
                let theModel = data[p.section].dataModel[p.index]
                print("Closest to \(h):\(m) is Section:", p.section, "Index:", p.index, "--", theModel.time)
            } else {
                print("\(h):\(m) is Later than all times!!!")
            }
            
            // add a half-hour
            currentTime = currentTime.addingTimeInterval(30 * 60)
            
        }

    }

    struct Section<T> { var dataModel: [T] }
    
    struct TimeSelModel {
        let hour: Int
        let minute: Int
        var time: String { "\(hour):\(minute)" }
        var cellInteraction: Bool = true
    }
    
    var dataSec0 = [ // Dati della sezione 0
        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)
    ]
    
    var dataSec1 = [ // Dati della sezione 1
        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)
    ]

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM