WatchOS, SwiftUI: How to send local notifications with a 'View' as body - swiftui

When creating the XCode project, I selected Apple Watch Application and enabled "Include Notification".
This lead me to have a NotificationView.swift and a NotificationController.swift in my project.
I have filled the NotificationView.swift with the View content i would like to be in my local notification.
In my regular HostingController.swift I would now like to schedule a local notification with the content of NotificationView.swift.
So far, I am using the current code:
let userNotificationCenter = UNUserNotificationCenter.current()
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "title"
notificationContent.body = "body"
notificationContent.categoryIdentifier = "categoryNameDummy"
let category = UNNotificationCategory(identifier: "categoryNameDummy", actions: [], intentIdentifiers: [] as? [String] ?? [String](), options: .customDismissAction)
let categories = Set<AnyHashable>([category])
userNotificationCenter.setNotificationCategories(categories as? Set<UNNotificationCategory> ?? Set<UNNotificationCategory>())
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: trigger)
userNotificationCenter.add(request) { (error) in
if let error = error {
debugPrint(error)
}
}
I have not made any changes to Info.plist regarding the categoryIdentifier.
What and where do I have to add code to now "catch" this notification and fill it with my custom NotificationView.swift content?

Did you ask the user for permission to send notifications?
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert]) { success, error in
if success {
print("Ok!")
} else if let error = error {
print(error.localizedDescription)
}
}
It only presents a permission alert the first time: once given permission it won't show up again.

Related

Im trying to repeatedly fetch data from a http api and update my SwiftUI view accordingly

im trying to fetch some data from a Http Api and use the responded JSON to update the text in my SwiftUI view accordingly
The problem is, that I need to fetch the data two times per second and I don't really know how to refresh my view accordingly.
This is how I fetch the data
var timer = Timer()
func scheduleTimer()
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(fetchData), userInfo: nil, repeats: true)
}
#objc func fetchData() {
let url:URL = URL(string: "http://XXX.XXX.XXX.23:8085/telemachus/datalink?altitude=v.altitude&longitude=v.long&latitude=v.lat&name=v.name")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error) }
guard let data = data else {return}
do {
let jsonData = try JSONDecoder().decode(JsonData.self, from: data)
let altitude = jsonData.altitude
print(altitude)
} catch let error {
print(error)
}
}.resume()
}
I used a timer to repeat this action. I also have a Model for the JsonData.
#EnvironmentObject is not usable in this case because the function is not inside the view.
First of all, This will kill the battery very quick. Also it may kill the bandwidth and the user may be blocked for too many requests.
Second of all, you should use socket for these kind of requests instead of restful.
Third of all your answer:
Two times per second? change the timeInterval value to 0.5. This will cause the execution to perform each half of second.
To update UI, you need to perform the update code in the main thread using the GCD or Operation queue like this:
DispatchQueue.main.async {
/* UI Work Goes Here */
// for example: myLabel.text = jsonData.altitude
}

How to open Specific View controller on Widgets/ Today Extension click

