簡體   English   中英

如何在 Swift 中按時間跟蹤 CollectionView 單元格

[英]How to track a CollectionView cell by time in Swift

我一直在開發一項功能,以檢測用戶何時看到帖子以及何時沒有。 當用戶確實看到帖子時,我將單元格的背景變為綠色,如果沒有,則保持紅色。 現在,在這樣做之后,我注意到即使用戶只向下滾動頁面,我也將所有單元格打開為綠色,所以我添加了一個計時器,但我不明白如何正確使用它,所以我想我自己也許你們有給我一個建議,因為我有點堅持了兩天:(

  • 編輯:忘了提到一個單元格標記為是否通過了最小長度(即 2 秒)。

這是我的代碼:我的 VC(CollectionView):

import UIKit

class ViewController: UIViewController,UIScrollViewDelegate {
    
    var impressionEventStalker: ImpressionStalker?
    var impressionTracker: ImpressionTracker?
    
    var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"
    
    
    @IBOutlet weak var collectionView: UICollectionView!{
        didSet{
            collectionView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
            impressionEventStalker = ImpressionStalker(minimumPercentageOfCell: 0.70, collectionView: collectionView, delegate: self)

        }
    }
    
    
    
    func registerCollectionViewCells(){
        let cellNib = UINib(nibName: CustomCollectionViewCell.nibName, bundle: nil)
        collectionView.register(cellNib, forCellWithReuseIdentifier: CustomCollectionViewCell.reuseIdentifier)
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        collectionView.delegate = self
        collectionView.dataSource = self
        
        registerCollectionViewCells()
        
    }
    
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        impressionEventStalker?.stalkCells()
        
    }
    
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        impressionEventStalker?.stalkCells()
    }
    
}

// MARK: CollectionView Delegate + DataSource Methods
extension ViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 100
    }
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        guard let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.reuseIdentifier, for: indexPath) as? CustomCollectionViewCell else {
            fatalError()
        }
        
        customCell.textLabel.text = "\(indexPath.row)"
        
        if indexPathsOfCellsTurnedGreen.contains(indexPath){
            customCell.cellBackground.backgroundColor = .green
        }else{
            customCell.cellBackground.backgroundColor = .red
        }
        
        return customCell
    }
    
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150, height: 225)
    }
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) // Setting up the padding
    }
    
    
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        //Start The Clock:
        if let trackableCell = cell as? TrackableView {
            trackableCell.tracker = ImpressionTracker(delegate: trackableCell)
            trackableCell.tracker?.start()
            
        }
    }
    
    
    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        //Stop The Clock:
        (cell as? TrackableView)?.tracker?.stop()
    }
    
    
    
}


// MARK: - Delegate Method:
extension ViewController:ImpressionStalkerDelegate{
    func sendEventForCell(atIndexPath indexPath: IndexPath) {
        
        guard let customCell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell else {
            return
        }
        

        customCell.cellBackground.backgroundColor = .green
        indexPathsOfCellsTurnedGreen.append(indexPath) // We append all the visable Cells into an array
    }
}

我的印象追蹤者:

import Foundation
import UIKit

protocol ImpressionStalkerDelegate:NSObjectProtocol {
    func sendEventForCell(atIndexPath indexPath:IndexPath)
}

protocol ImpressionItem {
    func getUniqueId()->String
}


class ImpressionStalker: NSObject {
    
    //MARK: Variables & Constants
    let minimumPercentageOfCell: CGFloat
    weak var collectionView: UICollectionView?
    
