I'm trying to make a progress bar act as a timer and count down from 15 seconds, here's my code:
我试图让进度条充当计时器并从15秒开始倒计时,这是我的代码:
private var timer: dispatch_source_t!
private var timeRemaining: Double = 15
override public func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
profilePicture.layer.cornerRadius = profilePicture.bounds.width / 2
let queue = dispatch_queue_create("buzz.qualify.client.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC, 5 * NSEC_PER_MSEC)
dispatch_source_set_event_handler(timer) {
self.timeRemaining -= 0.01;
self.timerBar.setProgress(Float(self.timeRemaining) / 15.0, animated: true)
print(String(self.timerBar.progress))
}
dispatch_resume(timer)
}
The print() prints the proper result, but the progress bar never updates, somestimes it will do a single update at around 12-15% full and just JUMP there and then do nothing else.
print()打印正确的结果,但进度条永远不会更新,有时它会在12-15%左右完成一次更新,只有JUMP然后再做其他事情。
How can I make this bar steadily flow down, and then execute a task at the end of the timer without blocking the UI thread.
如何使此栏稳定地向下流动,然后在计时器结束时执行任务而不阻止UI线程。
2 个解决方案
#1
5
In siburb's answer, he correctly points out that should make sure that UI updates happen on the main thread.
在siburb的回答中,他正确地指出应该确保在主线程上发生UI更新。
But I have a secondary observation, namely that you're doing 100 updates per second, and there's no point in doing it that fast because the maximum screen refresh rate is 60 frames per second.
但我有一个次要的观察,即你每秒进行100次更新,并且没有必要这么快,因为最大屏幕刷新率是每秒60帧。
However, a display link is like a timer, except that it's linked to the screen refresh rate. You could do something like:
但是,显示链接就像一个计时器,除了它链接到屏幕刷新率。你可以这样做:
var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime?
let duration = 15.0
func startDisplayLink() {
startTime = CFAbsoluteTimeGetCurrent()
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
func handleDisplayLink(displayLink: CADisplayLink) {
let percentComplete = Float((CFAbsoluteTimeGetCurrent() - startTime!) / duration)
if percentComplete < 1.0 {
self.timerBar.setProgress(1.0 - percentComplete, animated: false)
} else {
stopDisplayLink()
self.timerBar.setProgress(0.0, animated: false)
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
startDisplayLink()
}
Alternatively, if you're doing something on a background thread that wants to post updates to UIProgressView
faster than the main thread can service them, then I'd post that to the main thread using a dispatch source of type DISPATCH_SOURCE_TYPE_DATA_ADD
.
或者,如果你在后台线程上做一些事情,想要比主线程可以更快地发布更新到UIProgressView,那么我会使用DISPATCH_SOURCE_TYPE_DATA_ADD类型的调度源将它发布到主线程。
But, if you're just trying to update the progress view over some fixed period of time, a display link might be better than a timer.
但是,如果您只是尝试在一段固定的时间内更新进度视图,则显示链接可能比计时器更好。
#2
2
You must always update the UI on the main thread. I'm not a Swift expert, but it looks like you're currently trying to update the UIProgressView
in the background.
您必须始终更新主线程上的UI。我不是Swift专家,但看起来你正在尝试在后台更新UIProgressView。
Try updating the UIProgressView
on the main queue like this:
尝试更新主队列上的UIProgressView,如下所示:
dispatch_async(dispatch_get_main_queue(),^{
self.timerBar.setProgress(Float(self.timeRemaining) / 15.0, animated: true)
print(String(self.timerBar.progress))
})
#1
5
In siburb's answer, he correctly points out that should make sure that UI updates happen on the main thread.
在siburb的回答中,他正确地指出应该确保在主线程上发生UI更新。
But I have a secondary observation, namely that you're doing 100 updates per second, and there's no point in doing it that fast because the maximum screen refresh rate is 60 frames per second.
但我有一个次要的观察,即你每秒进行100次更新,并且没有必要这么快,因为最大屏幕刷新率是每秒60帧。
However, a display link is like a timer, except that it's linked to the screen refresh rate. You could do something like:
但是,显示链接就像一个计时器,除了它链接到屏幕刷新率。你可以这样做:
var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime?
let duration = 15.0
func startDisplayLink() {
startTime = CFAbsoluteTimeGetCurrent()
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
func handleDisplayLink(displayLink: CADisplayLink) {
let percentComplete = Float((CFAbsoluteTimeGetCurrent() - startTime!) / duration)
if percentComplete < 1.0 {
self.timerBar.setProgress(1.0 - percentComplete, animated: false)
} else {
stopDisplayLink()
self.timerBar.setProgress(0.0, animated: false)
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
startDisplayLink()
}
Alternatively, if you're doing something on a background thread that wants to post updates to UIProgressView
faster than the main thread can service them, then I'd post that to the main thread using a dispatch source of type DISPATCH_SOURCE_TYPE_DATA_ADD
.
或者,如果你在后台线程上做一些事情,想要比主线程可以更快地发布更新到UIProgressView,那么我会使用DISPATCH_SOURCE_TYPE_DATA_ADD类型的调度源将它发布到主线程。
But, if you're just trying to update the progress view over some fixed period of time, a display link might be better than a timer.
但是,如果您只是尝试在一段固定的时间内更新进度视图,则显示链接可能比计时器更好。
#2
2
You must always update the UI on the main thread. I'm not a Swift expert, but it looks like you're currently trying to update the UIProgressView
in the background.
您必须始终更新主线程上的UI。我不是Swift专家,但看起来你正在尝试在后台更新UIProgressView。
Try updating the UIProgressView
on the main queue like this:
尝试更新主队列上的UIProgressView,如下所示:
dispatch_async(dispatch_get_main_queue(),^{
self.timerBar.setProgress(Float(self.timeRemaining) / 15.0, animated: true)
print(String(self.timerBar.progress))
})