如何解决如何在 Swift 中按时间跟踪 CollectionView 单元格
我一直在开发一项功能,用于检测用户何时看到帖子以及何时未看到。当用户确实看到帖子时,我将单元格的背景变成绿色,如果没有,则保持红色。现在这样做之后,我注意到即使用户只向下滚动页面,我也将所有单元格都打开为绿色,所以我添加了一个计时器,但我无法理解如何正确使用它,所以我想自己也许你们有给我的一个建议,因为我有点坚持了两天:(
- 编辑:忘记提及如果单元格通过了 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,insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0,left: 20,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
}
}
我的 ImpressionStalker:
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
}
将所有“跟踪”代码移出控制器,而是将 Timer 添加到单元格类中。
当单元格出现时:
- 如果该单元格的数据的
hasBeenSeen
为false
- 启动 2 秒计时器
- 如果计时器超时,单元格已经可见 2 秒,因此将
hasBeenSeen
设置为true
(使用闭包或协议/委托模式告诉控制器更新数据源)和改变背景颜色 - 如果在计时器结束之前单元格在屏幕外滚动,请停止计时器并且不要告诉控制器任何事情
- 如果
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()
}
}
}
}
通过以下方式扩展 Cell-Class:
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()
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。