Load url in WKWebView when notification is opened - swiftui

I'm sending push notification's with OneSignal, I'm receiving them fine. My problem is, is that when I click on the notification, I'd like it to open the notification url. I have the notificationUrl, but loading the url in my WKWebView is my problem. I am using SwiftUi App lifecycle, and not UiKit. So take in note that I don't have a ViewController.
I've tried using UIApplication.shared.open(URL(string: notificationUrl)!) but this opens the link in the browser.
Here's my notification opened handler in AppDelegate.
.....
let osNotificationOpenedBlock: OSNotificationOpenedBlock = { resultObj in
let notification: OSNotification = resultObj.notification
let actionType = resultObj.action.type
let dataObj = resultObj.notification.additionalData
let notificationUrl = "\(dataObj?["notificationUrl"] ?? "")"
UIApplication.shared.open(URL(string: notificationUrl)!)
}
OneSignal.setNotificationOpenedHandler(osNotificationOpenedBlock)
....

What worked for me, is to make the webview variable a global variable in the code. So in WebView.swift or your webview file, put the WKWebView variable on the outside of the struct and apply:
var wbWebViewEl: WKWebView = WKWebView()
struct SwiftUiWebView: UIViewRepresentable {
...
Then, instead of:
UIApplication.shared.open(URL(string: notificationUrl)!)
Do:
var notificationRequestEl = URLRequest(url: url!)
wbWebViewEl.load(notificationRequestEl)

Related

SwiftUI open ShareLink from ActionSheet (confirmationDialog) Not Working

I have a ShareLink inside an ActionSheet (confirmationDialog) but it's not opening. Can anyone confirm if this is possible or not? I imagine it's something to do with the action sheet being dismissed at the same time.
.confirmationDialog("", isPresented: $isShowingMoreActionSheet, titleVisibility: .hidden) {
if let url = URL(string: "https://www.google.com") {
ShareLink("Share", item: url)
}
}

Title and sashColor not appearing on customized long-look notification with WKUserNotificationHostingController

I am building custom long-look notifications in an Apple Watch app, but for some reason the title defined in the notification's UNMutableNotificationContent is not showing on the long-look notification, and the custom sashColor I’m defining is not used.
In the “Transition to the Long-Look Interface” section of Presenting Notifications on Apple Watch on Apple’s website, there is a screenshot that shows what I would expect to see: a title for the notification in the sash, and a custom sash color.
I built an example app (code below) to isolate the issue.
Here is a screenshot of the notification in my app:
I expect to see the title (“Take Action!”) where the line is, and the sashColor as the background color for the circled region, based on my code.
The short-look of the notification does show the title briefly before it transitions to the long-look (it was hard to get a good screenshot, but here is one as it was animating into the long-look):
Showing or hiding the notification title is not mentioned anywhere that I can find in the documentation, so I expect that to show up automatically since it’s part of the notification.
For the sashColor override, I referred to Customizing Your Long-Look Interface on Apple’s website.
Is there something else specific I need to do to show the title on my customized long-look notification, and get sashColor to work?
Example App
To recreate the issue, create a watchOS app with companion iOS app in Xcode. I called it CustomWatchNotifications.
I updated the main iOS app file to this, with a simple class to request notification permission and send a test notification, which gets passed into the view:
import SwiftUI
import UserNotifications
#main
struct CustomWatchNotificationsApp: App
{
let notifications = NotificationController()
var body: some Scene
{
WindowGroup
{
ContentView(notifications: notifications)
}
}
}
class NotificationController
{
func requestPermissions()
{
Task {
try await UNUserNotificationCenter.current()
.requestAuthorization(
options: [.alert, .sound])
}
}
func scheduleNotification()
{
let content = UNMutableNotificationContent()
content.title = "Take Action!"
content.categoryIdentifier = "takeActionCategory"
content.sound = .default
// Schedule a new notification 5 seconds from now,
// so there is enough time to lock the phone screen
// to deliver notification to Apple Watch.
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: 5,
repeats: false)
let request = UNNotificationRequest(
identifier: "takeAction",
content: content,
trigger: trigger)
UNUserNotificationCenter.current()
.add(request)
}
}
This is the ContentView for the iOS app, which just includes the two buttons:
import SwiftUI
struct ContentView: View
{
let notifications: NotificationController
var body: some View
{
NavigationView
{
Form
{
// Request notification permissions
Button
{
notifications.requestPermissions()
} label: {
Text("Request Notification Permissions")
}
// Schedule notification
Button
{
notifications.scheduleNotification()
} label: {
Text("Schedule Notification")
}
}
}
}
}
On the watchOS side, I updated the main app file to include a custom View for the notification, inside a WKUserNotificationHostingController for this specific notification category:
import SwiftUI
import UserNotifications
#main
struct CustomWatchNotifications_Watch_AppApp: App
{
var body: some Scene
{
WindowGroup
{
ContentView()
}
WKNotificationScene(
controller: TakeActionNotificationController.self,
category: "takeActionCategory"
)
}
}
struct TakeActionNotificationView: View
{
var body: some View
{
Text("This is a test.")
}
}
class TakeActionNotificationController:
WKUserNotificationHostingController<TakeActionNotificationView>
{
// This does not seem to have an effect on sashColor.
override class var sashColor: Color?
{
return .red
}
override var body: TakeActionNotificationView
{
return TakeActionNotificationView()
}
// This has to be here for custom notification to show up.
override func didReceive(_ notification: UNNotification)
{}
}
When you build and run on real devices, make sure the watchOS app is installed before schedule the test notification. Once you schedule the test notification, lock the iPhone screen immediately so the notification gets delivered to Apple Watch.

