delay/sleep in Swift is not working - ios

I have a problem with the sleep function in Swift code. I'm using import Darwin and usleep(400000). Some actions before reaching the sleep are blocked and I dont know why. Here a short example from my code:
#IBAction func Antwort4Button(_ sender: Any) {
if (richtigeAntwort == "4"){
Antwort4.backgroundColor = UIColor.green
Ende.text = "Richtig!"
NaechsteFrage()
}
else {
Ende.text = "Falsch!"
//NaechsteFrage()
}
}
func NaechsteFrage() {
usleep(400000)
Antwort1.backgroundColor = UIColor.red
Antwort2.backgroundColor = UIColor.red
Antwort3.backgroundColor = UIColor.red
Antwort4.backgroundColor = UIColor.red
Ende.text = ""
FragenSammlung()
}
This lines will be not executed:
Antwort4.backgroundColor = UIColor.green
Ende.text = "Richtig!"
Why is calling sleep blocking these actions? If I delete the import Darwin and the sleep, my code works fine. Has anyone an idea? Sorry for my bad english :P

like #jcaron said
here the code to do it:
func delay(delayTime: Double, completionHandler handler:#escaping () -> ()) {
let newDelay = DispatchTime.now() + delayTime
DispatchQueue.main.asyncAfter(deadline: newDelay, execute: handler)
}
Edited: you can create a viewController extension to use in any viewControllers like this:
extension UIViewController {
func delay(delayTime: Double, completionHandler handler:#escaping () -> ()) {
let newDelay = DispatchTime.now() + delayTime
DispatchQueue.main.asyncAfter(deadline: newDelay, execute: handler)
}
}
So in your viewController you just call like this:
delay(delayTime: 2, completionHandler: {
_ in
// do your code here
})

Others alrady answered the question, I just wanted to provide some additional information (cannot comment yet).
You said that Antwort4.backgroundColor = UIColor.green is not executed. To clarify, this is executed, but you don't see the result becuase you call sleep, which is blocking the UI. Here is what happens:
set background color of Antwort4 to green
sleep: block the UI which prevents the app from actually showing the green background
set the background color of Antwort4 to red again
To solve the problem at hand you can use Apples Displatch API. So instead of sleept, you could use:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.Antwort1.backgroundColor = UIColor.red
self.Antwort2.backgroundColor = UIColor.red
self.Antwort3.backgroundColor = UIColor.red
self.Antwort4.backgroundColor = UIColor.red
self.Ende.text = ""
self.FragenSammlung()
}

The usleep() function will block your UI. If you don't want to have this behavior better use the Timer class.

Related

How to 10 second forward or backward in Spotify player

