I am using WKWebView
to load the URL for my hybrid web application. When I receive push notifications, I use deep linking to handle them by invoking the evaluateJavaScript
function of WebView.
It should function as follows: when a push notification is tapped, it should take the user to the appropriate page (in the app) using the deep link URL. However, we occasionally notice that it is rerouting to the mobile app’s home page or the page that was already accessed during the prior session or some random page of the app. In the issue case, we have noticed the error.
When I click the push notification it navigation should be to the direct designated screen not Home Screen.
Here I shared the code what I tried.
** // AppDelegate ** here I called the deeplink manager comman class for initial setup and return the url
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DeepLinkManager.initialSetup(launchOptions: launchOptions) //Here I initialise the deep linking in app delegate when launch the app
window?.backgroundColor = .white
window?.tintColor = assetsAssets.colors.tintColor
return true}
public func application(
_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return DeepLinkManager.handleDeepLink(app: app, open: url, options: options)}}
——> ViewController Class—— //This class I used for load the content in web view data and deep linking for redirect when receiving push notification
class ViewController: UIViewController, WKUIDelegate {
private var deepLinkObserver: Task<Void, Never>? {
willSet {
deepLinkObserver?.cancel()
}}
private var webView: WKWebView!
private let processPool = WKProcessPool()
override func viewDidLoad() {
super.viewDidLoad()
let dataStore = WKWebsiteDataStore.default()
let configuration = WKWebViewConfiguration()
let userController = WKUserContentController()
configuration.allowsInlineMediaPlayback = true
configuration.allowsPictureInPictureMediaPlayback = true
configuration.allowsAirPlayForMediaPlayback = true
configuration.processPool = processPool
configuration.websiteDataStore = dataStore
configuration.userContentController = userController
let webview = WKWebView(frame: .zero, configuration:
configuration)
webview.translatesAutoresizingMaskIntoConstraints = false
webview.allowsBackForwardNavigationGestures = true
webview.allowsLinkPreview = false
#if !PRODUCTION
if #available(iOS 16.4, *) {
webview.isInspectable = true
}
#endif
self.webView = webview
let trampoline = Trampoline(delegate: self)
for `case` in WebAppMessageName.allCases {
let name = `case`.rawValue
Self.logger.debug("subscribed for %@", name)
userController.add(trampoline, name: name)
}
deepLinkObserver = Task {
for await url in DeepLinkManager.shared.deepLinkStream {
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
if components?.queryItems == nil {
components?.queryItems = []
}
let magicNumberQueryItem = URLQueryItem(name: "magicNumber", value: "100")
components?.queryItems?.append(magicNumberQueryItem)
guard let modifiedUrl = components?.url?.absoluteString else { return }
os_log("Attempting to navigate by deep link: %{public}@", log: ViewController.osLogger, type: .debug, modifiedUrl)
do {
let _ = try await webView.evaluateJavaScript("window.mobileNavigateByDeeplink('(modifiedUrl)');true")
os_log("Successfully navigated by deep link: %{public}@", log: ViewController.osLogger, type: .info, modifiedUrl)
} catch {
os_log("Error navigating by deep link: %{public}@, error: %{public}@", log: ViewController.osLogger, type: .error, modifiedUrl, error.localizedDescription)
}
}
}
webView.navigationDelegate = self
webView.uiDelegate = self
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.scrollView.bounces = false
guard let superview = view else {
return
}
superview.backgroundColor = .white
superview.insertSubview(webview, belowSubview: loadShutter)
superview.addConstraints(
[
webView.topAnchor.constraint(equalTo: superview.topAnchor),
webView.leftAnchor.constraint(equalTo: superview.leftAnchor),
webView.bottomAnchor.constraint(equalTo: superview.bottomAnchor),
webView.rightAnchor.constraint(equalTo: superview.rightAnchor)
]
)}
deinit {
notificationSubscriptions = nil
deepLinkObserver = nil
fallbackTask = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
notificationSubscriptions = [
UIApplication.didBecomeActiveNotification,
Notification.Name.BatchPushUserDidAnswerAuthorizationRequest
].map { name in
Task { [weak self] in
for await _ in NotificationCenter.default.observe(
name: name,
object: nil,
queue: .main
) {
guard let self = self else {
break
}
self.getNotificationsStatus()
}
}
}}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
getNotificationsStatus()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
notificationSubscriptions = nil
}
func load(url: URL, fallbackTimeout: Double) {
webView.load(.init(url: url))
}
}
extension ViewController: WKNavigationDelegate {
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
guard navigationAction.navigationType == .linkActivated, !excludedDomains.contains(url.host?.replacingOccurrences(of: "www.", with: "") ?? "") else {
decisionHandler(.allow)
return
}
UIApplication.shared.open(url)
decisionHandler(.cancel)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
Self.logger.debug("didFinish")
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
Self.logger.error("error: %@", String(describing: error))
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
Self.logger.error("provisional navigation error: %@", String(describing: error))
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
self.webView.load(navigationAction.request)
return nil
}}
extension ViewController: WKScriptMessageHandler {
struct Attributes: Codable {
let isOptinEditor, isOptinOpta: Bool?
let userRegion: String?
enum CodingKeys: String, CodingKey {
case isOptinEditor = "is_optin_editor"
case isOptin = "is_optin_"
case userRegion = "user_region"
}}
struct Settings: Codable {
let region: String?
let apiRegion: String?
}
struct AppLink: Codable {
let appURLString: String
let installURLString: String
enum CodingKeys: String, CodingKey {
case appURLString = "app_url"
case installURLString = "install_url"
}
}
typealias TagsPayloadModel = PushNotificationManager.TagsPayloadModel
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
Self.logger.debug("postMessage name: %@, body: %@", message.name, String(describing: message.body))
if message.body as? String == Constants.WebMessage.articleTapped {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
SKStoreReviewController.requestReview()
}
}
guard let body = message.body as? String else {
return
}
guard let message = WebAppMessageName(rawValue: message.name) else {
return
}
switch message {
case .tags:
guard
let data = body.data(using: .utf8),
let tags = try? JSONDecoder().decode(
[TagsPayloadModel].self,
from: data
)
else {
assertionFailure("Can not handle tags payload: (body)")
break
}
PersonalisationManager.shared.handle(tags)
Task {
do {
PushNotificationManager.handle(
tags,
isHaveTeamWidgets: try await WidgetsManager.checkIfTeamsWidgetInstalled()
)
} catch {
Self.logger.error("check widgets installed error: %@", String(describing: error))
}
}
case .attributes:
if let bodyData = body.data(using: .utf8) {
PushNotificationManager.handle(data: bodyData)
if let attributes = try? JSONDecoder().decode(Attributes.self, from: bodyData) {
PushNotificationManager.set(attributes: attributes)
}
}
case .settings:
if
let bodyData = body.data(using: .utf8),
let settings = try? JSONDecoder().decode(Settings.self, from: bodyData) {
BISVersionManager.shared.set(settings: settings)
}
case .statusBar:
switch body.trimmingCharacters(in: .init([#"""#])) {
case "dark":
self.statusBarStyle = .darkContent
case "light":
self.statusBarStyle = .lightContent
default:
Self.logger.error("unknown status bar style %@", body)
break
}
break
case .events:
switch body {
case "readyToShow":
showContent()
default:
Self.logger.error("unknown event: %@", body)
break
}
case .notificationsPermissions:
Task {
do {
try await PushNotificationManager.toggleNotifications(turnOn: true)
} catch PushNotificationManager.Error.notificationsDenied {
URL(
string:UIApplication.openSettingsURLString
).map {
UIApplication.shared.open($0)
}
}
}
case .appLink:
if
let bodyData = body.data(using: .utf8),
let appLink = try? JSONDecoder().decode(AppLink.self, from: bodyData) {
if let appURL = URL(string: appLink.appURLString), UIApplication.shared.canOpenURL(appURL) {
UIApplication.shared.open(appURL)
} else if let installURL = URL(string: appLink.installURLString) {
UIApplication.shared.open(installURL)
}
}
}
}}
WKErrorDomain: Error Domain=WKErrorDomain Code=4 “A JavaScript exception occurred” WKJavaScriptExceptionMessage=TypeError: window.mobileNavigateByDeeplink is not a function.
What might be the issue?
SaravanaKumar_TEL is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.