Performing Long-Term Actions in the Background Swift 3 - swift

I have an app that uses Corebluetooth and uses its Background services. I need also long-term CoreBluetooth action so I need to implement State preservation and restoration. I followed Apple document the link at the below https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html also watched WWDC 2013 presentation video link below,
https://developer.apple.com/videos/play/wwdc2013/703/
First of all, the sources are in Objective C and I am not familiar with that, I tried to convert them in swift3.
Then, I defined CBCentralManager with restoration identifier shown below,
self.myCentralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentralManager"])
Then, added delegate method something in the below,
func centralManager( willRestoreState dict: [String : AnyObject]) {
let peripherals: [CBPeripheral] = dict[CBCentralManagerRestoredStatePeripheralsKey] as! [CBPeripheral]
for peripheral in peripherals {
self.peripherals.append(peripheral)
peripheral.delegate = self
}
}
Finally did didFinishLaunchingWithOptions method in AppDelegate,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
if let peripheralManagerIdentifiers: [String] = launchOptions?[UIApplicationLaunchOptionsKey.bluetoothCentrals] as? [String]{
for identifier in peripheralManagerIdentifiers{
if (identifier == "myCentralManager"){
}
}
}
return true
}
When I debug, didFinishLaunchingWithOptions method called, the parameter launchOptions?[UIApplicationLaunchOptionsKey.bluetoothCentrals] is nil I think because of first time I created the app, then got error when defining CBCentralManager with restoration identifier the error is "reason: ' has provided a restore identifier but the delegate doesn't implement the centralManager:willRestoreState: method'"
I'm totally confused, thank you for any help,
Best regards,

Related

Swift UserDefaults.standard.register in AppDelegate didFinishLaunchingWithOptions is not working

I am attempting to register a Standard UserDefault value in AppDelegate but getting nil when I try to get the value out in a ViewController. I did this all of time in Obj C. What am I missing with the Swift code here? Thanks
AppDelegate
private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// Override point for customization after application launch.
setupAppearance()
// register default values for userDefaults
UserDefaults.standard.register(defaults: [
"selectedUUID": "0"
])
UserDefaults.standard.synchronize(). // is this still needed?
return true
}
ViewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Get the uuid that was registered in AppDelegate
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var defaults: UserDefaults = UserDefaults.standard
var selectedUUID = defaults.string(forKey: "selectedUUID")
print(selectedUUID!) // selectedUUID is nil, why? }
You can use UserDefaults.standard.set to set the User Default value.
There is no need to call synchronize, according to Apple here:
Waits for any pending asynchronous updates to the defaults database
and returns; this method is unnecessary and shouldn't be used.
This was deprecated in iOS 12 (release notes).
Try placing the following code in your didFinishLaunchingWithOptions to see this work:
let testDefaults = UserDefaults.standard.object(forKey: "selectedUUID") as? String
if testDefaults != nil {
print("Found UUID")
} else {
print("Did Not Find UUID")
UserDefaults.standard.set(UUID().uuidString, forKey: "selectedUUID")
}

Updating Glance data in watchOS2.2

I'm hoping you smart people can help me as most of the data online is out of date.
I have an iPhone app that displays financial information.
I would like to present this on a watch glance screen.
I can get the app to send the dictionary of the latest information and the glance does update live if both the Glance screen and phone app are open.
I would like to know how to use the Glance screen to ask the phone app for the latest information.
The phone app will probably be closed so it would need waking up and then asked for the current information.
I'm using swift 7 and WatchOS 2.2 and IOS 9.3
A lot of information here on Stackoverflow refers to watchOS 1 so no longer works.
I look forward to your solutions.
Look into WCSession as there are different methods for sending different types of data. This implementation is sending a dictionary.
Must setup a WCSession on both watch and phone devices. AppDelegate in didFinishLaunchingWithOptions: and I use the ExtensionDelegate in its init method. Be sure to import WatchConnectivity when using WCSession. Using the AppDelegate as a WCSessionDelegate below.
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Setup session on phone
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
// WCSessionDelegate method receiving message from watch and using callback
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
// Reply with a dictionary of information based on the "message"
replyHandler(["Key": "Value"])
}
}
Setup WCSession on the watch:
// ExtensionDelegate.swift
override init() {
let session = WCSession.defaultSession()
session.activateSession()
}
Send message, consisting of a dictionary, to the phone in order to receive information in the callback:
// GlanceController.swift
WCSession.defaultSession().sendMessage(["Please give Glance data": "Value"], replyHandler:{ (response) in
// Extract data from response dictionary
}) { (error) in
// Handle error
}

