CADisplayLink 计时器无法正常工作 | Swift
CADisplayLink Timer Not working proper | Swift
我一直在用我的计时器,我的目标是知道用户看到 post 多长时间才能将其计为展示。我的意思是,如果您观看一个事件超过 3 秒,它将被计为印象。
现在由于某种原因,计时器没有按我预期的那样工作,老实说,它接近于我想要的工作了,这让我很害怕,因为我接近解决方案了。我的问题是,有时负责 StalkCells 的函数也会将显示时间不超过 3 秒的 posts 标记为“印象”或计数。
这是我的代码:首先是我的 VC:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var impressionEventStalker: ImpressionStalker?
var impressionTracker: ImpressionTracker?
var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"
var timer = Timer()
@IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
impressionEventStalker = ImpressionStalker(minimumPercentageOfCell: 0.75, 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.tracker = ImpressionTracker(delegate: customCell)
// print("Index: \(indexPath.row)")
customCell.tracker?.start()
customCell.textLabel.text = "\(indexPath.row)"
customCell.subLabel.text = "\(customCell.getVisibleTime())"
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: UIScreen.main.bounds.width - 40, height: 325)
}
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:
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//Stop The Clock:
(cell as? TrackableView)?.tracker?.stop()
}
func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
}
// 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 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.
}
// MARK: - Custome Cell Class:
class CustomCollectionViewCell: UICollectionViewCell {
var tracker: ViewTracker?
var indexPath : IndexPath?
static let nibName = "CustomCollectionViewCell"
static let reuseIdentifier = "customCell"
@IBOutlet weak var cellBackground: UIView!
@IBOutlet weak var textLabel: UILabel!
@IBOutlet weak var subLabel : UILabel!
func setup(_ index: IndexPath) {
self.indexPath = index
tracker?.start()
}
var numberOfTimesTracked : Double = 0 {
didSet {
self.subLabel.text = "\(numberOfTimesTracked)"
}
}
override func awakeFromNib() {
super.awakeFromNib()
cellBackground.backgroundColor = .red
layer.borderWidth = 0.5
layer.borderColor = UIColor.lightGray.cgColor
}
override func prepareForReuse() {
super.prepareForReuse()
tracker?.stop()
tracker = nil
}
}
// MARK: - ImpressionItem Delegate Methods:
extension CustomCollectionViewCell: ImpressionItem{
func getVisibleTime() -> Double {
return numberOfTimesTracked
}
func getUniqueId() -> String {
return self.textLabel.text!
}
}
// MARK: - TrackableView Delegate Methods:
extension CustomCollectionViewCell: TrackableView {
func thresholdTimeInSeconds() -> Double { // every 2 seconds counts as a view.
return 1
}
func viewDidStayOnViewPortForARound() {
numberOfTimesTracked = tracker?.getCurrTime() ?? 0 // counter for how long the cell stays on screen.
}
func precondition() -> Bool { // Checks when the cell is fully displayed so the timer can start.
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
}
}
我的 ImpressionStalker:
import Foundation
import UIKit
protocol ImpressionStalkerDelegate:NSObjectProtocol {
func sendEventForCell(atIndexPath indexPath:IndexPath)
}
protocol ImpressionItem {
func getUniqueId()->String
func getVisibleTime() -> Double
}
class ImpressionStalker: NSObject {
//MARK: Variables & Constants
let minimumPercentageOfCell: CGFloat
weak var collectionView: UICollectionView?
static var alreadySentIdentifiers = [String]() // All the cells IDs
weak var delegate: ImpressionStalkerDelegate?
//MARK: - Initializer
init(minimumPercentageOfCell: CGFloat, collectionView: UICollectionView, delegate:ImpressionStalkerDelegate ) {
self.minimumPercentageOfCell = minimumPercentageOfCell
self.collectionView = collectionView
self.delegate = delegate
}
//MARK: - Class Methods:
func stalkCells() {
for cell in collectionView!.visibleCells {
if let visibleCell = cell as? UICollectionViewCell & ImpressionItem {
if visibleCell.getVisibleTime() >= 3 {
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
}
print("%OfEachCell: \(visiblePercentOfCell) | CellID: \(visibleCell.getUniqueId()) | VisibleTime: \(visibleCell.getVisibleTime())")
delegate.sendEventForCell(atIndexPath: indexPath) // send the cell's index since its visible.
ImpressionStalker.alreadySentIdentifiers.append(visibleCell.getUniqueId())
// print(ImpressionStalker.alreadySentIdentifiers.count)
}
}
}
}
collectionView?.reloadData()
}
// Func Which Calculate the % Of Visible of each Cell:
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()
func getCurrTime() -> Double
}
final class ImpressionTracker: ViewTracker {
func getCurrTime() -> Double {
return numberOfTimesTracked
}
private weak var viewToTrack: TrackableView?
private var timer: CADisplayLink?
private var startedTimeStamp: CFTimeInterval = 0
private var endTimeStamp: CFTimeInterval = 0
var numberOfTimesTracked : Double = 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: .common)
timer?.isPaused = true
}
func start() {
guard viewToTrack != nil else { return }
timer?.isPaused = false
startedTimeStamp = CACurrentMediaTime() // Startup Time
}
func pause() {
guard viewToTrack != nil else { return }
timer?.isPaused = true
endTimeStamp = CACurrentMediaTime()
print("Im paused!")
}
func stop() {
timer?.isPaused = true
timer?.invalidate()
numberOfTimesTracked = 0
}
@objc func update() {
guard let viewToTrack = viewToTrack else {
stop()
return
}
guard viewToTrack.precondition() else {
startedTimeStamp = 0
endTimeStamp = 0
numberOfTimesTracked = 0
return
}
numberOfTimesTracked = endTimeStamp - startedTimeStamp
endTimeStamp = CACurrentMediaTime()
trackIfThresholdCrossed()
}
private func trackIfThresholdCrossed() {
guard let viewToTrack = viewToTrack else { return }
let elapsedTime = endTimeStamp - startedTimeStamp // total amount of passedTime.
if elapsedTime >= viewToTrack.thresholdTimeInSeconds() { // if its equal or greater than 1
// print("ElapsedTime: \(elapsedTime) | numberOfTimesTracked: \(numberOfTimesTracked)")
numberOfTimesTracked = Double(Int(elapsedTime))
viewToTrack.viewDidStayOnViewPortForARound()
// startedTimeStamp = endTimeStamp
}
}
}
如果要创建显示 link,通常只需调用 CADisplayLink(target:selector:)
。看到 CADisplayLink
documentation 表明它会是这样的:
weak var displayLink: CADisplayLink?
func createDisplayLink() {
self.displayLink?.invalidate() // cancel prior one, if any
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
print(displayLink.timestamp)
}
(因此,无需从视图向上导航到 window 到屏幕。只需创建显示 link 并将其添加到主 运行 循环中。如果您要保存对它的引用,我会称它为 displayLink
,而不是 timer
,以避免混淆。另外,我给该处理程序一个名称和参数,使它的目的不言而喻。)
但是让我们把它放在一边。问题是您 need/want 是否完全使用显示 link。 Display links 适用于必须以最佳方式绑定到屏幕刷新率的计时器(例如,它适用于更新 UI 的计时器,例如动画、类似秒表的文本字段等)。
那是低效的,尤其是每个单元格都这样做。您正在为每个单元格单独显示 link,每秒 60 次。如果您有 20 个可见单元格,那么您的方法每秒将被调用 1,200 次。相反,您可能每三秒对每个单元格进行一次调用。例如,如果您想知道某个单元格是否显示了 3 秒,您可以:
- 在显示单元格时创建一个非重复的三秒
Timer
(例如 willDisplay
);
invalidate
当单元格不再显示时 Timer
(例如在 didEndDisplaying
中),以及
- 如果计时器处理程序触发,则意味着单元格显示了三秒钟。
但它是 3 秒后的单个计时器事件,而不是每个单元每秒调用它 60 次。
我一直在用我的计时器,我的目标是知道用户看到 post 多长时间才能将其计为展示。我的意思是,如果您观看一个事件超过 3 秒,它将被计为印象。
现在由于某种原因,计时器没有按我预期的那样工作,老实说,它接近于我想要的工作了,这让我很害怕,因为我接近解决方案了。我的问题是,有时负责 StalkCells 的函数也会将显示时间不超过 3 秒的 posts 标记为“印象”或计数。
这是我的代码:首先是我的 VC:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var impressionEventStalker: ImpressionStalker?
var impressionTracker: ImpressionTracker?
var indexPathsOfCellsTurnedGreen = [IndexPath]() // All the read "posts"
var timer = Timer()
@IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
impressionEventStalker = ImpressionStalker(minimumPercentageOfCell: 0.75, 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.tracker = ImpressionTracker(delegate: customCell)
// print("Index: \(indexPath.row)")
customCell.tracker?.start()
customCell.textLabel.text = "\(indexPath.row)"
customCell.subLabel.text = "\(customCell.getVisibleTime())"
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: UIScreen.main.bounds.width - 40, height: 325)
}
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:
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//Stop The Clock:
(cell as? TrackableView)?.tracker?.stop()
}
func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
}
// 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 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.
}
// MARK: - Custome Cell Class:
class CustomCollectionViewCell: UICollectionViewCell {
var tracker: ViewTracker?
var indexPath : IndexPath?
static let nibName = "CustomCollectionViewCell"
static let reuseIdentifier = "customCell"
@IBOutlet weak var cellBackground: UIView!
@IBOutlet weak var textLabel: UILabel!
@IBOutlet weak var subLabel : UILabel!
func setup(_ index: IndexPath) {
self.indexPath = index
tracker?.start()
}
var numberOfTimesTracked : Double = 0 {
didSet {
self.subLabel.text = "\(numberOfTimesTracked)"
}
}
override func awakeFromNib() {
super.awakeFromNib()
cellBackground.backgroundColor = .red
layer.borderWidth = 0.5
layer.borderColor = UIColor.lightGray.cgColor
}
override func prepareForReuse() {
super.prepareForReuse()
tracker?.stop()
tracker = nil
}
}
// MARK: - ImpressionItem Delegate Methods:
extension CustomCollectionViewCell: ImpressionItem{
func getVisibleTime() -> Double {
return numberOfTimesTracked
}
func getUniqueId() -> String {
return self.textLabel.text!
}
}
// MARK: - TrackableView Delegate Methods:
extension CustomCollectionViewCell: TrackableView {
func thresholdTimeInSeconds() -> Double { // every 2 seconds counts as a view.
return 1
}
func viewDidStayOnViewPortForARound() {
numberOfTimesTracked = tracker?.getCurrTime() ?? 0 // counter for how long the cell stays on screen.
}
func precondition() -> Bool { // Checks when the cell is fully displayed so the timer can start.
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
}
}
我的 ImpressionStalker:
import Foundation
import UIKit
protocol ImpressionStalkerDelegate:NSObjectProtocol {
func sendEventForCell(atIndexPath indexPath:IndexPath)
}
protocol ImpressionItem {
func getUniqueId()->String
func getVisibleTime() -> Double
}
class ImpressionStalker: NSObject {
//MARK: Variables & Constants
let minimumPercentageOfCell: CGFloat
weak var collectionView: UICollectionView?
static var alreadySentIdentifiers = [String]() // All the cells IDs
weak var delegate: ImpressionStalkerDelegate?
//MARK: - Initializer
init(minimumPercentageOfCell: CGFloat, collectionView: UICollectionView, delegate:ImpressionStalkerDelegate ) {
self.minimumPercentageOfCell = minimumPercentageOfCell
self.collectionView = collectionView
self.delegate = delegate
}
//MARK: - Class Methods:
func stalkCells() {
for cell in collectionView!.visibleCells {
if let visibleCell = cell as? UICollectionViewCell & ImpressionItem {
if visibleCell.getVisibleTime() >= 3 {
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
}
print("%OfEachCell: \(visiblePercentOfCell) | CellID: \(visibleCell.getUniqueId()) | VisibleTime: \(visibleCell.getVisibleTime())")
delegate.sendEventForCell(atIndexPath: indexPath) // send the cell's index since its visible.
ImpressionStalker.alreadySentIdentifiers.append(visibleCell.getUniqueId())
// print(ImpressionStalker.alreadySentIdentifiers.count)
}
}
}
}
collectionView?.reloadData()
}
// Func Which Calculate the % Of Visible of each Cell:
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()
func getCurrTime() -> Double
}
final class ImpressionTracker: ViewTracker {
func getCurrTime() -> Double {
return numberOfTimesTracked
}
private weak var viewToTrack: TrackableView?
private var timer: CADisplayLink?
private var startedTimeStamp: CFTimeInterval = 0
private var endTimeStamp: CFTimeInterval = 0
var numberOfTimesTracked : Double = 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: .common)
timer?.isPaused = true
}
func start() {
guard viewToTrack != nil else { return }
timer?.isPaused = false
startedTimeStamp = CACurrentMediaTime() // Startup Time
}
func pause() {
guard viewToTrack != nil else { return }
timer?.isPaused = true
endTimeStamp = CACurrentMediaTime()
print("Im paused!")
}
func stop() {
timer?.isPaused = true
timer?.invalidate()
numberOfTimesTracked = 0
}
@objc func update() {
guard let viewToTrack = viewToTrack else {
stop()
return
}
guard viewToTrack.precondition() else {
startedTimeStamp = 0
endTimeStamp = 0
numberOfTimesTracked = 0
return
}
numberOfTimesTracked = endTimeStamp - startedTimeStamp
endTimeStamp = CACurrentMediaTime()
trackIfThresholdCrossed()
}
private func trackIfThresholdCrossed() {
guard let viewToTrack = viewToTrack else { return }
let elapsedTime = endTimeStamp - startedTimeStamp // total amount of passedTime.
if elapsedTime >= viewToTrack.thresholdTimeInSeconds() { // if its equal or greater than 1
// print("ElapsedTime: \(elapsedTime) | numberOfTimesTracked: \(numberOfTimesTracked)")
numberOfTimesTracked = Double(Int(elapsedTime))
viewToTrack.viewDidStayOnViewPortForARound()
// startedTimeStamp = endTimeStamp
}
}
}
如果要创建显示 link,通常只需调用 CADisplayLink(target:selector:)
。看到 CADisplayLink
documentation 表明它会是这样的:
weak var displayLink: CADisplayLink?
func createDisplayLink() {
self.displayLink?.invalidate() // cancel prior one, if any
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
print(displayLink.timestamp)
}
(因此,无需从视图向上导航到 window 到屏幕。只需创建显示 link 并将其添加到主 运行 循环中。如果您要保存对它的引用,我会称它为 displayLink
,而不是 timer
,以避免混淆。另外,我给该处理程序一个名称和参数,使它的目的不言而喻。)
但是让我们把它放在一边。问题是您 need/want 是否完全使用显示 link。 Display links 适用于必须以最佳方式绑定到屏幕刷新率的计时器(例如,它适用于更新 UI 的计时器,例如动画、类似秒表的文本字段等)。
那是低效的,尤其是每个单元格都这样做。您正在为每个单元格单独显示 link,每秒 60 次。如果您有 20 个可见单元格,那么您的方法每秒将被调用 1,200 次。相反,您可能每三秒对每个单元格进行一次调用。例如,如果您想知道某个单元格是否显示了 3 秒,您可以:
- 在显示单元格时创建一个非重复的三秒
Timer
(例如willDisplay
); invalidate
当单元格不再显示时Timer
(例如在didEndDisplaying
中),以及- 如果计时器处理程序触发,则意味着单元格显示了三秒钟。
但它是 3 秒后的单个计时器事件,而不是每个单元每秒调用它 60 次。