How can I reliably detect a link click in UIWebView? - ios

I have a UIWebView and I need to do something when user taps a link. There’s a delegate callback that can be used to detect the taps:
- (BOOL) webView: (UIWebView*) webView
shouldStartLoadWithRequest: (NSURLRequest*) request
navigationType: (UIWebViewNavigationType) navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
…
}
}
The problem is that this code doesn’t handle all link clicks. As an example, a plain Google Search results page does something weird with the links:
<a href="http://example.com/" class="l" onmousedown="return rwt(…)">
<em>Link Text</em>
</a>
The rwt function results in the links not triggering the UIWebViewNavigationTypeLinkClicked event when tapped. Is there a way to reliably detect all events that fall into the “navigate to some other page” bucket?

So far I have arrived at the following solution. First, I inject some JS code into the page when loaded:
function reportBackToObjectiveC(string)
{
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "callback://" + string);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
links[i].addEventListener("click", function() {
reportBackToObjectiveC("link-clicked");
}, true);
}
When user taps a link, I know it in advance thanks to the webView:shouldStartLoadWithRequest: navigationType: delegate call:
if ([[[request URL] scheme] isEqualToString:#"callback"]) {
[self setNavigationLeavingCurrentPage:YES];
return NO;
}
Then if another request comes and _navigationLeavingCurrentPage is true, I know the user has clicked a link even though the navigation type flag is UIWebViewNavigationTypeOther. I still have to test the solution extensively, for I’m afraid that it will lead to some false positives.

I believe this can be done, by embedding in the HTML code of the website a custom JavaScript which will monitor the events, and based on which event you want to monitor, it could trigger a page redirect with a custom URL scheme, which you can intercept in the shouldStartLoadWithRequest.
Something like this:
<script>
// Function to capture events
function captureEvent(el) {
window.location.href="callback://"+el.href;
}
var elms = document.getElementsByTagName("a");
for (var i=0; i<elms.length; i++) {
elms[i].addEventListener("onmousedown", function(){captureEvent(el)}, true);
}
</script>
Then in the shouldStartLoadWithRequest, you can search for NSURLRequest's that have a callback:// url scheme, and do whatever you want.
This has not been tested, but something like this might get you in the right direction.
Also, since this was mentioned, yes you can add your custom script to any webpage, by using this:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[super webViewDidFinishLoad:webView];
[webView stringByEvaluatingJavaScriptFromString:#"document.body.insertAdjacentHTML('BeforeEnd','<script>....</script>');"];
}

You need to add this line
webView.dataDetectorTypes = UIDataDetectorTypeAll;
then
(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType
method will get call.

I had a bunch of problems getting this to work. Using a UITapGestureRecognizer works, except that it will always receive the taps, even for links. Unfortunately, links have about a 300 ms delay before they get recognized, which means that the gesture recognizer gets the taps before the link is recognized, which creates a timing problem (even worse because you shouldn't hard code a time to wait in case Apple changes it). Plus, waiting 300+ ms for the tap gives a poor user experience for my app.
So what I ended up doing was overlaying a transparent div on top of everything that gets the non-link taps. Then, put the links at a higher z-level. Finally, make sure everything uses ontouchend="javascript:window.location.href='...';". ontouchend will only act on a tap release, which is what users expect. Setting window.location.href loads a new page, ensuring that all taps call -webView:shouldStartLoadWithRequest:navigationType: and I can check the URL to see which I need to do.
The HTML looks like:
...
...
...
...
(I'm not totally sure if "position: relative" always places things in the same position as otherwise, but it worked nicely for my simple page; your mileage may vary. However, you will needs position:something in order to get z-index to work.)

Related

Meteor: Disable calling automatically on iOS when tapping on phone number

I am using Meteor Framework.
The following code
123-456-7890
or
(123) 456-7890
Initiates a call automatically in iOS.
I need some kind of warning.. such as pop up saying
Call 123-456-7890 ?
and if the user wants to call then they can press Call or else Cancel.
How do I fix this on iOS?
You have two options.
First, you could wrap the call link inside a pop up or modal. In your template:
<a class="open-popup">Call 123-456-7890</a>
Open the pop up via click event. In Meteor you can do this like this:
Template.myTemplateName.events({
"click a.open-popup": function(event) {
// Open pop up ...
});
In this pop up you place the link 123-456-7890 alongside with a cancle button, which is nothing else than a pop up closer. <a class="close-popup">cancle</a>
Second, use a click event with confirm() function and then do a url redirect to the `tel://´ address.
Template.myTemplateName.events({
"click a.open-popup": function(event) {
var r = confirm("Call 123-456-7890 ?");
if (r == true) {
let phoneNumber = "123-456-7890";
window.location.href="tel://"+phoneNumber;
} else {
// closes
}
});
I guess, the second option is easier to implement and already does, what you are looking for.

Open link in native browser from InAppBrowser

I'm building an app that uses the InAppBrowser quite a lot. At some point, the user is able to click an external link from within this window. I tried diffrent methods, but none seems to get a good working result.
The best solution so far is listening to the loadstart event (As described here):
app.browser.addEventListener('loadstart', function (inAppBrowser) {
if(inAppBrowser.url.match(/domain\.com/) === null) {
var url = inAppBrowser.url;
window.open(url, "_system");
}
}
This opens the link in a new window, but also in the original InAppBrowser. Is it possible to cancel this event? Or is there a other approach i can try?
I already tried the following approaches:
Cross window communication.
Inserting a history.back(-1) via the executeScript method.
Call the window.open(url, '_system'); from within the InAppBrowser.
This is for iOS specific.
EDIT:
I ended up by adding this code in platforms/ios/APPNAME/Plugins/org.apache.cordova.inappbrowser/CDVInAppBrowser.m:
NSString *domainStr = [NSString stringWithFormat:#"domain.com"];
NSString *urlStr = [NSString stringWithFormat:#"%#", request.URL];
NSRange result = [urlStr rangeOfString:domainStr];
if(result.location == NSNotFound) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
return NO;
}
above this code:
return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
You have some options:
hide the external links injecting css to inappbrowser, if they don't appear, then they can't be clicked
add a loadstop listener and then hide the links
app.browser.addEventListener('loadstop', hideLinks);
function hideLinks(){
app.browser.insertCSS({
code: "a { display: none; }"
}, function() {
console.log("Styles Altered");
});
}
modify/subclass inappbrowser, changing the shouldStartLoadWithRequest method
change the return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; to return NO if the URL isn't yours

Back button handler in jQuery Mobile (Android) PhoneGap

Is there any way to handle back button (device backbutton) as default functionality to move back page? I need to implement the same functionality on back button goes to previous page. If there is no previous page (first page) it exit the application. Is this possible in PhoneGap?
I also need to pop page before going to push another page is this posible in jQuery?
Checking window.location.length would be the easiest way to determine if you're on the first page, but this isn't available in Phonegap.
But since you're using JQM, you can either use the navigate event as Omar suggests or could manually count the number of pages shown and the number of pages gone back (same thing) and use this to determine if the first page is being shown and whether to exit the app. Something like this would work:
var pageHistoryCount = 0;
var goingBack = false;
$(document).bind("pageshow", function(e, data) {
if (goingBack) {
goingBack = false;
} else {
pageHistoryCount++;
console.log("Showing page #"+pageHistoryCount);
}
});
function exitApp() {
console.log("Exiting app");
navigator.app.exitApp();
}
function onPressBack(e) {
e.preventDefault();
if(pageHistoryCount > 0) pageHistoryCount--;
if (pageHistoryCount == 0) {
navigator.notification.confirm("Are you sure you want to quit?", function(result){
if(result == 2){
exitApp();
}else{
pageHistoryCount++;
}
}, 'Quit My App', 'Cancel,Ok');
} else {
goingBack = true;
console.log("Going back to page #"+pageHistoryCount);
window.history.back();
}
}
function deviceready() {
$(document).bind('backbutton', onPressBack);
}
$(document).bind('deviceready', deviceready);
As for the second part of your question:
Secondly i need to pop page before going to push another page is this
posible in jquery ?
It's not clear what you're asking here. Do you mean you want to show some kind of popup content like a dialog between every page change? Please clarify then I can help you :-)

webVIew.request.URL in didFailLoadWithError: is the previous URL not the failed URL

Suppose there is a webpage A.html that contains a link to B.html.
If B.html is clicked then request.URL in shouldStartLoadWithRequest: will be B.html as it should be. However if there is a problem loading that page (suppose it doesn't exist for example) then in didFailLoadWithError: the value of webView.request.URL is not B.html but A.html.
Therefore it seems its not possible to know which page load failed unless I cache the last page load, but I would have expected webView.request.URL to be B.html, therefore is this a defect?
I didn't see documentation on what it should be.
[iOS 6]
I had the same problem. If anyone else does too, error.userInfo dictionary works instead.
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.domain == NSURLErrorDomain) {
if (error.code == NSURLErrorCancelled) { //ignore this one, interrupted load
return;
}
}
}
//NSString *theURLString = [webView.request.URL absoluteString]; this won't work - it just returns the last successful url
NSString *theURLString = [error.userInfo objectForKey:#"NSErrorFailingURLStringKey"]; //this works
The doco says NSErrorFailingURLStringKey is deprecated in iOS4 (only provided for backwards compatibility) and you should use NSURLErrorFailingURLStringErrorKey instead.
However NSURLErrorFailingURLStringErrorKey isn't returned (not by my version of UIWebView anyway). Instead, NSErrorFailingURLKey is another key returning the URL, but I can't find that in the documentation anywhere.
I faced the same issue with Swift 3.1. To get the failed url use this delegate method :
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool{
let failedUrl = (request.url?.absoluteString)! as String
return true
}
You're right that you would need to cache the last request sent (the shouldStartLoadWithRequest: delegate method would be a great place to do so), as the request property of a UIWebView appears to always return the last successful request (although the documentation doesn't specifically specify this, so I wouldn't call it a defect).

Mootools The Wall + Slimbox

I am using wall.plasm.it and would like to use slimbox to open the images in fullscreen http://www.digitalia.be/software/slimbox
But when I add the slimbox script to my website and click on an image, it just opens the image and redirects me to the image on the server but doesnt start slimbox
Ah, so it seems you would have to do something like the following:
Use wall.setCallOnUpdate( myFunctionUpdate ); (this) to update all newly generated images so that they have the rel=lightbox attribute set.
Reinitialize slimbox.
--OR--
You could simply use the CallOnUpdate to directly enable the slimbox on items using the API
I think you need to use Event Delegation - this is how I got my wall working with lightbox. I called this after the initWall() method and also the changeOnUpdate() so the new events would be added.
var boundClicker;
$$('div.tile a').each(function(button) {
var linkDest = button.get('href');
var title = button.get('data-title');
var type = button.get('rel');
var clicker = function(event)
{
event.stop();
Mediabox.open(linkDest, title, '470 290');
};
boundClicker = clicker.bind(button);
button.addEvent('click', boundClicker);
});
Add a "click" event handler and use the getMovement function.
items.each(function(item) {
item.addEvent("click",function(e){
if( wall.getMovement() ){
e.stop();
}
else {
Mediabox.open(...);
}
}); }

Resources