I am trying to add(move forward) 10 second song duration or minus(move backward) 10 second in Spotify player but i am really confused how to add or minus.
When i m trying to use this code the song is not changed duration
// forward button action
#IBAction func moveFrdBtnAction(_ sender: Any) {
SpotifyManager.shared.audioStreaming(SpotifyManager.shared.player, didSeekToPosition: TimeInterval(10))
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
player?.seek(to: position, callback: { (error) in
let songDuration = audioStreaming.metadata.currentTrack?.duration as Any as! Double
self.delegate?.getSongTime(timeCount: Int(songDuration)+1)
})
}
We are making a music application using the same SDK in both the platforms (Android & iOS), the seekToPosition method of the Spotify SDK is working correctly in the Android version, however, it is not working in the iOS one.The delegate method calls itself but the music stops.
Can you kindly let us know why this scenario is happening, and what should we do to run it on the iOS devices as well.
Can someone please explain to me how to solve this , i've tried to solve this but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I don't use this API so my answer will be based your code and Spotify's reference documentation.
I think there are a few things wrong with your flow:
As Robert Dresler commented, you should (approximately) never call a delegate directly, a delegate calls you.
I'm pretty sure your action currently results in jumping to exactly 10 seconds, not by 10 seconds.
(As an aside, I'd suggest changing the name of your function moveFrdBtnAction to at least add more vowels)
Anyway, here's my best guess at what you want:
// forward button action
#IBAction func moveForwardButtonAction(_ sender: Any) {
skipAudio(by: 10)
}
#IBAction func moveBackButtonAction(_ sender: Any) {
skipAudio(by: -10)
}
func skipAudio(by interval: TimeInterval) {
if let player = player {
let position = player.playbackState.position // The documentation alludes to milliseconds but examples don't.
player.seek(to: position + interval, callback: { (error) in
// Handle the error (if any)
})
}
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
// Update your UI
}
Note that I have not handled seeking before the start of the track, nor after the end which could happen with a simple position + interval. The API may handle this for you, or not.
You could take a look at the examples here: spotify/ios-sdk. In the NowPlayingView example they use the 'seekForward15Seconds', maybe you could use that? If you still need 10s I have added a function below. The position is in milliseconds.
"position: The position to seek to in milliseconds"
docs
ViewController.swift
var appRemote: SPTAppRemote {
get {
return AppDelegate.sharedInstance.appRemote
}
}
fileprivate func seekForward15Seconds() {
appRemote.playerAPI?.seekForward15Seconds(defaultCallback)
}
fileprivate seekBackward15Seconds() {
appRemote.playerAPI?.seekBackward15Seconds(defaultCallback)
}
// TODO: Or you could try this function
func seekForward(seconds: Int){
appRemote.playerAPI?.getPlayerState({ (result, error) in
// playback position in milliseconds
let current_position = self.playerState?.playbackPosition
let seconds_in_milliseconds = seconds * 1000
self.appRemote.playerAPI?.seek(toPosition: current_position + seconds_in_milliseconds, callback: { (result, error) in
guard error == nil else {
print(error)
return
}
})
})
}
var defaultCallback: SPTAppRemoteCallback {
get {
return {[weak self] _, error in
if let error = error {
self?.displayError(error as NSError)
}
}
}
}
AppDelegate.swift
lazy var appRemote: SPTAppRemote = {
let configuration = SPTConfiguration(clientID: self.clientIdentifier, redirectURL: self.redirectUri)
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return appRemote
}()
class var sharedInstance: AppDelegate {
get {
return UIApplication.shared.delegate as! AppDelegate
}
}
Edit1:
For this to work you need to follow the Prepare Your Environment:
Add the SpotifyiOS.framework to your Xcode project
Hope it helps!

How to stop a Function from Running in Swift 3.0