May I create an instance of AppDelegate?

I'm using Appdelegate with some timers to do background checks. How can I trigger to do a check right now.
I would have something like this in my Appdelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "test", userInfo: nil, repeats: true)
return true
}
func test() -> Bool {
print("true")
return true
}
is it admissable to do something like the following from any other class inside the app?
AppDelegate().test()
If you need access to some method or property of your app delegate, you could do something like this:
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
// Use your app delegate instance:
appDelegate.test()
}
Also, the code you wrote:
AppDelegate.test()
...is wrong; here test() is an instance method, so it should be called on an instance (not the class - notice the capital initial).
You can, but not by making another instance of AppDelegate but use the shared instance of it.
if let delegate = UIApplication.sharedApplication().delegate as? YOURAPPDELEGATE
Try above code.
In model unit tests it's often nice not to have UIApplication like framework classes, as they interfere with your tests in unforeseeable ways. But it's no problem at all to instantiate as many instances of your UIApplicationDelegate as you want:
let sut = AppDelegate()
XCTAssertTrue(sut.test(), "test failed")
It's a delegate class and as such, does not interfere with UIKit by just instantiating.
Also in normal app code you could do the same without harming the rest of your app—it's just less common to do these kind of tests within the actual app target.

Download in background in Swift

I'm trying to make my app download images in background. But when I press [Home] button, the app stop download. Is there any way to make it continue download even when I use another app? I have seen some apps can do like that but I don't know how.
This is what I've tried so far.
//
// AppDelegate.swift
// Swift-TableView-Example
//
// Created by Bilal ARSLAN on 11/10/14.
// Copyright (c) 2014 Bilal ARSLAN. All rights reserved.
//
import UIKit
import WebKit
protocol DownloadInBackgroundDelegate {
func downloadInBackgroundDidFinish(chapterid:Int, chaptername:String, storyid:Int, progressPercent:Float)
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shareCache = NSURLCache()
var downloadDelegate:DownloadInBackgroundDelegate? = nil
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var navigationBarAppearace = UINavigationBar.appearance()
application.setStatusBarOrientation(UIInterfaceOrientation.PortraitUpsideDown, animated: false)
self.startDownload()
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
FBAppEvents.activateApp()
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func application(application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
application.windows
}
func startDownload(){
var filesPath = [String]()
filesPath.append("https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/iphoneappprogrammingguide.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/MobileHIG.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/NetworkingOverview.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/AVFoundationPG.pdf")
filesPath.append("http://manuals.info.apple.com/MANUALS/1000/MA1565/en_US/iphone_user_guide.pdf")
downloadFiles(0, filesPath: filesPath)
}
func downloadFiles(index: Int, filesPath: [String]) -> Void {
var imgURL: NSURL = NSURL(string: filesPath[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
var fileCacheName = String(format: "%04d", index)
dispatch_async(dispatch_get_main_queue(), {
var fileExt = (data != nil && error == nil) ? Utility.checkImageType(data) : ""
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imagePath = paths.stringByAppendingPathComponent("\(fileCacheName).png")
if data.writeToFile(imagePath, atomically: false)
{
println("saved")
}
if index < filesPath.count - 1
{
var nextIndex:Int = index + 1
self.downloadFiles(nextIndex, filesPath: filesPath)
}
})
})
}
}
Answer
Based on the comment below, I found this thread : objective c - Proper use of beginBackgroundTaskWithExpirationHandler . I can solve my problem with it.
For downloading and storing of the images, instead of writing the logic yourself, I suggest you use some well known libraries, like:
https://github.com/Haneke/Haneke
https://github.com/rs/SDWebImage
Reason behind that is those libraries are well tested, quite robust and mainly very easy to use for basic tasks.
Now for the background download, there is beginBackgroundTaskWithExpirationHandler: that is specifically designed to do that. When you use it, you will get few more minutes to execute whatever you need (after that limit, your application will get terminated no matter what).
You can write following methods:
func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
When you want to use it, you just simple begin / end the task when starting / finishing the download call:
// Start task
let task = self.beginBackgroundTask()
// Do whatever you need
self.someBackgroundTask()
// End task
self.endBackgroundTask(task)
Hope it helps!
Use beginBackgroundTaskWithExpirationHandler: from the UIApplication to start a background task when the app enters the background
See Apple's document on multitasking background execution for details. See download in background in iphone its a similar question.

Not able to set Interactive Push Notifications on iOS8

I was already able to set Interactive LOCAL notifications, but the Remote notifications aren't working. I'm using Parse.com to send the JSON
My AppDelegate.Swift looks like this:
//
// AppDelegate.swift
// SwifferApp
//
// Created by Training on 29/06/14.
// Copyright (c) 2014 Training. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UINavigationBar.appearance().barTintColor = UIColor.orangeColor()
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
Parse.setApplicationId("eUEC7O4Jad0Kt3orqRouU0OJhkGuE20n4uSfrLYE", clientKey: "WypmaQ8XyqH26AeWIANttqwUjRJR4CIM55ioXvez")
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
func application(application: UIApplication!, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings!) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication!, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData!) {
let currentInstallation:PFInstallation = PFInstallation.currentInstallation()
currentInstallation.setDeviceTokenFromData(deviceToken)
currentInstallation.saveInBackground()
}
func application(application: UIApplication!, didFailToRegisterForRemoteNotificationsWithError error: NSError!) {
println(error.localizedDescription)
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
var notification:NSDictionary = userInfo.objectForKey("aps") as NSDictionary
if notification.objectForKey("content-available"){
if notification.objectForKey("content-available").isEqualToNumber(1){
NSNotificationCenter.defaultCenter().postNotificationName("reloadTimeline", object: nil)
}
}else{
PFPush.handlePush(userInfo)
}
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
on Parse, I'm setting the Push payload like this:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "FIRST_CATEGORY"
}
and I receive the push, but not with the custom buttons I've set.
I'm not sure if my problem is the same to yours (Make sure your problem is not due to Parse). Just post my solution here in case anyone else would encounter the same issue.
My problem is in the notification category.
Make sure you have set the category when registering the notification settings (I'm using Objective-C, no much difference):
UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
notificationCategory.identifier = #"CallNotificationCategory";
[notificationCategory setActions:#[declineAction, answerAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [[NSSet alloc] initWithObjects:notificationCategory, nil];
And, when you send remote notification, make sure you have "category" in the payload and the value is the same as you defined in the client. In my case it's something like:
{
"alert": "Tune in for the World Series, tonight at 8pm EDT",
"badge": "Increment",
"sound": "chime",
"category": "CallNotificationCategory"
}
This is in case if anyone comes across this problem while using Firebase Remote Notifications.
Just ask the backend developer to send:
"notification" : {
"title" : YOUR_TITLE,
"body" : YOUR_BODY,
"click_action" : YOUR_CATEGORY_NAME
}
You need to pass categories while registering for APNS.
Look at my sample :
var replyAction : UIMutableUserNotificationAction = UIMutableUserNotificationAction()
replyAction.identifier = "REPLY_ACTION"
replyAction.title = "Yes, I need!"
replyAction.activationMode = UIUserNotificationActivationMode.Background
replyAction.authenticationRequired = false
var replyCategory : UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
replyCategory.identifier = "REPLY_CATEGORY"
let replyActions:NSArray = [replyAction]
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Default)
replyCategory.setActions(replyActions, forContext: UIUserNotificationActionContext.Minimal)
let categories = NSSet(object: replyCategory)
let settings : UIUserNotificationType = UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge
UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings(forTypes: settings, categories: categories))
UIApplication.sharedApplication().registerForRemoteNotifications()
This worked for me to get interactive push notifications displaying and working in Swift with Parse. Note that you need to create a UIMutableNotificationAction for each interactive button you want to create. Source below goes into much more detail of configuring options for buttons.
In your app delegate file:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes:UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
var notificationActionAccept :UIMutableUserNotificationAction = UIMutableUserNotificationAction()
notificationActionAccept.identifier = "ACCEPT_IDENTIFIER"
notificationActionAccept.title = "Accept"
notificationActionAccept.destructive = true
notificationActionAccept.authenticationRequired = false
notificationActionAccept.activationMode = UIUserNotificationActivationMode.Background
var notificationCategory:UIMutableUserNotificationCategory = UIMutableUserNotificationCategory()
notificationCategory.identifier = "CallNotificationCategory"
notificationCategory .setActions([notificationActionAccept], forContext: UIUserNotificationActionContext.Default)
let notificationSettings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: NSSet(array:[notificationCategory]))
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
return true
}
And in Parse Cloud when you send the push, you would match the category to the UIMutableUserNotificationCategory like #Xialin mentioned above.
Looks like you weren't setting categories or at least not setting them until after you had registered the UINotificationSettings - you need to set them before or it won't work.
I got most of this info at the link below. It goes into more detail if needed. Hope this helps:
http://thecodeninja.tumblr.com/post/90742435155/notifications-in-ios-8-part-2-using-swift-what-is