iOS 10 remote notifications with pictures - ios

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
}
}

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.

Firestore storage upload task observer not executing while app is in background

I'm uploading 2 files to the storage, once both are done a Firestore document should be created. This works perfectly if the app is in the foreground.
However if the app is put into the background while still uploading, the .success observer code does not get executed, the .progress observer still executes until it's 100% and then everything just stops.
Once I put my app back into the foreground the code gets executed again (sometimes successfully, sometimes with an error telling me to check the server response).
Button that calls the upload functions:
#IBAction func postButtonClicked(_ sender: Any) {
print("Post button clicked, trying to post...")
//Make sure we are logged in and have a video
guard let videoURL = videoURL else {
print("We don't have a video, why are we trying to post?")
dismiss(animated: true, completion: nil)
return
}
//1 Create and start uploading thumbnail
createThumbnail(from: videoURL) { (image) in
if let image = image {
self.uploadThumbnailToStorage(image: image)
}
}
//2 Start uploading video
uploadVideoToStorage(from: videoURL)
dismiss(animated: true, completion: nil)
}
And the video upload function:
func uploadVideoToStorage(from url: URL) {
print("Start uploading video...")
let storageRef = storage.reference()
let videoRef = storageRef.child("videos/\(postRef.documentID)_video")
// Upload the file to the path "videos"
let uploadTask = videoRef.putFile(from: url, metadata: nil) { metadata, error in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
return
}
// Metadata contains file metadata such as size, content-type.
let size = metadata.size
print("Video file size: \(size)")
}
//Observe uploadTask
uploadTask.observe(.success) { snapshot in
snapshot.reference.downloadURL { (url, error) in
guard let downloadURL = url else {
if let error = error {
print("Error in video success observer: \(error.localizedDescription)")
}
return
}
print("Video upload success.")
self.videoString = downloadURL.absoluteString
self.videoUploadComplete = true
self.checkIfUploadsAreComplete()
}
}
uploadTask.observe(.progress) { snapshot in
let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount)
/ Double(snapshot.progress!.totalUnitCount)
print("Uploading video: \(percentComplete.rounded())%")
}
}
The checkIfUploadsAreComplete() function checks if both upload tasks are complete and then writes the document, but I don't think that code is relevant.
I have tried putting the uploadVideoToStorage() function into the background like so:
DispatchQueue.global(qos: .background).async {
// Request the task assertion and save the ID.
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TestVideoUpload") {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
}
// Send the data synchronously.
DispatchQueue.main.async {
self.uploadVideoToStorage(from: videoURL)
}
// End the task assertion.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
}
But that didn't do anything, any thoughts?

Is UNUserNotification have option to show notification only on apple watch?

I am developing apple watch app which have the functionality to show a notification to User after every 2 or 3 or 5 min.
I have achieved this functionality through iOS UNUserNotification and its works perfectly.
But what I want here is User can see all notifications only on Apple watch not on iPhone.
Right now it's showing on both iPhone & Apple watch.
Is it possible to show notifications only on the Apple watch?
I have implemented the below code for UserNotification.
func scheduleNotification(at date: Date) {
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (1*60), repeats: true)
let content = UNMutableNotificationContent()
content.title = "Test Reminder"
content.body = "Show More detail in Body!"
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "myCategory"
if let path = Bundle.main.path(forResource: "logo", ofType: "png") {
let url = URL(fileURLWithPath: path)
do {
let attachment = try UNNotificationAttachment(identifier: "logo", url: url, options: nil)
content.attachments = [attachment]
} catch {
print("The attachment was not loaded.")
}
}
let request = UNNotificationRequest(identifier: "textNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
}
}
}
Thanks in Advance!
No, this is not possible.
The system decides automatically where to show the notifications, developers have no control over this.
The notification will be displayed on the Apple Watch if the paired iPhone is locked and the Watch is worn by the user. In any other case, the notification will be displayed on the iPhone.
See Notifications on your Apple Watch
Yes, you can make the notification appear only on watch if you will trigger the Local notification from WatchKit Extension.
Please see the corresponding documentation: https://developer.apple.com/documentation/watchkit/notifications/taking_advantage_of_notification_forwarding?changes=__5

Swift 3 iMessage Extension doesn't open URL

