I've been going through the examples in WatchOS 2 By Tutorial book by the team over at RayWenderlich, specifically chapter 18. They all work fine. In my own App, I am trying to send a button press from the watch to fire a button on the iPhone App. Here's the relevant code in Swift from the Watch and the Phone:
Watch:
//
// InterfaceController.swift
// Wasted Time Extension
//
// Created by Michael Rowe on 7/21/15.
// Copyright © 2010-2015 Michael Rowe. All rights reserved.
//
import WatchKit
import WatchConnectivity
import Foundation
class InterfaceController: WKInterfaceController,WCSessionDelegate {
#IBOutlet var wasteLabel: WKInterfaceLabel!
#IBOutlet var costLabel: WKInterfaceLabel!
#IBOutlet var counter: WKInterfaceLabel!
#IBOutlet var statusButton: WKInterfaceButton!
// our watchconnective session
var session : WCSession?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if(WCSession.isSupported()){
session = WCSession.defaultSession()
session!.delegate = self
session!.activateSession()
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func session(session: WCSession, didReceiveMessage message: [String: AnyObject], replyHandler: [String: AnyObject] -> Void) {
print("Did receive message Watch \(message)")
}
#IBAction func addButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
var counterd = prefs.doubleForKey("keyPeopleInMeeting")
counterd++
counter.setText(String(format:"%9.0f", counterd))
// Sending data to iPhone via Interactive Messaging
if WCSession.isSupported(){
// we have a watch supporting iPhone
let session = WCSession.defaultSession()
// we can reach the watch
if session.reachable {
let message = ["add": "1"]
print("Message \(message)")
session.transferUserInfo(message)
print("Send Message Add - People \(counterd)")
}
}
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["add":"1"]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Add Button Pressed \(counterd)")
}
#IBAction func minusButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
var counterd = prefs.doubleForKey("keyPeopleInMeeting")
counterd--
if (counterd <= 1) {
counterd = 1
}
counter.setText(String(format:"%9.0f", counterd))
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["minus":"1"]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Minus Button Pressed \(counterd)")
}
func statusButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
let status = statusButton.description
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["status":status]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Status Button Pressed - Status \(statusButton)")
}
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]){
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
if let waste = applicationContext["waste"] as? Float {
print("Watch Receive - Waste \(waste)")
}
if let cost = applicationContext["cost"] as? Float {
print("Watch Receive - Cost \(cost)")
}
if let counternum = applicationContext["counter"] as? Float {
print("Watch Receive - Counter \(counternum)")
}
if let status = applicationContext["status"] as? String {
print("Watch Receive - Status \(status)")
statusButton.setTitle(status)
}
}
private func showReachabilityError() {
let tryAgain = WKAlertAction(title: "Try Again", style: .Default, handler: { () -> Void in })
let cancel = WKAlertAction(title: "Cancel", style: .Cancel, handler: { () -> Void in })
self.presentAlertControllerWithTitle("Your iPhone is not reachable.", message: "You cannot adjust the status or number of attendees Watch is not currently connected to your iPhone. Please ensure your iPhone is on and within range of your Watch.", preferredStyle: WKAlertControllerStyle.Alert, actions:[tryAgain, cancel])
}
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
print("Transfer User Info Error watch: \(error)")
}
}
And the receiving code on the
iPhone:CODE:
func session(session: WCSession,
didReceiveMessage message: [String : AnyObject],
replyHandler: ([String : AnyObject]) -> Void) {
if let counterd = message["add"] as? Float {
let reply = ["add":counterd]
print("iPhone Receive Add \(counterd)")
addButtonPressed(self)
replyHandler(reply)
}
if let counterd = message["minus"] as? Float {
let reply = ["minus":counterd]
print("iPhone Receive minus \(counterd)")
removeButtonPressed(self)
replyHandler(reply)
}
if let status = message["status"] as? String {
if status == "Start" {
let reply = ["status":"Quorum"]
meetingStartedButtonPressed(self)
replyHandler(reply)
}
if status == "Quorum" {
let reply = ["status": "Finish"]
quorumButtonPressed(self)
replyHandler(reply)
}
if status == "Finish" {
let reply = ["status": "Reset"]
meetingEndedButtonPressed(self)
replyHandler(reply)
}
if status == "Reset" {
let reply = ["status": "Start"]
resetButtonPressed(self)
replyHandler(reply)
}
print("iPhone Received Status Button \(status)")
}
}
I get the messages firing fine on the Watch and see them in the debug log... But they do not seem to fire on the Phone. The phone is successfully sending its messages to the watch.
I have tested this code both in the simulator and on my own watch and iPhone. Note that the messages from the iPhone to the Watch are done using the via updateApplicationContext vs. the send message I am trying to use to send messages from the watch to the iPhone. Here's a sample of the iPhone code for sending context:
if WCSession.isSupported() {
if session.watchAppInstalled {
let UserInfo = ["waste":Float((wastedAmount.text! as NSString).floatValue), "cost":Float((totalAmount.text! as NSString).floatValue), "counter":Float((peopleInMeeting.text! as NSString).floatValue), "status":"Start"]
do {
try session.updateApplicationContext(UserInfo as! [String : AnyObject])
} catch {
print("Updating the context failed: ")
}
}
}
More information is needed regarding specifically what you're actually seeing on the Watch, when you say:
I get the messages firing fine on the Watch and see them in the debug log... But they do not seem to fire on the Phone. The phone is successfully sending its messages to the watch.
However, one common occurrence is that the iPhone code is actually working correctly, and the only thing you are not seeing is the debug statements printed to the console. This seems likely to be the case since you say you are seeing the expected Watch messages, presumably including those from print("Reply: \(reply)"). This indicates the message is being handled by the iPhone.
When that's the case, it's often simply that you are expecting to see debug console messages from both the Watch and iOS simulator processes at the same time, but in fact you're only connected to one or the other. There are (at least) two things you can do here:
Run the WatchKit app from Xcode, but then change to attach to the iPhone process instead. In Xcode, go Debug > Attach to Process... and select the iPhone app under "Likely Targets".
Start by running the iPhone app, which will mean you are already attached to that process. In the Apple Watch simulator, run the Watch app. You'll then be able to debug the iPhone side of the communication.
To debug in Watch-OS while running iPhone app and vice versa in Xcode-8.1. Required running Process need to be attached .
Visually:-
Related
I can detect workout started on backgroun with apple watch, with below code
let workoutevent = HKObjectType.workoutType()
if store.authorizationStatus(for: workoutevent) != HKAuthorizationStatus.notDetermined {
store.enableBackgroundDelivery(for: workoutevent, frequency: .immediate, withCompletion: { (worked, error) in
print(worked)
print(error)
print("workoutevent enableBackgroundDelivery")
guard worked else {
self.logger.error("Unable to set up background delivery from HealthKit: \(error!.localizedDescription)")
print("workoutevent unable to set up background ")
fatalError()
}
if error != nil {
print("workoutevent error is ")
print(error)
}
})
backgroundObserver3 =
HKObserverQuery(sampleType: workoutevent,
predicate: nil,
updateHandler: processUpdate3(query:completionHandler3:error:))
if let queryworkout = backgroundObserver3 {
print("Starting workoutevent333 the background observer query.\(queryworkout)")
store.execute(queryworkout)
}
}else{
print("not determined....")
}
whenever I started workout on apple watch, it goes to
processUpdate3
very well,
but what I need to know is to when user finish workout.
how can I detect it ?
func processUpdate3(query: HKObserverQuery,
completionHandler3: #escaping () -> Void,
error: Error?) {
print("come here when work out started ")
...........
}
I don't see it in your code. But somewhere you must have an HKWorkoutSession. My app is set up to track running and I configure the session to begin like so;
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
do {
// HKWorkoutSession is set up here.
session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
workoutBuilder = session.associatedWorkoutBuilder()
} catch {
// handle errors
}
When the users taps the end workout button I call session.end()
Here is a link to the documentation.
I've consulted many variations of background app refresh for Apple Watch so that I can update the complications for my app. However, the process seems very much hit or miss and most of the time it doesn't run at all after some time.
Here is the code I currently have:
BackgroundService.swift
Responsibility: Schedule background refresh and handle download processing and update complications.
import Foundation
import WatchKit
final class BackgroundService: NSObject, URLSessionDownloadDelegate {
var isStarted = false
private let requestFactory: RequestFactory
private let logManager: LogManager
private let complicationService: ComplicationService
private let notificationService: NotificationService
private var pendingBackgroundTask: WKURLSessionRefreshBackgroundTask?
private var backgroundSession: URLSession?
init(requestFactory: RequestFactory,
logManager: LogManager,
complicationService: ComplicationService,
notificationService: NotificationService
) {
self.requestFactory = requestFactory
self.logManager = logManager
self.complicationService = complicationService
self.notificationService = notificationService
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(handleInitialSchedule(_:)),
name: Notification.Name("ScheduleBackgroundTasks"),
object: nil
)
}
func updateContent() {
self.logManager.debugMessage("In BackgroundService updateContent")
let complicationsUpdateRequest = self.requestFactory.makeComplicationsUpdateRequest()
let config = URLSessionConfiguration.background(withIdentifier: "app.wakawatch.background-refresh")
config.isDiscretionary = false
config.sessionSendsLaunchEvents = true
self.backgroundSession = URLSession(configuration: config,
delegate: self,
delegateQueue: nil)
let backgroundTask = self.backgroundSession?.downloadTask(with: complicationsUpdateRequest)
backgroundTask?.resume()
self.isStarted = true
self.logManager.debugMessage("backgroundTask started")
}
func handleDownload(_ backgroundTask: WKURLSessionRefreshBackgroundTask) {
self.logManager.debugMessage("Handling finished download")
self.pendingBackgroundTask = backgroundTask
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
processFile(file: location)
self.logManager.debugMessage("Marking pending background tasks as completed.")
if self.pendingBackgroundTask != nil {
self.pendingBackgroundTask?.setTaskCompletedWithSnapshot(false)
self.backgroundSession?.invalidateAndCancel()
self.pendingBackgroundTask = nil
self.backgroundSession = nil
self.logManager.debugMessage("Pending background task cleared")
}
self.schedule()
}
func processFile(file: URL) {
guard let data = try? Data(contentsOf: file) else {
self.logManager.errorMessage("file could not be read as data")
return
}
guard let backgroundUpdateResponse = try? JSONDecoder().decode(BackgroundUpdateResponse.self, from: data) else {
self.logManager.errorMessage("Unable to decode response to Swift object")
return
}
let defaults = UserDefaults.standard
defaults.set(backgroundUpdateResponse.totalTimeCodedInSeconds,
forKey: DefaultsKeys.complicationCurrentTimeCoded)
self.complicationService.updateTimelines()
self.notificationService.isPermissionGranted(onGrantedHandler: {
self.notificationService.notifyGoalsAchieved(newGoals: backgroundUpdateResponse.goals)
})
self.logManager.debugMessage("Complication updated")
}
func schedule() {
let time = self.isStarted ? 15 * 60 : 60
let nextInterval = TimeInterval(time)
let preferredDate = Date.now.addingTimeInterval(nextInterval)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: preferredDate,
userInfo: nil) { error in
if error != nil {
self.logManager.reportError(error!)
return
}
self.logManager.debugMessage("Scheduled for \(preferredDate)")
}
}
#objc func handleInitialSchedule(_ notification: NSNotification) {
if !self.isStarted {
self.schedule()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
The flow for the above file's usage is that it will be used by the ExtensionDelegate to schedule background refresh. The first time, it'll schedule a refresh for 1 minute out and then every 15 minutes after that.
Here is the ExtensionDelegate:
import Foundation
import WatchKit
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private var backgroundService: BackgroundService?
private var logManager: LogManager?
override init() {
super.init()
self.backgroundService = DependencyInjection.shared.container.resolve(BackgroundService.self)!
self.logManager = DependencyInjection.shared.container.resolve(LogManager.self)!
}
func isAuthorized() -> Bool {
let defaults = UserDefaults.standard
return defaults.bool(forKey: DefaultsKeys.authorized)
}
func applicationDidFinishLaunching() {
self.logManager?.debugMessage("In applicationDidFinishLaunching")
if isAuthorized() && !(self.backgroundService?.isStarted ?? false) {
self.backgroundService?.schedule()
}
}
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
self.logManager?.debugMessage("In handle backgroundTasks")
if !isAuthorized() {
return
}
for task in backgroundTasks {
self.logManager?.debugMessage("Processing task: \(task.debugDescription)")
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
self.backgroundService?.updateContent()
backgroundTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
self.backgroundService?.handleDownload(urlSessionTask)
default:
task.setTaskCompletedWithSnapshot(false)
}
}
}
}
When the app is launched from background, it'll try to schedule for the first time in applicationDidFinishLaunching.
From my understanding, WKExtension.shared().scheduleBackgroundRefresh will get called in schedule, then after the preferred time WatchOS will call handle with WKApplicationRefreshBackgroundTask task. Then I will use that to schedule a background URL session task and immediately start it as seen in the updateContent method of BackgroundService. After some time, WatchOS will then call ExtensionDelegate's handle method with WKURLSessionRefreshBackgroundTask and I'll handle that using the handleDownload task. In there, I process the response, update the complications, clear the task, and finally schedule a new background refresh.
I've found it works great if I'm actively working on the app or interacting with it in general. But let's say I go to sleep then the next day the complication will not have updated at all.
Ideally, I'd like for it to function as well as the Weather app complication WatchOS has. I don't interact with the complication, but it reliably updates.
Is the above process correct or are there any samples of correct implementations?
Some of the posts I've consulted:
https://wjwickham.com/posts/refreshing-data-in-the-background-on-watchOS/
https://developer.apple.com/documentation/watchkit/background_execution/using_background_tasks
https://spin.atomicobject.com/2021/01/26/complications-basic-functionality/
I am developing app on WatchOS 6 but I cannot receive remote notification
Here is my registration code for push notifications in ExtensionDelegate I am getting valid device token.
extension ExtensionDelegate {
func setupRemoteNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
print("[WATCH PUSH NOTIFICATIONS] Permission granted: \(granted)")
guard granted else {
DispatchQueue.main.async {
self.showNotificationsNotGrantedAlert()
return
}
return
}
self.getNotificationSettings()
}
}
private func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("[WATCH PUSH NOTIFICATIONS] Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
WKExtension.shared().registerForRemoteNotifications()
self.onRemoteNotificationRegistration()
}
}
}
private func onRemoteNotificationRegistration() { }
func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) {
// Convert token to string
let deviceTokenString = deviceToken.map { data in String(format: "%02.2hhx", data) }.joined()
print("[WATCH PUSH NOTIFICACTIONS] Device Token: \(deviceTokenString)")
UserSettings.shared.deviceToken = deviceTokenString
}
func didFailToRegisterForRemoteNotificationsWithError(_ error: Error) {
print("[WATCH PUSH NOTIFICATIONS] Failed to register device: \(error)")
UserSettings.shared.deviceToken = nil
}
func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (WKBackgroundFetchResult) -> Void) {
print("[WATCH PUSH NOTIFICATIONS] Push notification received: \(userInfo)")
let aps = userInfo["aps"] as! [String: AnyObject]
completionHandler(.noData)
}
}
extension ExtensionDelegate: UNUserNotificationCenterDelegate {
// show notification also when in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("[WATCH PUSH NOTIFICATION] Will present notification...")
let categoryIdentifier = notification.request.content.categoryIdentifier
let category = NotificationCategory(rawValue: categoryIdentifier)
if category == NotificationCategory.NotificationCategory {
} else if category == NotificationCategory.ActivityCategory {
}
completionHandler([.alert, .badge, .sound])
}
// called when tapped onto notification banner
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("[WATCH PUSH NOTIFICATION] Did receive notification response")
let userInfo = response.notification.request.content.userInfo as! [String: AnyObject]
let aps = userInfo["aps"] as! [String: AnyObject]
let categoryIdentifier = response.notification.request.content.categoryIdentifier
let category = NotificationCategory(rawValue: categoryIdentifier)
if category == NotificationCategory.NotificationCategory {
} else if category == NotificationCategory.ActivityCategory {
}
handleNotificationAction(response.actionIdentifier)
openNotification(userInfo: userInfo)
completionHandler()
}
}
extension ExtensionDelegate {
private func handleNotificationAction(_ actionIdentifier: String) {
let action = NotificationAction(rawValue: actionIdentifier)
if action == NotificationAction.Call {
print("Action: Call handled!")
} else if action == NotificationAction.Email {
print("Action: Email handled!")
} else if action == NotificationAction.Message {
print("Action: Message handled!")
}
}
private func openNotification(userInfo: [String: AnyObject]) {
// let something = userInfo["something"] ...
}
}
extension ExtensionDelegate {
private func showNotificationsNotGrantedAlert() {
let settingsActionTitle = NSLocalizedString("Settings", comment: "")
let cancelActionTitle = NSLocalizedString("Cancel", comment: "")
let message = NSLocalizedString("You need to grant a permission from notification settings.", comment: "")
let title = NSLocalizedString("Push Notifications Off", comment: "")
let settingsAction = WKAlertAction(title: settingsActionTitle, style: .default) {
print("[WATCH PUSH NOTIFICATIONS] Go to Notification Settings")
}
let cancelAction = WKAlertAction(title: cancelActionTitle, style: .cancel) {
print("[WATCH PUSH NOTIFICATIONS] Cancel to go to Notification Settings")
}
WKExtension.shared().rootInterfaceController?.presentAlert(withTitle: title, message: message, preferredStyle: .alert, actions: [settingsAction, cancelAction])
}
}
I've added Push notification Entitlement in WatchExtensions
I am sending notification from Push Notification Tester app using valid TeamId, P8 cert, P8 key, bundle Id -> where I am getting success.
I am sending default test notification nothing special
{
"aps": {
"alert": {
"title": "Silver Salmon Creek",
"body": "You are within 5 miles of Silver Salmon Creek."
},
"category": "Notification"
}
}
I have similar code for iOS and there notification are delivered correctly on iPhone.
UPDATE
I've tested a little bit more
And I have application added to my iPhone app
but iPhone app is for iOS 13
and Watch app is for WatchOS 6
So as I understand this WatchOS app can be install as standalone application.
I have checked "Support running without iOS App installation"
So as I understand my server should send this notification to both
Watch device Token and iOS device token. And there APNS/iOS/watchOS take the decision if there is duplicated notification where to deliver this notification i.e. to iPhone / watchOS.
If there is only watchOS app I think it will be delivered to watch Device token
If there is both watch/iOS app then it will send to iOS and then device whether user is using iPhone/Watch in given moment and deliver it to currently used device.
So now if I send notification to iPhone device token with iOS bundle identifier, and have locked iPhone and watch on wrist I am getting notification on my WatchOS and even can look at long notifications with dynamic content.
But when I've uninstall iOS version of application, and send notification with iPhone or watch device token and appropriate bundle id iOS/watchOS then for standalone app I am not getting this notification delivered again.
I encountered a similar issue, be sure your p8 Bundle ID is using the explicit WatchkitApp ID and not the Extension's ID.
Once I dropped the .watchkitextension and uninstalled (the app), reinstalled, reregistered the device, it worked.
I found the issue. It was related with the way Push Notification is send from the server using node-apns. I also have old Push Notification Tester app. 3.0 version of node-apns is required. there is additonal Header field send to APNS server
apns-push-type
I have working an application where we need to open certain screen based on voice command like if user says "Open Setting" then it should open the setting screen, so far that I have used the SpeechKit framework but I am not able to detect the end of speech silence. Like how Siri does it. I want to detect if the user has ended his sentence/phrase.
Please find the below code for same where I have integrate the SpeechKit framework in two ways.
A) Via closure(recognitionTask(with request: SFSpeechRecognitionRequest, resultHandler: #escaping (SFSpeechRecognitionResult?, Error?) -> Swift.Void) -> SFSpeechRecognitionTask)
let audioEngine = AVAudioEngine()
let speechRecognizer = SFSpeechRecognizer()
let request = SFSpeechAudioBufferRecognitionRequest()
var recognitionTask: SFSpeechRecognitionTask?
func startRecording() throws {
let node = audioEngine.inputNode
let recordingFormat = node.outputFormat(forBus: 0)
node.installTap(onBus: 0, bufferSize: 1024,
format: recordingFormat) { [unowned self]
(buffer, _) in
self.request.append(buffer)
}
audioEngine.prepare()
try audioEngine.start()
weak var weakSelf = self
recognitionTask = speechRecognizer?.recognitionTask(with: request) {
(result, error) in
if result != nil {
if let transcription = result?.bestTranscription {
weakSelf?.idenifyVoiceCommand(transcription)
}
}
}
}
But when I say any word/sentence like "Open Setting" then closure(recognitionTask(with:)) called multiple times and I have put the method(idenifyVoiceCommand) inside the closure which call multiple times, so how can I restrict to call only one time.
And I also review the Timer logic while googling it(SFSpeechRecognizer - detect end of utterance) but in my scenarion it does not work beacause I did not stop the audio engine as it continuously listening the user’s voice like Siri does.
B) Via delegate(SFSpeechRecognitionTaskDelegate)
speechRecognizer.recognitionTask(with: self.request, delegate: self)
func speechRecognitionTaskWasCancelled(_ task: SFSpeechRecognitionTask) {
}
func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishSuccessfully successfully: Bool) {
}
And I found that the delegate which handle when the end of speech occurs do not call it and accidentally call it after sometimes.
I had the same issue until now.
I checked your question and I suppose the code below helps you achieve the same thing I did:
recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest,
resultHandler: { (result, error) in
var isFinal = false
if result != nil {
self.inputTextView.text = result?.bestTranscription.formattedString
isFinal = (result?.isFinal)!
}
if let timer = self.detectionTimer, timer.isValid {
if isFinal {
self.inputTextView.text = ""
self.textViewDidChange(self.inputTextView)
self.detectionTimer?.invalidate()
}
} else {
self.detectionTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false, block: { (timer) in
self.handleSend()
isFinal = true
timer.invalidate()
})
}
})
This checks if input wasn't received for 1.5 seconds
To your speech recogniser class add:
private var timer : Timer?
And modify code here:
recognitionTask = speechRecognizer.recognitionTask(with: request) { (result, error) in
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats:false) { _ in
self.timer = nil
//do here what do you want to do, when detect pause more than 1.5 sec
}
if result != nil {
I am trying to do a MUC on iOS using xmpp_messenger_ios & XMPPFramework
Here is the code to join the room.
func createOrJoinRoomOnXMPP(){
// location has named array of lat and long
NSLog("Creating room on XMPP")
let roomJID: XMPPJID = XMPPJID.jidWithString(self.roomID + "#conference.ip-172-31-41-100")
let roomData: XMPPRoomCoreDataStorage = XMPPRoomCoreDataStorage.sharedInstance()
let chatRoom = XMPPRoom.init(roomStorage: roomData, jid: roomJID, dispatchQueue: dispatch_get_main_queue())
chatRoom.activate(OneChat.sharedInstance.xmppStream)
chatRoom.addDelegate(self, delegateQueue: dispatch_get_main_queue())
// let history = DDXMLElement.elementWithName("history")
// // Get lst messegs of the room
// history.addAttributeWithName("maxstanzas", stringValue: "10")
chatRoom.joinRoomUsingNickname(OneChat.sharedInstance.xmppStream!.myJID.user, history: nil)
}
as soon as this block executes I get an error in this code:
extension OneMessage: XMPPStreamDelegate {
public func xmppStream(sender: XMPPStream, didSendMessage message: XMPPMessage) {
if let completion = OneMessage.sharedInstance.didSendMessageCompletionBlock {
completion(stream: sender, message: message)
}
//OneMessage.sharedInstance.didSendMessageCompletionBlock!(stream: sender, message: message)
}
public func xmppStream(sender: XMPPStream, didReceiveMessage message: XMPPMessage) {
let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
if !OneChats.knownUserForJid(jidStr: user.jidStr) { // <<< ERROR LINE
OneChats.addUserToChatList(jidStr: user.jidStr)
}
if message.isChatMessageWithBody() {
OneMessage.sharedInstance.delegate?.oneStream(sender, didReceiveMessage: message, from: user)
} else {
//was composing
if let _ = message.elementForName("composing") {
OneMessage.sharedInstance.delegate?.oneStream(sender, userIsComposing: user)
}
}
}
}
fatal error: unexpectedly found nil while unwrapping an Optional value
I have noticed that as soon as the connection is made to chat room it fetches previous messages, and thus the above code is executed.
Please help me out is doing a MUC for room chat on ios. I have searched and have not found any solution.
thanks
I solved this by this temporary solution.
extension OneMessage: XMPPStreamDelegate {
public func xmppStream(sender: XMPPStream, didSendMessage message: XMPPMessage) {
if let completion = OneMessage.sharedInstance.didSendMessageCompletionBlock {
completion(stream: sender, message: message)
}
//OneMessage.sharedInstance.didSendMessageCompletionBlock!(stream: sender, message: message)
}
public func xmppStream(sender: XMPPStream, didReceiveMessage message: XMPPMessage) {
NSLog("This is blocked")
// let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
//
// if !OneChats.knownUserForJid(jidStr: user.jidStr) {
// OneChats.addUserToChatList(jidStr: user.jidStr)
// }
//
// if message.isChatMessageWithBody() {
// OneMessage.sharedInstance.delegate?.oneStream(sender, didReceiveMessage: message, from: user)
// } else {
// //was composing
// if let _ = message.elementForName("composing") {
// OneMessage.sharedInstance.delegate?.oneStream(sender, userIsComposing: user)
// }
// }
}
}
Blocking the OneMessage.swift code.
and handling the incoming messages in my ViewController.
This is not the right way to do it. but until ProcessOne give support for MUC this can be done.
Unwrapping that causes nil happens on:
user (return value of userForJID method is XMPPUserCoreDataStorageObject! )
jidStr (the type is String!)
Investigate which one happens to be nil.
Possible causes of user to be nil
- Nil value of jid or managedObjectContext is used in userForJID(:xmppStream:managedObjectContext)`
To find out which one is nil, simply do this:
guard let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
else { fatalError("user is nil") }
guard let userJIDStr = user.jidStr
else { fatalError("jidStr is nil") }
I think you need to understand XMPP MUC first, read this doc.
When you send a message to MUCRoom, the serve will broadcast the message to all the members, including yourself.
And here message.from() = room.jid BUT NOT user.jid.
That's why the user you tried to get from roster is nil.