I have a function that starts playing an animation that is running asynchronously (in the background). This animation is called indefinitely using a completion handler (see below). Is there a way to close this function upon pressing another button?
Here is my code:
func showAnimation() {
UIView.animate(withDuration: 1, animations: {
animate1(imageView: self.Anime, images: self.animation1)
}, completion: { (true) in
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.showAnimation() // replay first showAnimation
}
})
}
Then upon pressing another button we closeout the above function
showAnimation().stop();
Thanks
You can add a property to the class to act as a flag indicating whether the animation should be run or not.
var runAnimation = true
func showAnimation() {
if !runAnimation { return }
UIView.animate(withDuration: 1, animations: {
animate1(imageView: self.Anime, images: self.animation1)
}, completion: { (true) in
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
if runAnimation {
self.showAnimation() // replay first showAnimation
}
}
})
}
Then in the button handler to stop the animation you simply do:
runAnimation = false
Note that this does not stop the currently running 1 second animation. This just prevent any more animations.
There are a lot of ways to do this. The simplest is to have a Boolean property (which you should make properly atomic) that you check in your asyncAfter block, and don't just don't call showAnimation() again if it's true.
Another thing you can do, and what I like to do for more complex tasks, is to use OperationQueue instead of DispatchQueue. This allows you to cancel operations, either individually or all at once, or even suspend the whole queue (obviously don't suspend the main queue or call removeAllOperations() on it, though, since there may be other operations in there unrelated to your code).
You can provide a variable outside of your function, then observe its value and handle your task. I can give you a solution:
class SomeClass {
private var shouldStopMyFunction: Bool = false // keep this private
public var deadline: TimeInterval = 0
func stopMyFunction() {
shouldStopMyFunction = true
}
func myFunction(completionHanlder: #escaping (String)->()) {
// -------
var isTaskRunning = true
func checkStop() {
DispatchQueue.global(qos: .background).async {
if self.shouldStopMyFunction, isTaskRunning {
isTaskRunning = false
completionHanlder("myFunction is forced to stop! 😌")
} else {
//print("Checking...")
checkStop()
}
}
}
checkStop()
// --------
// Start your task from here
DispatchQueue.global().async { // an async task for an example.
DispatchQueue.global().asyncAfter(deadline: .now() + self.deadline, execute: {
guard isTaskRunning else { return }
isTaskRunning = false
completionHanlder("My job takes \(self.deadline) seconds to finish")
})
}
}
}
And implement:
let anObject = SomeClass()
anObject.deadline = 5.0 // seconds
anObject.myFunction { result in
print(result)
}
let waitingTimeInterval = 3.0 // 6.0 // see `anObject.deadline`
DispatchQueue.main.asyncAfter(deadline: .now() + waitingTimeInterval) {
anObject.stopMyFunction()
}
Result with waitingTimeInterval = 3.0: myFunction is forced to stop! 😌
Result with waitingTimeInterval = 6.0: My job takes 5.0 seconds to finish

Quickly updating UIView background color in Swift?

I have an array of times for when the background color of a UIView should change colors. The array holds values from 0-10000ms, where each index is the duration between the first change and the current change. I have implemented a loop to execute the tasks at the scheduled times, and using print statements, it seems to be working. However, the background color only changes to the last color instead of changing continuously.
I believe this is because the background color does not update until the loop is done. Is this correct, and if so, how can I fix this?
sendingView.backgroundColor = UIColor.black
let startingTime = getCurrentMillis()
var found = false
for time in receivingMsgData {
while(!found) {
let curDuration = getCurrentMillis() - startingTime
if curDuration > time {
if(sendingView.backgroundColor == UIColor.black) {
sendingView.backgroundColor = UIColor.white
print("playing at \(getCurrentMillis()-startingTime) turning white")
} else {
sendingView.backgroundColor = UIColor.black
print("playing at \(getCurrentMillis()-startingTime) turning black")
}
found = true
}
}
found = false
}
Try something like this
DispatchQueue.global(qos: .userInitiated).async {
let delay:TimeInterval = 0.01 // Seconds
for index in 0..<100 {
if index % 2 == 0 {
Thread.sleep(forTimeInterval: delay)
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.white
print("white")
}
}else{
Thread.sleep(forTimeInterval: delay)
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.black
print("black")
}
}
}
}
Do your action in async thread and change the color on main thread
You doing to much in main thread causing it to hang, aslo put some delay so that user can view the animation
try something like this in your case
let delay:TimeInterval = 0.01 // Seconds
sendingView.backgroundColor = UIColor.black
let startingTime = getCurrentMillis()
var found = false
DispatchQueue.global(qos: .userInitiated).async {
for time in receivingMsgData {
while(!found) {
let curDuration = getCurrentMillis() - startingTime
if curDuration > time {
if(sendingView.backgroundColor == UIColor.black) {
Thread.sleep(forTimeInterval: delay)
DispatchQueue.main.async {
sendingView.backgroundColor = UIColor.white
print("playing at \(getCurrentMillis()-startingTime) turning white")
}
} else {
Thread.sleep(forTimeInterval: delay)
DispatchQueue.main.async {
sendingView.backgroundColor = UIColor.black
print("playing at \(getCurrentMillis()-startingTime) turning black")
}
}
found = true
}
}
found = false
}
}
The code you posted either blocks the main thread or the operation is not on the main thread when setting the color. Both of the situations will most likely result in no changes until the execution of the whole loop is done or after some time even.
When you are dealing with data that are scheduled at some time which may be irrelevant to the speed of the screen update you need to give the color on demand when the screen will refresh.
In your case I would suggest using a display link CADisplayLink which is designed to trigger whenever the screen should refresh. You can even get the current time value since the beginning from the display link itself. That elapsed time can be used the same way you are already using curDuration in your code. Assuming that the rest of the code works fine that is.
I believe this is because the background color does not update until the loop is done. Is this correct
Yes, it is. Drawing doesn't happen until all your code on the main thread finishes and the CATransaction is committed. You would need to do one of two things:
Run your time-consuming code on a background thread, coming to the main thread only to talk to the interface and set the color.
Use some form of delay to get off the main thread long enough to let drawing take place before continuing with your "loop".

How to hide UICollectionView Cell in ios after specific time. eg. Half Hour, 7 week etc