I am creating an iOS Application iMessage Extension.
According to Example by Apple, I creating a message according to provided logic
guard let url: URL = URL(string: "http://www.google.com") else { return }
let message = composeMessage(url: url)
activeConversation?.insert(message, completionHandler: { [weak self] (error: Error?) in
guard let error = error else { return }
self?.presentAlert(error: error)
})
also
private func composeMessage(url: URL) -> MSMessage {
let layout = MSMessageTemplateLayout()
layout.caption = "caption"
layout.subcaption = "subcaption"
layout.trailingSubcaption = "trailing subcaption"
let message = MSMessage()
message.url = url
message.layout = layout
return message
}
and
private func presentAlert(error: Error) {
let alertController: UIAlertController = UIAlertController(
title: "Error",
message: error.localizedDescription,
preferredStyle: .alert
)
let cancelAction: UIAlertAction = UIAlertAction(
title: "OK",
style: .cancel,
handler: nil
)
alertController.addAction(cancelAction)
present(
alertController,
animated: true,
completion: nil
)
}
As far as I understand, after message is sent, on a click, Safari browser should be opened.
When I click on a sent message, MessageViewController screen takes place in whole screen, without opening safari or another app.
Where is the problem? How can I achieve desired functionality?
Here is the code I use to open a URL from a iMessage extension. It is currently working to open the Music app in the WATUU iMessage application. For instance with the URL
"https://itunes.apple.com/us/album/as%C3%AD/1154300311?i=1154300401&uo=4&app=music"
This functionality currently works in iOS 10, 11 and 12
func openInMessagingURL(urlString: String){
if let url = NSURL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: Selector("openURL:")) == true{
responder?.perform(Selector("openURL:"), with: url)
}
responder = responder!.next
}
}
}
UPDATE FOR SWIFT 4
func openInMessagingURL(urlString: String){
if let url = URL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: #selector(UIApplication.open(_:options:completionHandler:))) == true{
responder?.perform(#selector(UIApplication.open(_:options:completionHandler:)), with: url)
}
responder = responder!.next
}
}
}
I think safari Browser only opens for macOS. This worked for me:
override func didSelectMessage(message: MSMessage, conversation: MSConversation) {
if let message = conversation.selectedMessage {
// message selected
// Eg. open your app:
let url = // your apps url
self.extensionContext?.openURL(url, completionHandler: { (success: Bool) in
})
}
}
Using the technique shown by Julio Bailon
Fixed for Swift 4 and that openURL has been deprecated.
Note that the extensionContext?.openURL technique does not work from an iMessage extension - it only opens your current app.
I have posted a full sample app showing the technique on GitHub with the relevant snippet here:
let handler = { (success:Bool) -> () in
if success {
os_log("Finished opening URL")
} else {
os_log("Failed to open URL")
}
}
let openSel = #selector(UIApplication.open(_:options:completionHandler:))
while (responder != nil){
if responder?.responds(to: openSel ) == true{
// cannot package up multiple args to openSel so we explicitly call it on the iMessage application instance
// found by iterating up the chain
(responder as? UIApplication)?.open(url, completionHandler:handler) // perform(openSel, with: url)
return
}
responder = responder!.next
}
It seems it is not possible to open an app from a Message Extension, except the companion app contained in the Workspace. We have tried to open Safari from our Message Extension, it did not work, this limitation seems by design.
You could try other scenari to solve your problem :
Webview in Expanded Message Extension
You could have a Webview in your Message Extension, and when you click
on a message, you could open the Expanded mode and open you Url in the
Webview.
The user won't be in Safari, but the page will be embedded in your Message Extension.
Open the Url in the Companion App
On a click on the message, you could open your Companion app (through
the Url Scheme with MyApp://?myParam=myValue) with a special parameter
; the Companion app should react to this parameter and could redirect
to Safari through OpenUrl.
In this case, you'll several redirects before the WebPage, but it should allow to go back to the conversation.
We have also found that we could instance a SKStoreProductViewController in a Message Extension, if you want to open the Apple Store right in Messages and let the user buy items.
If you only need to insert a link, then you should use activeConversation.insertText and insert the link. Touching the message will open Safari.
openURL in didSelectMessage:conversation: by using extensionContext
handle the URL scheme in your host AppDelegate

iOS/Swift - Is there a way we can link our app as a WhatsApp message?

I have my app running on my iPhone, and I'm able to send some data to my WhatsApp from my App. I went through the following to do the same :
Sending message to WhatsApp from your app using Swift?
Is there a way we can create a URL that would open our own app directly if it is installed? Could someone suggest any place where I could understand how it could be done?
Something like this should work:
let originalString = "Some text you want to send"
let encodedString = originalString.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let url = NSURL(string: "whatsapp://send?text="+encodedString!)
if UIApplication.sharedApplication().canOpenURL(url!) {
UIApplication.sharedApplication().open(url as URL, options: [:]) { (success) in
if success {
print("WhatsApp accessed successfully")
} else {
print("Error accessing WhatsApp")
}
}
} else {
print("whatsapp not installed")
}

Resources