I can't get the Apple Watch Complication to update/refresh in WatchOS 3. I I'm using the following code in my ComplicationController.swift file.
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward])
}
func getTimelineStartDate(for complication: CLKComplication, withHandler handler: #escaping (Date?) -> Void) {
handler(Date())
}
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: #escaping (Date?) -> Void) {
handler(Date(timeIntervalSinceNow: 60 * 30))
}
I have also tried to schedule an update from the handle background task method in the ExtensionDelegate.swift but it dosen't seem to work either.
func scheduleNextRefresh() {
let fireDate = Date(timeIntervalSinceNow: 30 * 60)
let userInfo = ["lastActiveDate" : Date(),
"reason" : "updateWeekNumber"] as Dictionary
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: userInfo as NSSecureCoding) { (error) in
if error == nil {
print("Succesfully updated week number")
}
}
}
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task: WKRefreshBackgroundTask in backgroundTasks {
if WKExtension.shared().applicationState == .background {
if task is WKApplicationRefreshBackgroundTask {
print("Task received")
scheduleNextRefresh()
}
}
task.setTaskCompleted()
}
}
WKRefreshBackgroundTask do not update anything itself, it just allows your app to go to active state and run code (placed somewhere around print("Task received") line) that will update your complication. Remember that number of WKRefreshBackgroundTasks is limited.
Complication can be updated such way:
let server = CLKComplicationServer.sharedInstance()
// if you want to add new entries to the end of timeline
server.activeComplications?.forEach(server.extendTimeline)
// if you want to reload all the timeline, which according to snippets looks like your case
server.activeComplications?.forEach(server.reloadTimeline)
This will cause system to call getCurrentTimelineEntry(for:withHandler:) methods of your CLKComplicationDataSource, where you can prepare and return updated entries.
More about complications update in documentation. More about background tasks in WWDC16 session.
Related
I am using the IntentsExtension and IntentsUIExtension for a messaging app to allow a user to send messages using Siri.
It all works and the message is sent once, however when the extension UI is displayed, the view I define in configureView is displayed 3 times.
This is the default code intent handler. Either using this default code or my custom code the result is the same. UI extension IntentViewController configureView method called 3 times:
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
// MARK: - INSendMessageIntentHandling
// Implement resolution methods to provide additional information about your intent (optional).
func resolveRecipients(for intent: INSendMessageIntent, with completion: #escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INSendMessageRecipientResolutionResult.needsValue()])
return
}
var resolutionResults = [INSendMessageRecipientResolutionResult]()
for recipient in recipients {
let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)]
case 1:
// We have exactly one matching contact
resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)]
case 0:
// We have no contacts matching the description provided
resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()]
default:
break
}
}
completion(resolutionResults)
} else {
completion([INSendMessageRecipientResolutionResult.needsValue()])
}
}
func resolveContent(for intent: INSendMessageIntent, with completion: #escaping (INStringResolutionResult) -> Void) {
if let text = intent.content, !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
} else {
completion(INStringResolutionResult.needsValue())
}
}
// Once resolution is completed, perform validation on the intent and provide confirmation (optional).
func confirm(intent: INSendMessageIntent, completion: #escaping (INSendMessageIntentResponse) -> Void) {
// Verify user is authenticated and your app is ready to send a message.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
// Handle the completed intent (required).
func handle(intent: INSendMessageIntent, completion: #escaping (INSendMessageIntentResponse) -> Void) {
// Implement your application logic to send a message here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
// Implement handlers for each intent you wish to handle. As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.
// MARK: - INSearchForMessagesIntentHandling
func handle(intent: INSearchForMessagesIntent, completion: #escaping (INSearchForMessagesIntentResponse) -> Void) {
// Implement your application logic to find a message that matches the information in the intent.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
// Initialize with found message's attributes
response.messages = [INMessage(
identifier: "identifier",
content: "I am so excited about SiriKit!",
dateSent: Date(),
sender: INPerson(personHandle: INPersonHandle(value: "sarah#example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
)]
completion(response)
}
// MARK: - INSetMessageAttributeIntentHandling
func handle(intent: INSetMessageAttributeIntent, completion: #escaping (INSetMessageAttributeIntentResponse) -> Void) {
// Implement your application logic to set the message attribute here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
}
and the UI code:
class IntentViewController: UIViewController, INUIHostedViewControlling {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// MARK: - INUIHostedViewControlling
// Prepare your view controller for the interaction to handle.
func configureView(for parameters: Set<INParameter>, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: #escaping (Bool, Set<INParameter>, CGSize) -> Void) {
// Do configuration here, including preparing views and calculating a desired size for presentation.
completion(true, parameters, self.desiredSize)
}
var desiredSize: CGSize {
return self.extensionContext!.hostedViewMaximumAllowedSize
}
}
Putting a breakpoint on the completion handler in configureView I can see it is called 3 times. In my app this causes the custom view to appear 3 times one after another stacked vertically.
I have a situation in my App which I need to upload several files ranging from 10-50+- (small size, about 5 mb) and also need to support continuing in the background if needed.
At this point I'm able to upload perfectly while the app is in the foreground but once the app goes into background the current running upload continue running and finish in the background, but for some reason the rest of the operations never gets called.
Here is my code
Background queue init:
private var _backgroundUploadQueue = OperationQueue()
Background queue setup:
self._backgroundUploadQueue.maxConcurrentOperationCount = 4
self._backgroundUploadQueue.name = "Background Queue"
self._backgroundUploadQueue.qualityOfService = .background
Creating the operetions:
private func _createUploadOperation(from action: S3UploadAction) -> AsyncBlockOperation {
let operation = AsyncBlockOperation({ [weak self] operation in
guard !operation.isCancelled else {
operation.finish()
return
}
self?._s3SinglePartUploader.upload(
action: action,
completion: { [weak self] result in
self?._handleUploadCompletion(operation: operation, action: action, result: result)
}
)
self?._singlePartUploadStarted(action: action)
})
operation.name = "\(action.recordingSetVersion)_\(action.partNumber)"
return operation
}
Adding operations:
private func _startBackgroundUploading() {
for (_, actions) in self._uploadActions.enumerated() {
for action in actions.value {
let uploadOperation = self._createUploadOperation(from: action)
self._backgroundUploadQueue.addOperation(uploadOperation)
}
}
}
In the AppDelegate I add this code:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: #escaping () -> Void
) {
S3Uploader.shared.setBackgourndSessionCompletion(backgroundTaskCompletion: completionHandler)
AWSS3TransferUtility.interceptApplication(
application,
handleEventsForBackgroundURLSession: identifier,
completionHandler: completionHandler
)
}
I am currently working on implementing In App Purchases in my app and after restoring purchases i would like to call a completion to perform an action of displaying an alert to the user. I was doing it this way and found a post that says it might not even be executed. How can I properly structure this.
func restoreIAPPurchases(completion: (() -> Void)) {
if !self.canMakePayments {
return
}
self.paymentQueue.restoreCompletedTransactions()
completion()
}
let alertController = UIAlertController.vy_alertControllerWithTitle(nil, message: "Restore will reprocess your existing subscription. You will not be charged", actionSheet: false)
alertController.addAction("Ok")
alertController.addActionWithTitle("Restore", style: .default) {
IAPService.shared.restoreIAPPurchases {
UIAlertController.vy_showAlertFrom(self, title: "Restore complete", message: "Successfully restored purchase")
}
}
alertController.presentFrom(self)
"I was doing it this way and found a post that says it might not even be executed"
It might not be executed because you don't call the completion handler on all paths.
As Sh_Khan mentioned in his answer, you don't really need a completion handler here, you need to use the delegate methods to be informed when it completes and whether it was successful or not. But your particular issue with your specific code is that you are not calling completion in the if statement.
if !self.canMakePayments {
return
}
Should probably be
guard canMakePayments else {
completion()
return
}
In the code you had, if canMakePayments is false then your completion code will not execute.
The result is asynchonous here
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue)
or
func paymentQueue(_ queue: SKPaymentQueue,
restoreCompletedTransactionsFailedWithError error: Error)
Suppose I'm writing code for login and need Completion Hander for wait/call back after request completed.
//MARK:- #Properties
var signInCompletionHandler : ((_ result : AnyObject?, _ error : NSError?) -> Void)?
var viewController : UIViewController?
//MARK:- call login method with completion handler.
func login(withViewControler viewController : UIViewController, completionHandler : #escaping (_ result : AnyObject?, _ error : NSError?) -> Void) {
// Write your logic here.
}
Okay, so I'm trying to build an iOS app that relies on Firebase (To work with its android version)
I started with creating a repository for each actor in my app and a general repository to manage them all
Each repository manages the observers of this actor. An example:
Inside the PagesRepository, this is a function that retrieves all the pages from Firebase and returns it inside a completionHandler:
//MARK: Gets the whole pages list
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observe(DataEventType.value) { pagesSnapshot in
guard pagesSnapshot.exists() else {
displayError(error: "Pages snapshot doesn't exist")
return
}
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}
}
And then I call it from the ViewController like this:
repository.getPagesList { (pages, error) in
guard error == nil else {
return
}
//Do processing
}
I know this may be a lot to take in, but my problem is that every time I call the function, it creates a new observer but doesn't cancel the old one... So, the completionHandler is called multiple times with different values
How should I manage this problem?
(Sorry for being complicated and a little unclear, I'm just really lost)
It seems like you only want to observe the value once so I would use this instead:
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observeSingleEvent(of: .value, with: { (pagesSnapshot) in
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}) { (error) in
// Display error
}
}
I am trying to implement a bill payment extension via Siri. In the extension user only specifies the bill type and after the input, he/she will be directed to application and needs to login. Everything works fine if there is only my application that supports bill payment, is installed in the device. Even applications supporting money send via Siri can lead to unwanted behavior. Another finding in the issue is, if the device OS version is between 10.3 and 11 this issue is encountered. However, in iOS 11 v6 this issue seems to be fixed.
Problem: Almost always Siri does not enter the method handle(intent:completion:) which is the required INPayBillIntentHandling protocol method. Extension only asks for type of the bill, enters resolve protocol methods and enters the optional protocol method confirm(intent:completion:). In the end it needs to enter handle method but it does not enter the method. After opening the main application via Siri, AppDelegate's application:continueUserActivity:restorationHandler: method does not called either.
Edit: I have one other Siri extension(Send Payment) that functioning properly.
Codes are given below..
#available(iOSApplicationExtension 10.3, *)
class PayBillIntentHandler: NSObject, INPayBillIntentHandling {
public func handle(payBill intent: INPayBillIntent, completion: #escaping (INPayBillIntentResponse) -> Void) {
completion(INPayBillIntentResponse(
code: .inProgress,
userActivity: process(intent: intent)))
}
func confirm(payBill intent: INPayBillIntent, completion: #escaping (INPayBillIntentResponse) -> Void) {
let userActivity = process(intent: intent)
let success = (userActivity != nil)
let code: INPayBillIntentResponseCode = success ? .success : .failureInsufficientFunds
let response = INPayBillIntentResponse(code: code, userActivity: userActivity)
response.billDetails = INBillDetails(billType: intent.billType,
paymentStatus: .unpaid,
billPayee: nil,
amountDue: nil,
minimumDue: nil,
lateFee: nil,
dueDate: nil,
paymentDate: nil)
completion(response)
}
func resolveBillType(forPayBill intent: INPayBillIntent,
with completion: #escaping (INBillTypeResolutionResult) -> Void) {
if intent.billType != .unknown {
completion(INBillTypeResolutionResult.success(with: intent.billType))
} else {
completion(INBillTypeResolutionResult.needsValue())
}
}
#discardableResult fileprivate func process(intent: INPayBillIntent) -> NSUserActivity? {
let userActivity = NSUserActivity(activityType: String(describing: INPayBillIntent.self))
var properties = [String: Any]()
properties["billType"] = billType: intent.billType.rawValue
userActivity.userInfo = ["properties": properties]
}
}