    static var alreadySentIdentifiers = [String]()
    weak var delegate: ImpressionStalkerDelegate?
    
    
    //MARK: Initializer
    init(minimumPercentageOfCell: CGFloat, collectionView: UICollectionView, delegate:ImpressionStalkerDelegate ) {
            self.minimumPercentageOfCell = minimumPercentageOfCell
            self.collectionView = collectionView
            self.delegate = delegate
        }
    
    
    // Checks which cell is visible:
    func stalkCells() {
        for cell in collectionView!.visibleCells {
            if let visibleCell = cell as? UICollectionViewCell & ImpressionItem {
                let visiblePercentOfCell = percentOfVisiblePart(ofCell: visibleCell, inCollectionView: collectionView!)
                
                if visiblePercentOfCell >= minimumPercentageOfCell,!ImpressionStalker.alreadySentIdentifiers.contains(visibleCell.getUniqueId()){ // >0.70 and not seen yet then...
                    guard let indexPath = collectionView!.indexPath(for: visibleCell), let delegate = delegate else {
                        continue
                    }
                    
                   
                    delegate.sendEventForCell(atIndexPath: indexPath) // send the cell's index since its visible.
                    
                    ImpressionStalker.alreadySentIdentifiers.append(visibleCell.getUniqueId()) // to avoid double events to show up.
                }
            }
        }
    }
    
    
    // Func Which Calculate the % Of Visible of each Cell:
    private func percentOfVisiblePart(ofCell cell:UICollectionViewCell, inCollectionView collectionView:UICollectionView) -> CGFloat{
           
           guard let indexPathForCell = collectionView.indexPath(for: cell),
               let layoutAttributes = collectionView.layoutAttributesForItem(at: indexPathForCell) else {
                   return CGFloat.leastNonzeroMagnitude
           }
           
           let cellFrameInSuper = collectionView.convert(layoutAttributes.frame, to: collectionView.superview)
           
           let interSectionRect = cellFrameInSuper.intersection(collectionView.frame)
           let percentOfIntersection: CGFloat = interSectionRect.height/cellFrameInSuper.height
           
           return percentOfIntersection
       }
}

印象追蹤器:

import Foundation
import UIKit

protocol ViewTracker {

    init(delegate: TrackableView)
    func start()
    func pause()
    func stop()
    
}


final class ImpressionTracker: ViewTracker {
    private weak var viewToTrack: TrackableView?
        
    private var timer: CADisplayLink?
    private var startedTimeStamp: CFTimeInterval = 0
    private var endTimeStamp: CFTimeInterval = 0
    
     init(delegate: TrackableView) {
        viewToTrack = delegate
        setupTimer()
    }
    
    
    func setupTimer() {
        timer = (viewToTrack as? UIView)?.window?.screen.displayLink(withTarget: self, selector: #selector(update))
        timer?.add(to: RunLoop.main, forMode: .default)
        timer?.isPaused = true
    }
    
    
    func start() {
        guard viewToTrack != nil else { return }
        timer?.isPaused = false
        startedTimeStamp = CACurrentMediaTime() // Current Time in seconds.
    }
    
    func pause() {
        guard viewToTrack != nil else { return }
        timer?.isPaused = true
        endTimeStamp = CACurrentMediaTime()
        print("Im paused!")
    }
    
    func stop() {
        timer?.isPaused = true
        timer?.invalidate()
    }
    
    @objc func update() {
        guard let viewToTrack = viewToTrack else {
            stop()
            return
        }

        guard viewToTrack.precondition() else {
            startedTimeStamp = 0
            endTimeStamp = 0
            return
        }

        endTimeStamp = CACurrentMediaTime()
        trackIfThresholdCrossed()
    }
    
    
    private func trackIfThresholdCrossed() {
       
        guard let viewToTrack = viewToTrack else { return }
        let elapsedTime = endTimeStamp - startedTimeStamp
        if  elapsedTime >= viewToTrack.thresholdTimeInSeconds() {
            viewToTrack.viewDidStayOnViewPortForARound()
            

            startedTimeStamp = endTimeStamp
        }
    }
}

我的自定義單元:

import UIKit


protocol TrackableView: NSObject {
    var tracker: ViewTracker? { get set }
    func thresholdTimeInSeconds() -> Double //Takes care of the screen's time, how much "second" counts.
    func viewDidStayOnViewPortForARound() // Counter for how long the "Post" stays on screen.
    func precondition() -> Bool // Checks if the View is full displayed so the counter can go on fire.
}


class CustomCollectionViewCell: UICollectionViewCell {
    var tracker: ViewTracker?
    
    static let nibName = "CustomCollectionViewCell"
    static let reuseIdentifier = "customCell"
    
