Animating Image in Watch App doesn't work (Swift) - ios

I have a problem with my animated image.
In my page, I have a label in the center, initialised with a text "Start dictation" and an Image at the bottom initialised without image
There is my code :
func dictation() {
let seconds = 1.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.label.setText("")
self.myImage.setImageNamed("frame-")
self.myImage.startAnimatingWithImagesInRange(NSMakeRange(0, 15), duration: 0.5, repeatCount: 0)
})
presentTextInputControllerWithSuggestions([], allowedInputMode: .Plain, completion: { (selectedAnswers) -> Void in
if ((selectedAnswers != nil) && (selectedAnswers!.count>0) ){
if selectedAnswers![0] is String {
self.myImage.stopAnimating()
self.myImage.setImageNamed("")
self.label.setText((selectedAnswers![0] as! String))
}
}
})
}
When my dictation is finished, there is a time before the displaying of my text. So, I tried to add animation to see that it's in progress.
Here, I want to start my dictation, start in background my animation and clear my text. And, when my speech is ready to be display, I want to stop and clear the animation and print my text.
My problem is : sometimes, when I come back on my page after dictation, I found my first text "Start dictation" and not my animation.
I tried with debug mode and I added breakpoints and logs in all my code. All is executed in the good order but the result is really random..
I saw also that my animation doesn't stop when I use stopAnimating() and doesn't clear when I use setImageNamed("").
Could you help me ?

When I started animation, I wasn't on the main page so, the code was executed but "self" was not my main page.
To solve this, I just call my animation in the willActivate function when I come back on the main page

Related

How to immediately redraw a Label (NSTextField) in Swift? [duplicate]

I'm trying to change an UIImageView alpha attribute on click. However, the change of the attribute is done after all the code-lines are run, and I want it to be as soon as the click is done. To simplify the code, I changed all the code-lines for a sleep(2) as it is giving the same effect.
The click function
#IBAction func click_btn() {
imageView.alpha = 0.5
sleep(2)
}
I've tried using DispatchQueue and async methods but the result is the same.
Here's the result, as you can see, when I click the button the Image doesn't change until the 2 sleep seconds pass. How can I change the alpha property immediately after the click?
That is the way the event loop on iOS (and Mac OS) works. UI changes are queued up as you run through your code, and then applied once you return and the app visits the event loop. Your delay is PREVENTING the change from taking place. If you get rid of the sleep it will work as intended.
Note that normally button taps fire on "touch up inside", which means the button doesn't respond until the user releases their finger. You can attach the action to a touch down event instead if you want.
What is it you actually want to accomplish?
Edit:
Rewrite your code like this:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue.main.async() {
//Code that takes 2 seconds to run
}
}
That will cause your function to return immediately, and your alpha change to take place. On the next pass through the event loop, the system will find your async task on the main queue, pick it up and start processing it. Note that for the 2 seconds it takes to run, the UI will freeze. It might be better to run the slow code on a background queue and only update the UI once the "slow bits" are finished:
#IBAction func click_btn() {
imageView.alpha = 0.5
DispatchQueue. global().async {
// Run the code that takes 2 seconds to run on a background thread
DispatchQueue.main.async() {
// Do your UI updates that have to run after the slow code finishes on the main thread.
}
}

How do I display a spinner in the middle of UI work?

I have the following function on iOS that performs UI work on a TableView, and the challenge is that if it begins to take longer then 0.5sec. a spinner should be displayed to the user so that the screen doesn't look like it froze.
func updateForm(with rowItems: [RowItem]) {
self.tableView.beginUpdates() // performance tweak.
let viewControllerName = String.init(describing: self.classForCoder) // id
var defaultSection = Form.createSectionWith(tag: viewControllerName, in: form)
// MARK: - update rows
let allRows = self.form.allRows
let startTime = CFAbsoluteTimeGetCurrent()
var showSpinner = false
for (index, item) in rowItems.enumerated() {
.
.
.
<TableView processing work on 100's of rows>
.
.
.
// Evaluate our running time for this process loop, and display spinner if we're past a threshold of seconds.
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
if timeElapsed > 0.5 && showSpinner == false {
showSpinner = true
self.showActivityIndicator(withStatus: "processing") // {NEVER GETS DISPLAYED}
}
} // for (index, item) ...
Of course, when I make the call to showActivityIndicator, it never actually gets displayed.
How can I interrupt and pause the UI work to have the showActivityIndicator spinner animation show-up, then let the loop continue?
You need to get off the main thread in order to allow the run loop to cycle and the redraw moment to occur. A simple delay after showing the spinner before proceeding with your work will solve the problem. But you will have to reorganize your code to allow for that. The easiest solution is to give up your 0.5 second idea and just show the spinner before starting in the first place.
I think there are some more important architectural questions that you need to be addressing with that code, but as a solution to the question asked...
before you start processing the data, create a timer with a delay that will load the spinner when it fires
let delayInSeconds = 5.0
let timer = Timer.scheduledTimer(withTimeInterval: delayInSeconds, repeats: false){
//closure that loads the spinner view on top of the current view
}
then when the processing activity is complete
timer?.invalidate() //cancels the timer if it hasn't fired
and then unload the spinner view

How to start and stop dictation in Apple Watch witout pressing button

I wrote code to use dictation on my apple watch. I used presentTextInputControllerWithSuggestions without suggestions to directly start dictation.
But, I have two problem :
I want to start dictation when my app starts. For this, I call my function in the willActivate method but with this, just a waiting image appears in my screen, not my first page with dictation.
I want to stop dictation without press "Done" button. I don't know if it's possible and how can I make this.
There is my code :
func dictation(){
self.presentTextInputControllerWithSuggestions([], allowedInputMode: WKTextInputMode.Plain, completion:{
(results) -> Void in
//myCode
})
}
override func willActivate(){
super.willActivate()
dictation()
}
Do you have solutions ?
Thanks for your help #Feldur
I tried with delay and it seems to work
There is my code :
override init(){
super.init()
print("start init")
let seconds = 1.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.dictation()
})
print("end init")
}
There are my logs :
start init
end init
start awakeWithContext
end awakeWithContext
start willactivate
end willactivate
start didAppear
end didAppear
start dictation
My screen appears and after, my dictation starts.
Do you have an idea for stop dictation when user stops speaking ?