I am trying to open specific view controller on widgets click , but can not able to open it , i am able to open app using url schema but i want to open specific view controller how can i do this, here is code for open app using url schema :
#IBAction func open_app(_ sender: Any)
{ extensionContext?.open(URL(string: "open://")! ,
completionHandler: nil)
}
on button click i am opeing app sucessfully using url schema. but now i want to open specific view controller on that click how can i do this?
According to your requirement, I have created a sample to get this working correctly.
1. First of all in TodayViewController interface, create 3 different UIButtons and give their tag values to uniquely identify them.
Here I have given tags as: 1, 2, 3 to First, Second and Third UIButton.
2. Next you need to write the code to open your Containing App from Today Extension. In TodayViewController create an #IBAction for and connect it to all three UIButtons.
#IBAction func openAppController(_ sender: UIButton)
{
if let url = URL(string: "open://\(sender.tag)")
{
self.extensionContext?.open(url, completionHandler: nil)
}
}
In the above code, tag will be added to the url scheme to identify which UIViewController needs to be opened on UIButton press. So the url will look something like: open://1
3. In the Containing App's URL Types need to make an entry for URL Scheme, i.e
As evident from the above screenshot, there is no need to make entry for each url that you want to open from your extensions. URLs having same url scheme have only a single entry.
4. When the containing app is opened from extension, you can get the handle in AppDelegate’s application(_ : url: sourceApplication: annotation: ) method. Here, you can handle which controller to open i.e.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
{
if url.scheme == "open"
{
switch url.host
{
case "1":
//Open First View Controller
case "2":
//Open Second View Controller
case "3":
//Open Third View Controller
default:
break
}
}
return true
}
url.scheme identifies the scheme of URL i.e. open and url.host identifies the host component in the URL which is currently set to the UIButton's tag value which you can use to uniquely identify which UIButton is pressed and what to de next accordingly.
For more on Today Extensions, you can refer to: https://hackernoon.com/app-extensions-and-today-extensions-widget-in-ios-10-e2d9fd9957a8
Let me know if you still face any issues regarding this.
add a new scheme for your App
enter image description here
as Shown above image...
then, write a code below on IBAction of your Today Extension
#IBAction func btnFirstWidgetAction() {
let url: URL? = URL(string: "schemename://secondViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
#IBAction func btnSecondWidgetAction() {
let url: URL? = URL(string: "schemename://secondViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
#IBAction func btnThirdWidgetAction() {
let url: URL? = URL(string: "schemename://thirdViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
than, add method application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) in AppDelegate file and write code to redirect in specific ViewController in this method.
//call when tap on Extension and get url that is set into a ToadyExtension swift file...
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlPath : String = url.absoluteString
print(urlPath)
if self.isContainString(urlPath, subString: "firstViewController") {
//here go to firstViewController view controller
}
else if self.isContainString(urlPath, subString: "firstViewController") {
//here go to secondViewController view controller
}
else {
//here go to thirdViewController view controller
}
return true
}
this method used for check your string is contains as sub string that are given in widget button action. if contain than true otherwise false
func isContainString(_ string: String, subString: String) -> Bool {
if (string as NSString).range(of: subString).location != NSNotFound { return true }
else { return false }
}
In xCode 11 if you are using sceneDelegate, follow the same logic as described by Malik and Mahesh but use the following function in the SceneDelegate instead:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
//Do stuff with the url
}
}
(instead of application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool)
In details:
First:
Add a url scheme in your project -> info -> url Types -> add url Scheme. Here you can get started by filling the 'URL Schemes" field only (with your app name for instance).
Second:
In your extension, use the following function (called by a button for instance):
let urlString = "MyAppName://host/path"
if let url = URL(string: urlString)
{
self?.extensionContext?.open(url, completionHandler: nil)
}
Third:
Implement your logic in Scene Delegate with:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
//Do stuff with the url
}
}
Swift5
Step1: select project>info>url types>add url scheme
step2: go to the button action method and use this code
let tag = 1
if let url = URL(string: "open://\(tag)")
{
self.extensionContext?.open(url, completionHandler: nil)
}
step 3: welcome you get the control of your host app, jus add this in app delegate method
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
if url.scheme == "open"
{
switch url.host
{
case "1":
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
default:
break
}
}
return true
}
Congrats! you open the controller.

Notification Content Extension is not appearing for multiple category name?

