Web 应用程序在 iOS 问题上自动重定向到初始页面(不应重定向)
Web app auto-redirecting to the initial page on iOS problem (it should NOT redirect)
我有一个用于 Android 和 iOS 的网络应用程序,可以打开一个网页。该应用程序适用于 Android 和大多数 iOS 设备。
但出于某种原因,对于某些 iOS 用户,该应用会在使用该应用几分钟后随机将用户重定向到初始页面。
用户在初始页面上打开应用程序,点击 link 进入另一个页面,用户正在阅读该页面,然后由于某种原因应用程序重定向回初始页面。
应用程序中没有 JavaScript 重定向代码,有一个服务工作者,但没有重定向。
它不会一直发生,但它确实会发生并惹恼用户。
对可能发生的事情有什么想法吗?
更新:代码片段
ViewController.swift
import UIKit
import WebKit
import Firebase
import UserNotifications
class ViewController: UIViewController , WKNavigationDelegate, WKUIDelegate{
@IBOutlet weak var loading: UIActivityIndicatorView!
@IBOutlet weak var screenSplash: UIImageView!
@IBOutlet weak var webView: WKWebView!
let reachability = Reachability()!
var request = URLRequest(url: URL(string: "https://app.com/webview")!)
var screen = CGRect.zero
var flag = 0
override func viewDidLoad() {
super.viewDidLoad()
screen = UIScreen.main.bounds
webView.frame.origin.x = 0
webView.frame.origin.y = 0
webView.frame.size.height = screen.height
webView.frame.size.width = screen.width
webView?.navigationDelegate = self
webView.scrollView.bounces = false
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
webView?.load(request)
self.view.addSubview(webView)
screen = UIScreen.main.bounds
webView.frame.origin.x = 0
webView.frame.origin.y = 0
webView.frame.size.height = screen.height
webView.frame.size.width = screen.width
webView.navigationDelegate = self
self.webView?.uiDelegate = self
view.addSubview(webView!)
}
@objc func internetChanged(note: Notification){
let reachability = note.object as! Reachability
if reachability.connection != .none{
print("Volvio la conexion")
webView?.load(request)
viewDidLoad()
}else{
//let alert = UIAlertController(title: "", message: "Es necesario tener una conexión activa a internet", preferredStyle: .alert)
//alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
//self.present(alert, animated: true, completion: nil)
print("Es necesario tener una conexión activa a internet")
let htmlPath = Bundle.main.path(forResource: "error", ofType: "html")
let folderPath = Bundle.main.bundlePath
let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
do{
let htmlString = try NSString(contentsOfFile:htmlPath!, encoding:String.Encoding.utf8.rawValue)
self.webView.loadHTMLString(htmlString as String,baseURL: baseUrl)
}catch{
}
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
{
}
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
{
let fmcToken=InstanceID.instanceID().token() as! String
print(fmcToken)
webView.evaluateJavaScript("(function() { if(typeof sendDeviceToken === 'function'){ return sendDeviceToken('ios','\(fmcToken)'); }else{ return false; } })()", completionHandler: { (data, error) in
if let err = error {
print(err)
print(err.localizedDescription)
} else {
guard let dataValue = data else {return}
print("res sendDeviceToken")
print(dataValue)
}
})
screenSplash.isHidden = true
loading.isHidden = true
webView.evaluateJavaScript("document.getElementsByTagName('meta')['viewport'].content='initial-scale=1.0, user-scalable=no';"){
(result,error) in if error != nil {
print(result ?? "")
}
}
if(flag==0){
NotificationCenter.default.addObserver(self, selector: #selector(internetChanged), name: Notification.Name.reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("No se pudo iniciar la notificacion")
}
flag=flag+1
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
if let url = navigationAction.request.url,
let host = url.host, !host.hasPrefix("app.com") || url.absoluteString.contains("/share/"),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
print(url)
print("Redirected to browser. No need to open it locally")
decisionHandler(.cancel)
} else {
// print("Open it locally")
decisionHandler(.allow)
}
} else {
// print("not a user click")
decisionHandler(.allow)
}
}
//fix the alert
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler()
}))
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(false)
}))
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(nil)
}))
self.present(alertController, animated: true, completion: nil)
}
//end fix the alert
func clickActionOpen(action: String) {
request = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)
var requestClick = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)
webView.load(requestClick)
}
}
服务人员
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
// resources to be cached again.
// https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/custom-offline-page/service-worker.js
var CACHE_VERSION = 'v4'
var CACHE_NAME = CACHE_VERSION + ':sw-cache-'
function onInstall(event) {
console.log('[Serviceworker]', 'Installing!', event)
event.waitUntil(
caches.open(CACHE_NAME).then(function prefill(cache) {
return cache.addAll([
'<%= asset_path "admin.js" %>',
'<%= asset_path "admin.css" %>',
'<%= asset_path "site/homepage-logo.png" %>'
])
})
)
}
function onActivate(event) {
console.log('[Serviceworker]', 'Activating!', event)
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames
.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
return cacheName.indexOf(CACHE_VERSION) !== 0
})
.map(function(cacheName) {
return caches.delete(cacheName)
})
)
})
)
}
// Borrowed from https://github.com/TalAter/UpUp
// then from https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onfetch
function onFetch(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
// console.log('Found response in cache:', response)
return response
}
// console.log('No response found in cache. About to fetch from network...')
return fetch(event.request).then(function(response) {
// console.log('Response from network is:', response)
return response
}).catch(function(error) {
// console.error('Fetching failed:', error)
throw error
})
})
)
}
self.addEventListener('install', onInstall)
self.addEventListener('activate', onActivate)
self.addEventListener('fetch', onFetch)
原因可能是
- ISP(互联网服务提供商)
- 对于 ruby-on-rails,身份验证并使用他们的 authentication_token 来验证用户对您的 API 的网络请求。否则,应用程序将终止。
希望对您有所帮助。
根据您所解释的情况,如果在意外情况下调用 internetChanged
方法,页面可能会重新加载。
如果在显示 viewController 时设备的网络状态发生变化,我可以看到这种情况会发生。您已在方法 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
中观察到通知。所以当网络状态改变时,internetChanged
方法将被调用。在方法的if
和else
中,都有加载webView的逻辑。
我还看到,您已经观察到通知但从未移除观察者。所以 ViewController 会有内存泄漏,可能不会从内存中释放。我建议您删除 deinit
中的观察者
另一件事是,您可以更好地观察 -viewDidLoad
中的通知,而不是 didFinish
方法。现在按照当前逻辑发生的事情是,每当重新加载 webView 时,一个新的观察者将被添加到方法 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
中的 ViewController。所以让我们以某种方式调用 internetChanged
方法 3 次,然后将添加 3 个观察者。因此,下次网络更改时,它会使 internetChanged
被调用三次,导致 webPage 依次加载 3 次,并继续循环。
我有一个用于 Android 和 iOS 的网络应用程序,可以打开一个网页。该应用程序适用于 Android 和大多数 iOS 设备。
但出于某种原因,对于某些 iOS 用户,该应用会在使用该应用几分钟后随机将用户重定向到初始页面。
用户在初始页面上打开应用程序,点击 link 进入另一个页面,用户正在阅读该页面,然后由于某种原因应用程序重定向回初始页面。
应用程序中没有 JavaScript 重定向代码,有一个服务工作者,但没有重定向。
它不会一直发生,但它确实会发生并惹恼用户。
对可能发生的事情有什么想法吗?
更新:代码片段
ViewController.swift
import UIKit
import WebKit
import Firebase
import UserNotifications
class ViewController: UIViewController , WKNavigationDelegate, WKUIDelegate{
@IBOutlet weak var loading: UIActivityIndicatorView!
@IBOutlet weak var screenSplash: UIImageView!
@IBOutlet weak var webView: WKWebView!
let reachability = Reachability()!
var request = URLRequest(url: URL(string: "https://app.com/webview")!)
var screen = CGRect.zero
var flag = 0
override func viewDidLoad() {
super.viewDidLoad()
screen = UIScreen.main.bounds
webView.frame.origin.x = 0
webView.frame.origin.y = 0
webView.frame.size.height = screen.height
webView.frame.size.width = screen.width
webView?.navigationDelegate = self
webView.scrollView.bounces = false
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
webView?.load(request)
self.view.addSubview(webView)
screen = UIScreen.main.bounds
webView.frame.origin.x = 0
webView.frame.origin.y = 0
webView.frame.size.height = screen.height
webView.frame.size.width = screen.width
webView.navigationDelegate = self
self.webView?.uiDelegate = self
view.addSubview(webView!)
}
@objc func internetChanged(note: Notification){
let reachability = note.object as! Reachability
if reachability.connection != .none{
print("Volvio la conexion")
webView?.load(request)
viewDidLoad()
}else{
//let alert = UIAlertController(title: "", message: "Es necesario tener una conexión activa a internet", preferredStyle: .alert)
//alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
//self.present(alert, animated: true, completion: nil)
print("Es necesario tener una conexión activa a internet")
let htmlPath = Bundle.main.path(forResource: "error", ofType: "html")
let folderPath = Bundle.main.bundlePath
let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
do{
let htmlString = try NSString(contentsOfFile:htmlPath!, encoding:String.Encoding.utf8.rawValue)
self.webView.loadHTMLString(htmlString as String,baseURL: baseUrl)
}catch{
}
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
{
}
func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
{
let fmcToken=InstanceID.instanceID().token() as! String
print(fmcToken)
webView.evaluateJavaScript("(function() { if(typeof sendDeviceToken === 'function'){ return sendDeviceToken('ios','\(fmcToken)'); }else{ return false; } })()", completionHandler: { (data, error) in
if let err = error {
print(err)
print(err.localizedDescription)
} else {
guard let dataValue = data else {return}
print("res sendDeviceToken")
print(dataValue)
}
})
screenSplash.isHidden = true
loading.isHidden = true
webView.evaluateJavaScript("document.getElementsByTagName('meta')['viewport'].content='initial-scale=1.0, user-scalable=no';"){
(result,error) in if error != nil {
print(result ?? "")
}
}
if(flag==0){
NotificationCenter.default.addObserver(self, selector: #selector(internetChanged), name: Notification.Name.reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("No se pudo iniciar la notificacion")
}
flag=flag+1
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
if let url = navigationAction.request.url,
let host = url.host, !host.hasPrefix("app.com") || url.absoluteString.contains("/share/"),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
print(url)
print("Redirected to browser. No need to open it locally")
decisionHandler(.cancel)
} else {
// print("Open it locally")
decisionHandler(.allow)
}
} else {
// print("not a user click")
decisionHandler(.allow)
}
}
//fix the alert
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler()
}))
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(false)
}))
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(nil)
}))
self.present(alertController, animated: true, completion: nil)
}
//end fix the alert
func clickActionOpen(action: String) {
request = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)
var requestClick = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)
webView.load(requestClick)
}
}
服务人员
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
// resources to be cached again.
// https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/custom-offline-page/service-worker.js
var CACHE_VERSION = 'v4'
var CACHE_NAME = CACHE_VERSION + ':sw-cache-'
function onInstall(event) {
console.log('[Serviceworker]', 'Installing!', event)
event.waitUntil(
caches.open(CACHE_NAME).then(function prefill(cache) {
return cache.addAll([
'<%= asset_path "admin.js" %>',
'<%= asset_path "admin.css" %>',
'<%= asset_path "site/homepage-logo.png" %>'
])
})
)
}
function onActivate(event) {
console.log('[Serviceworker]', 'Activating!', event)
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames
.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
return cacheName.indexOf(CACHE_VERSION) !== 0
})
.map(function(cacheName) {
return caches.delete(cacheName)
})
)
})
)
}
// Borrowed from https://github.com/TalAter/UpUp
// then from https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onfetch
function onFetch(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
// console.log('Found response in cache:', response)
return response
}
// console.log('No response found in cache. About to fetch from network...')
return fetch(event.request).then(function(response) {
// console.log('Response from network is:', response)
return response
}).catch(function(error) {
// console.error('Fetching failed:', error)
throw error
})
})
)
}
self.addEventListener('install', onInstall)
self.addEventListener('activate', onActivate)
self.addEventListener('fetch', onFetch)
原因可能是
- ISP(互联网服务提供商)
- 对于 ruby-on-rails,身份验证并使用他们的 authentication_token 来验证用户对您的 API 的网络请求。否则,应用程序将终止。
希望对您有所帮助。
根据您所解释的情况,如果在意外情况下调用 internetChanged
方法,页面可能会重新加载。
如果在显示 viewController 时设备的网络状态发生变化,我可以看到这种情况会发生。您已在方法 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
中观察到通知。所以当网络状态改变时,internetChanged
方法将被调用。在方法的if
和else
中,都有加载webView的逻辑。
我还看到,您已经观察到通知但从未移除观察者。所以 ViewController 会有内存泄漏,可能不会从内存中释放。我建议您删除 deinit
另一件事是,您可以更好地观察 -viewDidLoad
中的通知,而不是 didFinish
方法。现在按照当前逻辑发生的事情是,每当重新加载 webView 时,一个新的观察者将被添加到方法 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
中的 ViewController。所以让我们以某种方式调用 internetChanged
方法 3 次,然后将添加 3 个观察者。因此,下次网络更改时,它会使 internetChanged
被调用三次,导致 webPage 依次加载 3 次,并继续循环。