Swift code executing before display updates

So I've just started trying out coding in swift and I'm creating an extremely basic app (purely to experiment and learn) where you click a button and two playing cards appear on the screen.
I'm trying to get it so that when the two playing cards are the same, the button to play again disables, the program pauses for a few seconds, then the button re-enables (so later I can add some 'win' text during the pause time).
Now the button and pausing fully works besides one problem. When testing, the program pauses and then when it finishes pausing the display then updates to show the two cards being equal. But while it pauses, it shows two random non equal cards.
I'm not sure why, seeing as the cards update before I check if they're equal, but I'm new to swift (literally last few days) so not sure how it works.
Any ideas? :)
#IBAction func playRoundTapped(sender: UIButton) {
// Change the card image each time the play button is pressed using a random number generator
self.firstCardImageView.image = UIImage(named: String(format: "card%i", arc4random_uniform(13)+1))
self.secondCardImageView.image = UIImage(named: String(format: "card%i", arc4random_uniform(13)+1))
// Check if the cards are equal
if firstCardImageView.image == secondCardImageView.image && firstCardImageView.image != "card" {
playRoundButton.userInteractionEnabled=false;
NSThread.sleepForTimeInterval(4)
playRoundButton.userInteractionEnabled=true;
}
}
Don't sleep in the main thread as this will stop all interactions with your app. You need to replace:
NSThread.sleepForTimeInterval(4)
playRoundButton.userInteractionEnabled=true;
with:
let enableTime = dispatch_time(DISPATCH_TIME_NOW, Int64(4 * Double(NSEC_PER_SEC)))
dispatch_after(enableTime, dispatch_get_main_queue()) {
playRoundButton.userInteractionEnabled=true;
}
First off, a better solution is not pausing at all and using dispatch_after to change the playRoundButton button state after 4 seconds.
If you want to stick with pausing, then you should give time for the UI to update itself before pausing. E.g.,
dispatch_async(dispatch_get_main_thread(), {
//Check if both cards are equal
if firstCardImageView.image == secondCardImageView.image && firstCardImageView.image != "card" {
playRoundButton.userInteractionEnabled=false;
NSThread.sleepForTimeInterval(4)
playRoundButton.userInteractionEnabled=true;
}
});
The fact is, when you assign a new image to your buttons, the button is actually redrawn on screen only at the next run-loop cycle, so if you pause before that is run, no visual change can be seen...
Keep in mind that pausing on the main thread will make your app unresponsive during that timeframe.
You're comparing two UIImage instances, which doesn't work with == because it will only compare the pointers. In your case, it would be much easier to compare the numbers that generated those images.
Other than that, you're pausing the main thread, which takes care of updating the user interface, so it doesn't actually get a chance to do so. One way to solve this problem is by using NSTimer.
#IBAction func playRoundTapped(sender: UIButton) {
let firstNumber = arc4random_uniform(13) + 1
let secondNumber = arc4random_uniform(13) + 1
firstCardImageView.image = UIImage(named: "card\(firstNumber)")
secondCardImageView.image = UIImage(named: "card\(secondNumber)")
if firstNumber == secondNumber {
playRoundButton.userInteractionEnabled = false;
NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: "enableButton", userInfo: nil, repeats: false)
}
}
func enableButton() {
playRoundButton.userInteractionEnabled = true;
}

apple watch - slow image animation first time

I'm building a small app for apple watch. I have a Group and a Label inside of it. What I'm trying to do is:
animate background image of the group
fade in label after image animation ends
My code looks essentially like this:
group.setBackgroundImageNamed("show_back-");
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
self.label.setAlpha(1)
};
};
The problem is that the first time this sequence is triggered, the image animation seems to run slower than 1.5 seconds, because the label starts fading in earlier than the images stop changing. If this is triggered again while the app is running, everything works as expected. I guess it has something to do with images preloading or something.
How can I make it work consistently? I couldn't find any sort of callback on image sequence animation end to subscribe to.
EDIT
Another problem I've noticed: I have another case when bg is animated from a dispatch_after block, and when I leave the app by tapping the crown and return by double-tapping it, either the dispatch_after block is not triggered, or the background animation is not rendered correctly the first time it is invoked (I think the second, because I tried adding a breakpoint into the dispatch block and it triggered every time I tested).
I'm running watchOS2, so maybe it is related to the beta state the OS is currently in?
I ran into the same issue as you.
This happens because on the first time you try it, the watch takes time to load the images. Also apple doesn't give us any 'pre load' method, so I came up with a little work around it:
When my controller will be displayed:
func willActivate()
I play the animation sequence once in a background tread, this way when my user clicks on it the images are already loaded.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { [weak self] in
if let uSelf = self {
uSelf.statusAnimationImage.setImageNamed("my image name")
uSelf.statusAnimationImage.startAnimatingWithImagesInRange(NSMakeRange(0, 359), duration: 0.5, repeatCount: 1)
}
}
That was the best way I found to solve this problem and it works for me.
try doing
group.setBackgroundImageNamed("show_back-");
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
self.label.setAlpha(1)
};
};
I'm not exactly sure what you're doing but also try doing animateWithDuration(0) or (1.5)

Resources