简体   繁体   English

如何在willDisplayCell回调中重新加载collectionView的单个单元格?

[英]How to reload a single cell of a collectionView in the willDisplayCell callback?

I have a collectionView that I am making a timeline of sorts out of. 我有一个collectionView,我正在制作各种各样的时间表。 There are many thin cells in a horizontal collectionView - each cell represents 1 day. 水平集合视图中有许多细胞 - 每个单元代表1天。 Here is the project code: https://github.com/AlexMarshall12/singleDayTimeline.git 这是项目代码: https//github.com/AlexMarshall12/singleDayTimeline.git

Here is the viewController: 这是viewController:

let cellIdentifier = "DayCollectionViewCell"
class ViewController: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate {

    @IBOutlet weak var button: UIButton!
    var dates = [Date?]()
    var startDate: Date?
    var endDate: Date?
    private var selectedIndexPath: IndexPath?

    @IBOutlet weak var daysCollectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        daysCollectionView.register(UINib.init(nibName: "DayCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellIdentifier)

        let allDates = Helper.generateRandomDate(daysBack: 900, numberOf: 10)
        self.dates = allDates.sorted(by: {
            $0!.compare($1!) == .orderedAscending
        })
        self.startDate = Calendar.current.startOfDay(for: dates.first as! Date)

        self.endDate = dates.last!
        self.dates = Array(dates.prefix(upTo: 1))
        daysCollectionView.delegate = self
        daysCollectionView.dataSource = self
    }

    var onceOnly = false

    internal func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if !onceOnly {
            //let lastDateIndexPath = IndexPath(row: dates.count - 1,section: 0)
            let lastDate = dates.last
            let lastDayIndex = lastDate!?.interval(ofComponent: .day, fromDate: startDate!)
            let lastDayCellIndexPath = IndexPath(row: lastDayIndex!, section: 0)
            self.daysCollectionView.scrollToItem(at: lastDayCellIndexPath, at: .left, animated: false)
            self.selectedIndexPath = lastDayCellIndexPath
            self.daysCollectionView.reloadItems(at: [lastDayCellIndexPath])

            onceOnly = true
        }
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        let days = self.endDate!.days(from: self.startDate!)
        if days <= 150 {
            return 150
        } else {
            print(days,"days")
            return days + 1
        }
    }

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

        let cellDate = Calendar.current.date(byAdding: .day, value: indexPath.item, to: self.startDate!)

        if let selectedRow = selectedIndexPath {
            cell.reloadCell(selectedRow==indexPath)
        } else {
            cell.reloadCell(false)
        }

        if Calendar.current.component(.day, from: cellDate!) == 15 {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "MMM"
            let monthString = dateFormatter.string(from: cellDate!)
            cell.drawMonth(month: monthString)
        }
        if Calendar.current.component(.day, from: cellDate!) == 1 && Calendar.current.component(.month, from: cellDate!) == 1 {
            print("drawYEAR")
            cell.drawYear(year:Calendar.current.component(.year, from: cellDate!))
        }
        if self.dates.contains(where: { Calendar.current.isDate(cellDate!, inSameDayAs: $0!) }) {
            print("same")
            cell.backgroundColor = UIColor.red
        }
        //cell.backgroundColor = UIColor.blue
        return cell
    }

    @IBAction func buttonPressed(_ sender: Any) {

        let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
        let randomDate = self.dates[randomIndex]
        let daysFrom = randomDate?.days(from: self.startDate!)
        let indexPath = IndexPath(row: daysFrom!, section: 0)
        self.selectedIndexPath = indexPath;

        //daysCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
        daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        daysCollectionView.reloadData()
    }

}

Note that when the buttonPressed function is Called, the "selectedIndexPath" changes. 请注意,当调用buttonPressed函数时,“selectedIndexPath”会更改。 Then the next time cellForItemAt is called, it does cell.reloadCell and if the indexPath is this selectedIndexPath, it unhides the arrowImageView (see below) 然后下次调用cellForItemAt时,它会执行cell.reloadCell,如果indexPath是这个selectedIndexPath,它将取消隐藏arrowImageView(见下文)

class DayCollectionViewCell: UICollectionViewCell {

    ...
    func reloadCell(_ isSelected:Bool){
        arrowImage.isHidden = !isSelected
    }

}