I have added Custom Remote Notification Content Extension to my project and added multiple Extension categories into the Notification Content Extension target info.plist file like the following:
added different types of notification action categories for different notifications into the AppDelegate:
func addRichRotificationActions() {
let confirmAction = UNNotificationAction(identifier: "ConfirmAction", title: "Confirm", options: [.foreground])
let cancelAction = UNNotificationAction(identifier: "CancelAction", title: "Cancel", options: [.destructive])
let closeAction = UNNotificationAction(identifier: "CloseAction", title: "Close", options: [.foreground])
let openTicketCategory = UNNotificationCategory(identifier: "OpenTicket", actions: [confirmAction, cancelAction], intentIdentifiers: [], options: [])
let confirmTicketCategory = UNNotificationCategory(identifier: "ConfirmTicket", actions: [closeAction, cancelAction], intentIdentifiers: [], options: [])
let closeTicketCategory = UNNotificationCategory(identifier: "CloseTicket", actions: [], intentIdentifiers: [], options: [])
let cancelTicketCategory = UNNotificationCategory(identifier: "CancelTicket", actions: [], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([openTicketCategory, confirmTicketCategory, closeTicketCategory, cancelTicketCategory])
}
Now I am sending the apns json following way:
For Open tickets getting category name as "OpenTicket":
[AnyHashable("default"): You have a new ticket, AnyHashable("aps"): {
alert = "#8556 - New Booking for Mr. Tomas";
badge = 1;
category = OpenTicket;
"mutable-content" = 1;
sound = default;
}]
For Confirm tickets getting category name as "ConfirmTicket":
[AnyHashable("default"): You have a confirmed ticket, AnyHashable("aps"): {
alert = "#8556 - Ticket Confirmed for Mr. Tomas";
badge = 1;
category = ConfirmTicket;
"mutable-content" = 1;
sound = default;
}]
and so on.
But unfortunately, I am receiving the default notification with different action buttons rather than the custom notification content extension with different actions. I can't able to figure out the problem. How is it possible to get notification content extension with different actions for remote notification?
Just need to make the UNNotificationExtensionCategory as an Array rather than String in the info.plist of Notification Content Extension target.

Upload Stream Requests and UIProgressView, Swift 3

I would like to track the progress of videos uploaded through a stream request with a UIProgressView. Unfortunately, I am not using Alamofire, so I'm not sure if URLSession has this ability. Below is relevant code from my application.
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
let uploadCell = contentTableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! NewContentCell
uploadCell.uploadProgressView.progress = uploadProgress
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let uploadCell = contentTableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! NewContentCell
uploadCell.uploadProgressView.progress = 1.0
}
didCompleteWithError correctly sets the UIProgressView to indicate that the upload is complete, however, didSendBodyData is returning values greater than 1 through the uploadProgress calculation.
It's my first time utilizing a stream request, so I'm hoping I simply glossed over something that you could help point out. Here is the structure of my request as well for reference.
let request = NSMutableURLRequest(url: NSURL(string: "\(requestUrl)")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBodyStream = InputStream(data: body as Data)
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
let dataTask = session.uploadTask(withStreamedRequest: request as URLRequest)
dataTask.resume()
Much thanks for your input and help.
Implementing
public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
is the correct way to track stream request progression.
But if you want to now the totalBytesExpectedToSend, you must tell it to the server. So don't forget to set the correct Content-Length header in your request!
Here's the way i'm creating the request:
var request = URLRequest(url: url)
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.addValue(String(dataToUpload.count), forHTTPHeaderField: "Content-Length") // <-- here!
request.httpBodyStream = InputStream(data: dataToUpload)
var task = session.uploadTask(withStreamedRequest: request)
task?.resume()
Reading documentation further, figured out that stream objects do not support totalBytesExpectedToSend. It may be a hack, but just using the file's NSData.length feature allows for correct progress tracking. So for stream requests using URLSession, progress can be tracked by using didSendBodyData, with let uploadProgress: Float = Float(totalBytesSent) / Float(mediaSize), where mediaSize is NSData.length.

How to send json request to server and not wait on response

I am building mobile applications to go with my django based backend. I make a post request in swift like this:
var request: NSMutableURLRequest = NSMutableURLRequest()
var url = "https://webapp.com/makepost/"
url += NSUserDefaults.standardUserDefaults().stringForKey("userPk")!
url += "/"
var err: NSError?
request.URL = NSURL(string: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: NSJSONWritingOptions(rawValue:0))
} catch _ {
print ("error")
}
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) {(response, data, error) -> Void in
var query = String(data: data!, encoding: NSUTF8StringEncoding)
query = query!.stringByReplacingOccurrencesOfString("Optional(", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
query = query!.stringByReplacingOccurrencesOfString(")", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
print(query)
//update ui
}
As soon as I make this post it creates the necessary models in django by reading the jsonObject.
The response doesn't matter and could take long as I'm notifying other users via FCM.
This is what I'm trying to do:
Make a post request.
Ignore the response.
Update the UI immediately after I make the post request.
How should I achieve this?
like so
//set request variable here
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) {(response, data, error) -> Void in
var query = String(data: data!, encoding: NSUTF8StringEncoding)
}
sleep(2)
// wait two seconds for models to create in backend
dispatch_async(dispatch_get_main_queue()) {
self.refresh(self.refreshButton)
//refresh ui here with method that fires another get request
}
}