    @IBOutlet weak var cellBackground: UIView!
    @IBOutlet weak var textLabel: UILabel!
    
    var numberOfTimesTracked : Int = 0 {
        didSet {
            self.textLabel.text = "\(numberOfTimesTracked)"
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        cellBackground.backgroundColor = .red
        layer.borderWidth = 0.5
        layer.borderColor = UIColor.lightGray.cgColor
    }
    
    
    
    override func prepareForReuse() {
        super.prepareForReuse()
        print("Hello")
        tracker?.stop()
        tracker = nil

    }
}

extension CustomCollectionViewCell: ImpressionItem{
    func getUniqueId() -> String {
        return self.textLabel.text!
    }
}


extension CustomCollectionViewCell: TrackableView {
    func thresholdTimeInSeconds() -> Double { // every 2 seconds counts as a view.
        return 2
    }
    
    
    func viewDidStayOnViewPortForARound() {
        numberOfTimesTracked += 1 // counts for how long the view stays on screen.
    }
    
    
    
    func precondition() -> Bool {
        let screenRect = UIScreen.main.bounds
        let viewRect = convert(bounds, to: nil)
        let intersection = screenRect.intersection(viewRect)
        return intersection.height == bounds.height && intersection.width == bounds.width
    }
}

可能想要使用的方法...

在您發布的代碼中,您創建了一個“閱讀帖子”數組:

var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"

假設您的真實數據將具有多個屬性,例如:

struct TrackPost {
    var message: String = ""
    var postAuthor: String = ""
    var postDate: Date = Date()
    // ... other stuff
}

添加另一個屬性以跟蹤它是否已“看到”:

struct TrackPost {
    var message: String = ""
    var postAuthor: String = ""
    var postDate: Date = Date()
    // ... other stuff

    var hasBeenSeen: Bool = false
}

將所有“跟蹤”代碼移出 controller,而是將計時器添加到您的單元 class。

當單元格出現時:

  • 如果該單元格的數據的hasBeenSeenfalse
    • 啟動 2 秒計時器
    • 如果計時器到時,單元格已可見 2 秒,因此將hasBeenSeen設置為true (使用閉包或協議/委托模式告訴 controller 更新數據源)並更改背景顏色
    • 如果在計時器結束之前單元格滾動到屏幕外,請停止計時器並且不要告訴 controller 任何事情
  • 如果hasBeenSeen一開始就為true ,則不要啟動 2 秒計時器

現在,您的cellForItemAt代碼將如下所示:

    let p: TrackPost = myData[indexPath.row]
    
    customCell.authorLabel.text = p.postAuthor
    customCell.dateLabel.text = myDateFormat(p.postDate) // formatted as a string
    customCell.textLabel.text = p.message
    
    // setting hasBeenSeen in your cell should also set the backgroundColor
    //  and will be used so the cell knows whether or not to start the timer
    customCell.hasBeenSeen = p.hasBeenSeen
    
    // this will be called by the cell if the timer elapsed
    customCell.wasSeenCallback = { [weak self] in
        guard let self = self else { return }
        self.myData[indexPath.item].hasBeenSeen = true
    }

一個更簡單的方法怎么樣:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    for subview in collectionView!.visibleCells {
        if /* check visible percentage */ {
             if !(subview as! TrackableCollectionViewCell).timerRunning {
                  (subview as! TrackableCollectionViewCell).startTimer()
             }
        } else {
             if (subview as! TrackableCollectionViewCell).timerRunning {
                  (subview as! TrackableCollectionViewCell).stopTimer()
             }
        }
    }
}

通過以下方式擴展單元類:

class TrackableCollectionViewCell {
    
    static let minimumVisibleTime: TimeInterval = 2.0

    var timerRunning: Bool = true
    private var timer: Timer = Timer()

    func startTimer() {

        if timerRunning {
            return
        }

        timerRunning = true
        timer = Timer.scheduledTimer(withTimeInterval: minimumVisibleTime, repeats: false) { (_) in
            // mark cell as seen
        }
    }

    func stopTimer() {
         timerRunning = false
         timer.invalidate()
    }
    
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM