How to show a notification when app is closed (iOS)? - ios

In my app, I am downloading data using the Facebook graph api and wish to present a notification when new data is available. I want to download data, compare it with what's already stored in NSUserDefaults and show a notification if it is different.
I allowed background fetch in the info.plist file and in my appDelegate, I have added the following code:
In didFinishLaunchingWithOptions():
UIApplication.sharedApplication().setMinimumBackgroundFetchInterval(30)
In performFetchWithCompletionHandler()
(this doesn't include the code to check if the fetched data is new, it's just a test)
let url = "https://graph.facebook.com/109315262061/posts?limit=20&fields=id,full_picture,picture,from,shares,attachments,message,object_id,link,created_time,comments.limit(0).summary(true),likes.limit(0).summary(true)&access_token=\(API_KEY)"
let task = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { (data, response, error) in
if error == nil
{
dispatch_async(dispatch_get_main_queue(), {
do
{
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)
ids.removeAll()
if let items = jsonData["data"] as? [[String:AnyObject]]
{
for item in items
{
if let id = item["id"] as? String
{
ids.append(id)
}
}
if ids.count == 20
{
print(jsonData)
let notification = UILocalNotification()
notification.alertBody = "You have new notifications!"
notification.alertTitle = "NSITConnect"
notification.fireDate = NSDate(timeIntervalSinceNow: 1)
UIApplication.sharedApplication().scheduleLocalNotification(notification)
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
completionHandler(UIBackgroundFetchResult.NewData)
}
}
}
catch
{
}
})
}
}
task.resume()
The downloading operation occurs successfully but I don't see a notification. How can I fix this? Also is there a way to download data when the app is force closed and then display the notification? My apologies if this is a silly question, I am fairly new to this concept!

Notifications aren't shown if the app is already open, try creating an alert to display your message, or create a custom controller/view.
You can check wether your app is currently active or not like this, and then either create the notification or the alert/custom controller.
application.applicationState == UIApplicationState.Active

Related

Post to Instagram by opening Instagram app – iOS, Swift

I have an Instagram scheduling app and I am trying to open this (see image below) in Swift 5.x. The goal is simple: save Image to Firebase, once it is time to post, notification!, user clicks on the notification and this (image below) opens up with the appropriate image/video to post. Everything works except for opening Instagram with the appropriate photo/video. I have tried this:
func postToInstagram(image: URL) {
let videoFileUrl: URL = image
var localId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
localId = request?.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
// completion handler is called on an arbitrary thread
// but since you (most likely) will perform some UI stuff
// you better move everything to the main thread.
DispatchQueue.main.async {
guard error == nil else {
// handle error
print(error)
return
}
guard let localId = localId else {
// highly unlikely that it'll be nil,
// but you should handle this error just in case
return
}
let url = URL(string: "instagram://library?LocalIdentifier=\(localId)")!
guard UIApplication.shared.canOpenURL(url) else {
// handle this error
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
})
}
and this:
func postToInstagram(image: URL, igURL: String) {
let urlStr: String = "instagram://app"
let url = URL(string: igURL)
if UIApplication.shared.canOpenURL(url!) {
print("can open")
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
To no avail. The latter code works, but only opens the Instagram app itself, which is fine, but I would like to open the View in the image below rather than Instagram's home screen. I also tried changing the URL to "instagram://share" and this works but goes to publish a regular post, whereas I want the user to decide what they want to do with their image.
This is where I want to go:
Note: For everyone who will be telling me this and whoever will wonder: Yes, my URL schemes (LSApplicationQueriesSchemes) are fine. And, just to clarify, I need to fetch the image/video from Firebase before posting it.

contact access permission is shown in simulator but not on real device sometimes swift

I have a simple code that request access to contacts
override func viewDidLoad() {
super.viewDidLoad()
fetchContacts()
}
func fetchContacts()
{
let allowedCharset = CharacterSet
.decimalDigits
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, err) in
if let error = err
{
print("failed to access",error)
return
}
if (granted)
{
///// after we get access to fetch contacts //// we reload table view data ///
print("access granted")
let keys = [CNContactGivenNameKey,CNContactPhoneNumbersKey,CNContactFamilyNameKey,CNContactMiddleNameKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointerIfYouWantToStopEnumerating) in
let array = contact.phoneNumbers
for number in array
{
let fullName = contact.givenName + contact.middleName
let lastName = contact.familyName
let value = number.value.stringValue
let number = String(value.unicodeScalars.filter(allowedCharset.contains))
print (number)
/////////// 4 cases we just need the phone not to be zero ///////
if (fullName != "SPAM")
{
self.firstName.append(fullName)
self.lastName.append(lastName)
self.numberArray.append(number)
}
}
})
//self.table()
}
catch let err2 {
print ("failer to enurmerate",err2)
}
}
}
}
This code works fine on simulator. When I delete app on the simulator and clean then build and run the app again it works fine a popup view appears with permissions request, however on real device it works the permissions pops the first time when I delete the app from the phone and clean then build and run I dont receive the pop permission request again
When you delete an app the iOS keeps the permissions for a day for bundle identifier if you want to remove it in the same date you have a three options
change the iPhone OS (iOS) data by increasing iPhone OS (iOS) date with a day
Wait for a day
Reset the Device settings
Click here the apple docs reference that i take the screenshot form it also you can check it.

Can an application tune in to Core Data changes made by an extension?

I'm working on an application which uses a shared Core Data database between itself and a Notification Service Extension. Both the application and the extension are able to read and write to the same Core Data database.
The application however needs to update the displayed information as soon as the corresponding fields change in the database. Is there an efficient way for it to be notified of the changes the extension makes to the database? I assume the application and the extension use different managed contexts to access the database. Or am I wrong?
Using SwiftEventBus this is pretty straight forward
Controller.swift
let yourObj = YourObject()
SwiftEventBus.post("EventName", sender: yourObj)
Extension.swift
let yourObj = YourObject()
SwiftEventBus.post("EventName", sender: yourObj)
AppDelegate.swift
SwiftEventBus.onMainThread(self, name: "EventName") { (result) in
if let yourObject = result.object as? YourObject {
// Queue or write the data as per your need
}
}
I found a solution to the problem I described after being pointed towards using notification by #CerlinBoss. It is possible to send a notification from the extension to the application (or vice versa). This can be done in iOS using a Darwin notification center. The limitation however is that you can't use the notification to send custom data to your application.
After reading many articles I decided that I'd avoid making changes to the Core Data database from two different processes and using multiple managed contexts. Instead, I queue the data I need to communicate to the application inside a key in the UserDefaults and once the application is notified of the changes, I'd dequeue them and update the Core Data context.
Common Code
Swift 4.1
import os
import Foundation
open class UserDefaultsManager {
// MARK: - Properties
static let applicationGroupName = "group.com.organization.Application"
// MARK: - Alert Queue Functions
public static func queue(notification: [AnyHashable : Any]) {
guard let userDefaults = UserDefaults(suiteName: applicationGroupName) else {
return
}
// Retrieve the already queued notifications.
var alerts = [[AnyHashable : Any]]()
if let data = userDefaults.data(forKey: "Notifications"),
let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
alerts.append(contentsOf: items)
}
// Add the new notification to the queue.
alerts.append(notification)
// Re-archive the new queue.
let data = NSKeyedArchiver.archivedData(withRootObject: alerts)
userDefaults.set(data, forKey: "Notifications")
}
public static func dequeue() -> [[AnyHashable : Any]] {
var notifications = [[AnyHashable : Any]]()
// Retrieve the queued notifications.
if let userDefaults = UserDefaults(suiteName: applicationGroupName),
let data = userDefaults.data(forKey: "Notifications"),
let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
notifications.append(contentsOf: items)
// Remove the dequeued notifications from the archive.
userDefaults.removeObject(forKey: "Notifications")
}
return notifications
}
}
Extension:
Swift 4.1
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
os_log("New notification received! [%{public}#]", bestAttemptContent.body)
// Modify the notification content here...
// Queue the notification and notify the application to process it
UserDefaultsManager.queue(notification: bestAttemptContent.userInfo)
notifyApplication()
contentHandler(bestAttemptContent)
}
}
func notifyApplication() {
let name: CFNotificationName = CFNotificationName.init("mutableNotificationReceived" as CFString)
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
CFNotificationCenterPostNotification(center, name, nil, nil, true)
os_log("Application notified!")
}
}
Application:
Swift 4.1
// Subscribe to the mutableNotificationReceived notifications from the extension.
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
let name = "mutableNotificationReceived" as CFString
let suspensionBehavior = CFNotificationSuspensionBehavior.deliverImmediately
CFNotificationCenterAddObserver(center, nil, mutableNotificationReceivedCallback, name, nil, suspensionBehavior)
}
let mutableNotificationReceivedCallback: CFNotificationCallback = { center, observer, name, object, userInfo in
let notifications = UserDefaultsManager.dequeue()
for notification in notifications {
// Update your Core Data contexts from here...
}
print("Processed \(notifications.count) dequeued notifications.")
}

iOS 10 remote notifications with pictures

First of all I'm new developing with Swift, I have an Objective C background, but I'm mainly an Android developer.
Actually I'm developing an app in Swift 3 and Xcode 8. I need to add to this application the new rich push notification system of Apple.
I have been able to add a local rich notification example with pics, videos and gifs. But I need to show remote notifications with pics hosted on a typical Internet server.
To launch local notifications I'm using this code:
#IBAction func launchPicNotification(sender: UIButton) {
let content = UNMutableNotificationContent()
content.title = "Title"
content.body = "Body"
content.sound = UNNotificationSound.default()
let url = Bundle.main.url(forResource:"foto", withExtension: "jpg")
let attachment = try? UNNotificationAttachment(identifier: "Notification",
url: url!,
options: [:])
if let attachment = attachment {
print("Yes")
content.attachments.append(attachment)
}else{
print("No")
}
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 10.0, repeats: false)
let request = UNNotificationRequest(identifier:"identificador", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request){(error) in
if (error != nil){
//handle here
}
}
}
I need to load a remote jpg file as attached image. Does somebody know how can I load a remote file instead of load a local picture?
Thanks
I added an extension to UIImage to handle this. Once you create a UIImage from the downloaded data, you can call this function to create a local URL.
extension UIImage {
func createLocalURL() -> URL? {
guard let data = UIImagePNGRepresentation(self) else {
print("Coule not get UIImagePNGRepresentation Data for photo")
return nil
}
let localUrl = self.getDocumentsDirectory().appendingPathComponent("copy.png")
do {
try data.write(to: localUrl)
} catch let error {
print("Failed to write to URL")
print(error)
}
return localUrl
}
}

How can I get missed CloudKit notification while app is in the background mode?

CloudKit manages my notifications (not my dedicated server)
My first device changes something in CloudKit Container and pushes notification.
... but on my second device my app is currently running in background mode. So, the notification arrives to device with Alert, but the app itself doesn't know about it.
What is the elegant and effective way to catch this one missed notification (or even more) when the app goes back to the foreground mode?
Suppose the change is related to my top visible controller, and I would like to apply that change without fetching anything on viewDidAppear:.
Simply you can do the following, implemented inside UIApplicationDelegate method:
func applicationWillEnterForeground(application: UIApplication) {
var queryNotifications = [CKQueryNotification]()
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
operation.notificationChangedBlock = { notification in
if let queryNotification = notification as? CKQueryNotification {
queryNotifications.append(queryNotification)
}
}
operation.fetchNotificationChangesCompletionBlock = { token, error in
var notificationIdentifiers = [CKNotificationID]()
for queryNotification in queryNotifications {
let recordID = queryNotification.recordID!
//here you can do enything you need with your recordID
container.publicCloudDatabase.fetchRecordWithID(recordID, completionHandler: { object, error in
notificationIdentifiers.append(queryNotification.notificationID!)
if queryNotifications.count == notificationIdentifiers.count {
let operationQueue = NSOperationQueue()
operationQueue.addOperation(CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIdentifiers))
}
})
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}

Resources