I going to hide a cell after half an hour time interval from populating time. If app is in not running state then also performs the same operation. Please Help me. Thank's in advance.
Here is the function delay that can help you to process delays even in background state (delay will be processed immediately after app becomes active if time is ok). And with this code you can easy cancel this delay if needed But this solution will not work for the situation when the app is not running (for this case I will give another solution):
import Foundation
import UIKit
typealias dispatch_cancelable_closure = (_ cancel : Bool) -> Void
#discardableResult
func delay(_ time:TimeInterval, closure: #escaping ()->Void) -> dispatch_cancelable_closure? {
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(time * 1000))) {
// closure()
// }
//
// return nil
func dispatch_later(_ clsr:#escaping ()->Void) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(time * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: clsr)
}
var closure:(()->Void)? = closure
var cancelableClosure:dispatch_cancelable_closure?
let delayedClosure:dispatch_cancelable_closure = { cancel in
if closure != nil {
if (cancel == false) {
// DispatchQueue.main.async {
// closure?()
// }
DispatchQueue.main.async(execute: closure!)
// DispatchQueue.main.async(execute: closure as! #convention(block) () -> Void);
}
}
closure = nil
cancelableClosure = nil
}
cancelableClosure = delayedClosure
dispatch_later {
if let delayedClosure = cancelableClosure {
delayedClosure(false)
}
}
return cancelableClosure;
}
func cancel_delay(_ closureToCancel:dispatch_cancelable_closure?) {
if closureToCancel != nil {
closureToCancel!(true)
}
}
But for case when your application is not running, you need to save the time when you want to remove the cell in NSDefaults before application comes into background, and when application becomes active you can use this delay function to set the rest of the time (or if time expired you can remove the cell immidiatly)

Animation callback altering a different variable

I have a button which shows a view and which automatically goes away after specified time interval. Now if the button is pressed again while the view is already visible then it should go away and show a new view and the timer for new view be reset.
On the button press I have following code:
func showToast() {
timer?.invalidate()
timer = nil
removeToast()
var appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var toAddView = appDelegate.window!
toastView = UIView(frame: CGRectMake(0, toAddView.frame.height, toAddView.frame.width, 48))
toastView.backgroundColor = UIColor.darkGrayColor()
toAddView.addSubview(toastView)
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("removeToast"), userInfo: nil, repeats: false)
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.toastView.frame.origin.y -= 48
})
}
To remove toast i have the following code:
func removeToast() {
if toastView != nil {
UIView.animateWithDuration(0.5,
animations: { () -> Void in
self.toastView.frame.origin.y += 48
},
completion: {(completed: Bool) -> Void in
self.toastView.removeFromSuperview()
self.toastView = nil
})
}
}
Now even though I reset the timer each time by doing timer.invalidate() I get two calls in removeToast() which removes the newly inserted view. Can it be that UIView.animate be causing problems, I'm not getting how to debug the two callbacks for removeToast(). A demo project showing the behavior is here
NOTE: I did find some post saying to use dispatch_after() instead of timer, also as asked by #jervine10, but it does not suffice my scenario. As if I use dispatch_after then it's difficult to invalidate GCD call. Is there something that could be accomplished with NSTimers. I think that NSTimers are meant for this and there's something that I'm doing wrong.
Sorry for not seeing your sample project, and thanks for directing me towards it. I can clearly see where the issue is, and the solution is super simple. Change remove toast to this:
func removeToast() {
guard let toastView = self.toastView else {
return
}
UIView.animateWithDuration(0.1,
animations: { () -> Void in
toastView.frame.origin.y += 48
},
completion: {(completed: Bool) -> Void in
toastView.removeFromSuperview()
if self.toastView == toastView {
self.toastView = nil
}
})
}
Basically, the problem is that you are capturing self in the animation blocks, not toastView. So, once the animation blocks execute asynchronously, they will remove the new toastView set in the previous function.
The solution is simple and also fixes possible race conditions, and that is to capture the toastView in a variable. Finally, we check if the instance variable is equal to the view we are removing, we nullify it.
Tip: Consider using weak reference for toastView
Use dispatch_after to call a method after a set period of time instead of a timer. It would look like this:
let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC))
dispatch_after(popTime, dispatch_get_main_queue()) { () -> Void in
self.removeToast()
}

Resources