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)
Related
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
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 ?
I can't wait for group of SKAction because completion handler executes immediately. Here is basic example:
let a1 = SKAction.runAction(SKAction.fadeAlphaTo(0.5, duration: duration), onChildWithName: child1.name!)
let a2 = SKAction.runAction(SKAction.fadeAlphaTo(1, duration: duration), onChildWithName: child2.name!)
runAction(SKAction.group([a1, a2]), completion: { () -> Void in
// do something
})
Nodes child1 and child2 are children of scene. When I run app on iPhone 5s (iOS 8.4) simulator I see that completion block doesn't wait while group actions finished and starts immediately.
So my two questions:
Is it s bug? If no, where is in SKAction documentation I can find explanation of this behaviour.
How to do this right? I know I can use counter and start each action a1 and a2 with completion block and just check when counter became 0 but it looks like a boilerplate code for me.
According to Apple's documentation, SKAction.runAction returns an action of instantaneous duration (that is 0 duration).
Thus in your code both SKActions, a1 and a2, have instantaneous duration. This is why the completion block is called immediately.
From Apple Documentation
SKAction.runAction : This action has an instantaneous duration,
although the action executed on the child may have a duration of its
own.
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKAction_Ref/#//apple_ref/occ/clm/SKAction/runAction:onChildWithName:
One way to set the completion handler would be to set it to one of the child nodes, since both actions have the same duration.
let a1 = SKAction.fadeAlphaTo(0.5, duration: 1);
let a2 = SKAction.fadeAlphaTo(1, duration: 1);
child1.runAction(a1)
child2.runAction(a2) { () -> Void in
//completion code
}
I am developing an application which has 5 animations on each page. each animation is launched when you click on them. to assist the user in 5 seconds if not pressed any image, aid is released. this is repeated for each animation, if you click on the animation 1, when the animation ends, it throws back the counter 5 seconds, but in those 5 seconds is pressed in any animation, the counter should be canceled. as I can manage it? until now did with dispatch_after but not how to cancel
If you want your operations to be cancelable, one easy approach is to use an NSBlockOperation.
let operation = NSBlockOperation()
operation.addExecutionBlock { () -> Void in
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in
if operation.cancelled {
return
} else {
// perform your animation
}
})
}
To start your operation:
operation.start()
If you want to cancel your operation:
operation.cancel()
If your operation has many stages, you should periodically check operation.cancelled - calling cancel() does nothing itself beside set that to true.
For more complex work, you can chain multiple operations together in a dependency and put them in an NSOperationQueue.
Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.