Creating WebView using SwiftUI 2.0 can't create a view from UIViewRepresentable

I'm trying create a WebView view in SwiftUI 2.0 Xcode 12.4 but it's so new I can't find good examples for answers and best practice. For the code below I get the following error.
Type 'SwiftUIWebView' does not conform to protocol 'UIViewRepresentable'
Also setting the config has changed and I can't enable .allowsContentJavaScript properly. Trying to add it to the "perfs" triggers an error complaining about not liking a Bool.
import SwiftUI
import WebKit
struct SwiftUIWebView: UIViewRepresentable {
let url: URL?
func makeUIView(context: Context) -> some WKWebView {
let prefs = WKWebpagePreferences()
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
return WKWebView(frame: .zero, configuration: config) //frame CGRect
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let myURL = url else { return }
let request = URLRequest(url: myURL)
uiView.load(request)
}
}
Remove some from this line:
func makeUIView(context: Context) -> some WKWebView {
some is a specific keyword used when dealing with generics and opaque types (see this link for more reading: What is the `some` keyword in Swift(UI)?). In this case, you don't need to generic-ize WKWebView, since you know exactly what it is.
You can set the prefs.allowsContentJavaScript property by doing this:
prefs.allowsContentJavaScript = true

WKWebView in SwiftUI not loading HTML string on macOS

first question on StackOverflow (and relatively new to native app dev targeting macOS).
I'm currently trying to build a simple SwiftUI view, that leverages WKWebView's loadHTMLString function, to display hardcoded HTML string on the screen.
AFAIK webkit does not support SwiftUI as of the moment, so I need to embed AppKit UI in my SwiftUI app using NSViewRepresentable. This what I got so far following the docs and XCode's autocomplete:
import SwiftUI
import WebKit
struct HTMLView: NSViewRepresentable {
typealias NSViewType = WKWebView
let html = "<h1>Hello wordl</h1>"
func makeNSView(context: Context) -> WKWebView {
let webview = WKWebView()
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) {
nsView.loadHTMLString(html, baseURL: nil)
}
}
struct HTMLView_Previews: PreviewProvider {
static var previews: some View {
HTMLView()
}
}
Should be noted that preview canvas does not load the HTML (displays empty window).
I then replace the default Text() view in ContentView.Swift with HTMLView(), and run my application.
The application compiles, but the WebView fails to load the HTML (I get an empty window). I get the following errors in console:
WebPageProxy::processDidTerminate: (pid 0), reason 3
WebPageProxy::dispatchProcessDidTerminate: reason = 3
WebPageProxy::processDidTerminate: (pid 0), reason 3
WebPageProxy::dispatchProcessDidTerminate: reason = 3
WebPageProxy::tryReloadAfterProcessTermination: process crashed and the client did not handle it, not reloading the page because we reached the maximum number of attempts
Any help with the above would be highly appreciated!
On macOS, although it doesn't seem like this should be necessary, you need to set "Outgoing Connections (Client)" to true in your "Signing and Capabilities" on your target in order for WKWebView to load, even though you're loading from a string and not from an external page.
As soon as I changed this, your example worked fine.

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.