This works when the button is pressed, however I am trying to make it work in the willDisplayCell callback. 当按下按钮时,这可以工作,但是我试图让它在willDisplayCell回调中工作。 You can see that in this callback I basically just get the latest (most recent) date and scroll to it and then set SelectedIndexPath to that indexPath. 您可以看到在此回调中我基本上只获取最新(最近)的日期并滚动到它,然后将SelectedIndexPath设置为该indexPath。 Now I need a way to make the arrow draw on that cell. 现在我需要一种方法来在该单元格上绘制箭头。

Here is what it looks like now: 这是现在的样子: 在此输入图像描述

As you can see it doesn't show the arrow UNTIL you scroll around. 正如您所看到的那样,它不显示箭头直到你滚动。 I think what is happening is that when you scroll around enough cellForItemAt gets called which we know works. 我认为正在发生的事情是,当你滚动足够的cellForItemAt时,我们知道它被调用了。 But How do I get willDisplayCell to do the same thing successfully and thus have the arrow load from initial startup? 但是如何让willDisplayCell成功完成同样的事情,从而在初始启动时加载箭头?

In your viewDidLoad , you seem to be initializing dates , startDate and endDate . viewDidLoad中 ,您似乎正在初始化datestartDateendDate

So, right after those variables are set up, you can set the selectedIndexPath using the available data. 因此,在设置这些变量之后,您可以使用可用数据设置selectedIndexPath Then, when the cellForItem method is called, the code there would automatically handle the visibility of your arrow. 然后,当调用cellForItem方法时, 那里的代码将自动处理箭头的可见性。

Place this code right after you initialize the 3 variables, inside viewDidLoad . viewDidLoad中初始化3个变量后立即放置此代码。

if let startDate = startDate,
    let lastDayIndex = dates?.last?.interval(ofComponent: .day, fromDate: startDate) {
    selectedIndexPath = IndexPath(row: lastDayIndex, section: 0)
}

You would not need to handle this in willDisplayCell , or use the onceOnly boolean. 您不需要在willDisplayCell中处理此问题 ,也不需要使用onceOnly布尔值。

I add a new answer is because I noticed some incomplete details about the other answers. 我添加一个新的答案是因为我注意到其他答案的一些不完整的细节。

In the viewDidLoad , there is a weird code: 在viewDidLoad中,有一个奇怪的代码:

 self.dates = Array(dates.prefix(upTo: 1))

I think this is a test code. 我认为这是一个测试代码。 So the result would be only one bar totally. 所以结果只有一个吧。 And whatever the reason is, those codes for random one or last one should be added after this code. 无论原因是什么,应该在此代码之后添加随机的一个或最后一个的代码。

    let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
    let randomDate = self.dates[randomIndex]
    let daysFrom = randomDate?.days(from: self.startDate!)
    let indexPath = IndexPath(row: daysFrom!, section: 0)
    self.selectedIndexPath = indexPath;
//     daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)

Please notice I comment the last one. 请注意我评论最后一个。 If there is only one bar, it is good enough, but if there is more than one, the scrollToItem should be added somewhere else, because currently the collectionView has not been initialized. 如果只有一个bar,那就足够了,但如果有多个,则应将scrollToItem添加到其他位置,因为当前collectionView尚未初始化。

One idea place is just as original post in the willDisplay, Which is easy to implement. 一个想法的地方就像willDisplay中的原始帖子一样,易于实现。 The only issue is the time span of willDisplay is short, so reloading whole collectionData cannot be called there. 唯一的问题是willDisplay的时间跨度很短,因此无法在那里调用重新加载整个collectionData。

But it is good for scrolling to one indexPath there. 但是在那里滚动到一个indexPath是有好处的。 Just as the following code. 就像下面的代码一样。

 var onceOnly = false
 internal func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {

            if (!onceOnly){
            self.daysCollectionView.scrollToItem(at: self.selectedIndexPath!, at: .left, animated: false)
            onceOnly = true
            }
}

There is another good place to call both functions (selectIndex and scrolling) as in the following place : 还有另一个调用两个函数(selectIndex和scrolling)的好地方,如下所示:

    override  func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    let randomIndex = Int(arc4random_uniform(UInt32(self.dates.count)))
    let randomDate = self.dates[randomIndex]
    let daysFrom = randomDate?.days(from: self.startDate!)
    let indexPath = IndexPath(row: daysFrom!, section: 0)
    if self.selectedIndexPath != nil {
      daysCollectionView.reloadItems(at: [self.selectedIndexPath!])
    }
    self.selectedIndexPath = indexPath;
     daysCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
     daysCollectionView.reloadItems(at: [indexPath])
}

Now I think it is a complete answer. 现在我认为这是一个完整的答案。 Hope it is helpful. 希望它有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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