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

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 ?

Related

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

In Swift what is purpose of using UIBackgroundTaskIdentifier. how below mentioned code will execute

self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
print("animateRightToLeft: went here")
if let indentifier = self.backgroundTaskIdentifier {
print("animateRightToLeft: stop here")
UIApplication.shared.endBackgroundTask(indentifier)
}
})
My App auto killed after some time if App goes background.
Can some one advice is it because of the above code?
It would be much easier to help you if you explain what you are trying to do? The code you provided will only allow your app to execute code in background for limited amount of time (currently 180 seconds on my iPhone 7).
Detailed:
Once you call beginBackgroundTask, you are given a timer which starts running after your app goes to background. While that timer is running, your app will be executing code even in background. When this timer runs out, or you call endBackgroundTask, your code will stop executing in background. Also if that timer runs out before you called endBackgroundTask, your expiration handler will be called and you should call endBackgroundTask there.
Please note that the code you wrote in the expirationHandler will be called only if you don't call endBackgroundTask before timer runs out.
You can use this code to test how it all behaves, e.g. if you run it as is, app will print backgroundTimeRemaining in the console even when in background. If you comment beginBackgroundTask your app will not print anything after it goes to background.
private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
var timer: Timer?
#IBAction func buttontapped(_ sender: Any)
{
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block:
{
(timer) in
NSLog("$$$$$ Time remaining: \(UIApplication.shared.backgroundTimeRemaining)")
})
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler:
{
NSLog("$$$$$ Timer expired: Your app will not be executing code in background anymore.")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
})
NSLog("$$$$$ start")
DispatchQueue.main.asyncAfter(deadline:.now() + 30)
{
NSLog("$$$$$ end")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
}
}
From Docs beginBackgroundTask(expirationHandler:)
This method requests additional background execution time for your app. Call this method when leaving a task unfinished might be detrimental to your app’s user experience. For example, call this method before writing data to a file to prevent the system from suspending your app while the operation is in progress. Do not use this method simply to keep your app running after it moves to the background.
Each call to this method must be balanced by a matching call to the endBackgroundTask(_:) method.
My App auto killed after some time if App goes background , is it because of the above code?
no it isn't the above snippet only asks for additional time until task is finished , your app will be terminated anyway

SKNode's action run completion block does not get called

I have a watchOS 4 app which displays SpriteKit animations (SKActions) on top of the UI. Everything works fine in simulator and also on device first couple of times, then after some time when app is in background, and it is started, animations just freeze and completion block for the most long-lasting animation is not called. Any idea what might be the issue?
This is how I run my actions, caller is waiting for completion closure in order to hide the spritekit scene:
private func runActions(with icon: SKShapeNode?, completion: #escaping () -> Void) {
if let icon = icon, let scaleAction = scaleAction, let bg = background {
self.label?.run(fadeInOutAction)
icon.run(scaleAction)
icon.run(fadeInOutAction)
bg.run(backgroundAction, completion: completion)
} else {
completion()
}
}
And yes, I am aware that SKScene is paused when app moves to background. I am doing this in willActivate of my InterfaceController:
if scene.scene?.isPaused == true {
scene.scene?.isPaused = false
}
I want to emphasize that this works first always. It begins to fail after the app has been backgrounded for some time. Especially if I start the app from complication and try to immediately fire these animations, then this freezing happens.
Can I answer my own question? I guess I can? Here goes:
I finally solved this. It turns out that the WKInterfaceScene in WatchKit has ALSO an isPaused property that you need to turn false sometimes. So now in willActivate of my InterfaceController I will also check that and turn it false if it is true. Since I made this change, I haven't seen a single hiccup, freeze or anything weird anymore.
Case closed, I guess. I leave this here for future generations who might face this issue.

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

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

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