I'm using a UIWebViewthat loads HTML from a database string using webView.loadHTMLString(self.htmlContent, baseURL: nil)
The htmlContent contains the following:
<ul class="anchorNavigation">
<li>
1. Inline Test Link
</li>
<li>
2. Inline Test Link
</li>
...
</ul>
... and later in the HTML:
...
...
However, whenever I click the inline link in the webView nothing happens.
What I've tried so far:
Changing the anchor tag to 'real' valid W3C HTML. E.g. <a id='parsys_47728'>Test</a>
Saving the HTML to a file in the temp directory and loading it using loadRequest(). E.g. let path = tempDirectory.URLByAppendingPathComponent("content.html") and webView.loadRequest(NSURLRequest(URL: path))
Intercepting the loadRequest method by implementing the func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool delegate. The request.URL says something strange like: "applewebdata://1D9D74C2-BBB4-422F-97A7-554BCCD0055A#parsys_47728"
I don't have any idea anymore how to achieve this. I know from previous projects that local HTML files in the bundle work with inline links. I just cannot figure out why this doesn't work.
Help much appreciated! Thank you!
If there's a fragment (e.g., #anchorName), then use JavaScript to scroll. Otherwise, assume it's a link without a fragment and use openURL.
// UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked ) {
// NSLog(#"request.URL %#",request.URL); // e.g., file:///.../myApp.app/#anchorName
NSString *anchorName = request.URL.fragment; // e,g, "anchorName"
if ( anchorName ) {
[webView stringByEvaluatingJavaScriptFromString:[NSString swf:#"window.location.hash='%#';",anchorName]];
return NO;
} else { // assume http://
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
}
return YES;
}
I'm still looking for a way to have the scroll position change smoothly (animated) rather than jumping.
Related
I recently made a simple macOS app that loads an html page form the bundle in a WebKit WebView (the new one, not legacy) but I noticed that when I type some keys in the page (not in an input box, I mean body keypress handled using javascript) an error sound is played. I was wondering if there is a solution for this problem.
I also might say that the html page works perfectly if I open it with Safari and there are no errors in the built-in inspector console as well as no error sound is played (which might tell us that the problem comes with something I'm missing in the application).
Here's my Swift code (App Delegate Obviously):
#IBOutlet weak var contentView: WebView!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let path = Bundle.main.path(forResource: "index", ofType: "html", inDirectory: "HTML_Content")
let url = URL(fileURLWithPath: path!)
let request = URLRequest(url: url)
contentView.mainFrame.load(request)
}
My HTML Code (Which works perfectly) [index.html]:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="js/jquery-1.12.0.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</head>
<body>
<h2 id="text"></h2>
</body>
</html>
My JavaScript / jQuery Code [js/main.js]:
$(document).keypress(function(e) {
var text = $('#text').text();
if (e.which == 8) {
if (text.length != 0) {
text = text.slice(0, -1);
$('#text').text(text);
}
} else if ('abcdefghijklmnopqrstuvwxyz1234567890 '.indexOf(
String.fromCharCode(e.which).toLowerCase()) != -1) {
$('#text').text(text + String.fromCharCode(e.which));
}
});
Can someone help me fix it?
I finally understood why the application was giving me the error sound:
The WebView needs to perform the key equivalent on its content.
It actually couldn't perform it by default but I forced it using a custom class with this override, excluding the shortcut keys (or I wouldn't be able to use them):
Swift 4:
import WebKit
class WebViewController: WebView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.modifierFlags.contains(.command) ||
event.modifierFlags.contains(.control) ||
event.modifierFlags.contains(.shift) ||
event.modifierFlags.contains(.option) {
return false
}
return true
}
}
Then I connected the custom class to the xib element using the element inspector's custom class property.
And, in the end, the sound was gone.
I have implemented the accepted solutions here and it does work for
some websites. For eg: Go to www.tomcruise.com and click on his trailers. Each of those links have target="_blank" and started opening after implementing the solution suggested in the previously linked stack overflow post.
But now I found that if we go to here and click on any link(the one I tried, as of writing this question, has a href tag as below
<a rel="nofollow" target="_blank" href="http://www.nowmagazine.co.uk/celebrity-news/victoria-and-david-beckham-fighting-to-be-together-296082" class="ot-anchor aaTEdf" jslog="10929; track:click" dir="ltr">http://www.nowmagazine.co.uk/celebrity-news/victoria-and-david-beckham-fighting-to-be-together-296082</a>
When I click on this link from inside WKWebView, the WKUIDelegate method below, does get called but has navigationAction.request = "" and hence nothing happens when webView.loadRequest("") gets called. Anyone else face this issue?
optional func webView(_ webView: WKWebView, createWebViewWithConfiguration configuration: WKWebViewConfiguration,
forNavigationAction navigationAction: WKNavigationAction,
windowFeatures windowFeatures: WKWindowFeatures) -> WKWebView?{
if navigationAction.targetFrame == nil {
webView.loadRequest(navigationAction.request)
}
return nil
}
What is special about the above specified href tag that is causing the WKUIDelegate method to be called with an empty url?
How do we fix this issue? Let me know how you root caused the issue as I am interested in debugging as well.
I was hoping that I could solve it using the WKWebView delegate methods, but I could not figure it out.
So I went to the UIWebView era's solution of running a javascript function upon completion of web Page loading
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
let jsCode = "var allLinks = document.getElementsByTagName('a');if (allLinks) { var i;for (i=0; i<allLinks.length; i++) {var link = allLinks[i];var target = link.getAttribute('target');if (target && target == '_blank') {link.setAttribute('target','_self');} } }"
webView.evaluateJavaScript(jsCode, completionHandler: nil)
}
This fixed the issue where tapping on the links in any google plus Posts page was resulting in an empty page being loaded
UPDATE on Nov 3rd 2015: The phenomenon explained in the question, no longer happens for me in Swift 2.0 code. So , you should be able to use the solution presented here for all your purposes
I have a UIWebview contains a html "select" tag, which is shown as a on the screen.
When I click the dropdown, the UIWebview brings up a UIWebSelectSinglePicker View automatically, which is shown as .
I want to change the picker view background color and text color. How can I achieve this goal?
I tried to listen on UIKeyboardWillShowNotification event, but at that moment, this view has not been created.
Thanks in advance for any helps.
I managed to resolve the issue myself.
If someone also want to change the UIPickView on the fly, please take a look:
First, add a listener on UIKeyboardWillShowNotification event.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_pickerViewWillBeShown:) name:UIKeyboardWillShowNotification object:nil];
Second, when notification fired, call change background color method after delay. <-- This is very important, if call method without delay, the pickview does not exist at that moment.
- (void)_pickerViewWillBeShown:(NSNotification*)aNotification {
[self performSelector:#selector(_resetPickerViewBackgroundAfterDelay) withObject:nil afterDelay:0];
}
Third, go through the UIApplication windows and find out pickerView. And you can change what ever you want for pickerView.
-(void)_resetPickerViewBackgroundAfterDelay
{
UIPickerView *pickerView = nil;
for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
for (UIView *uiView in [uiWindow subviews]) {
pickerView = [self _findPickerView:uiView];
}
}
if (pickerView){
[pickerView setBackgroundColor:UIColorFromRGB(0x00FF00)];
}
}
(UIPickerView *) _findPickerView:(UIView *)uiView {
if ([uiView isKindOfClass:[UIPickerView class]] ){
return (UIPickerView*) uiView;
}
if ([uiView subviews].count > 0) {
for (UIView *subview in [uiView subviews]){
UIPickerView* view = [self _findPickerView:subview];
if (view)
return view;
}
}
return nil;
}
Hope it will help.
I believe I've come up with an alternate solution to this problem. There are certain circumstances with the other solution proposed where the label colours appear incorrect (using the system default instead of the overridden colour). This happens while scrolling the list of items.
In order to prevent this from happening, we can make use of method swizzling to fix the label colours at their source (rather than patching them after they're already created).
The UIWebSelectSinglePicker is shown (as you've stated) which implements the UIPickerViewDelegate protocol. This protocol takes care of providing the NSAttributedString instances which are shown in the picker view via the - (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component method. By swizzling the implementation with our own, we can override what the labels look like.
To do this, I defined a category on UIPickerView:
#implementation UIPickerView (LabelColourOverride)
- (NSAttributedString *)overridePickerView:(UIPickerView *)pickerView
attributedTitleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
// Get the original title
NSMutableAttributedString* title =
(NSMutableAttributedString*)[self overridePickerView:pickerView
attributedTitleForRow:row
forComponent:component];
// Modify any attributes you like. The following changes the text colour.
[title setAttributes:#{NSForegroundColorAttributeName : [UIColor redColor]}
range:NSMakeRange(0, title.length)];
// You can also conveniently change the background of the picker as well.
// Multiple calls to set backgroundColor doesn't seem to slow the use of
// the picker, but you could just as easily do a check before setting the
// colour to see if it's needed.
pickerView.backgroundColor = [UIColor yellowColor];
return title;
}
#end
Then using method swizzling (see this answer for reference) we swap the implementations:
[Swizzle swizzleClass:NSClassFromString(#"UIWebSelectSinglePicker")
method:#selector(pickerView:attributedTitleForRow:forComponent:)
forClass:[UIPickerView class]
method:#selector(overridePickerView:attributedTitleForRow:forComponent:)];
This is the Swizzle implementation I developed based off the link above.
#implementation Swizzle
+ (void)swizzleClass:(Class)originalClass
method:(SEL)originalSelector
forClass:(Class)overrideClass
method:(SEL)overrideSelector
{
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method overrideMethod = class_getInstanceMethod(overrideClass, overrideSelector);
if (class_addMethod(originalClass,
originalSelector,
method_getImplementation(overrideMethod),
method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(originalClass,
overrideSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
#end
The result of this is that when a label is requested, our override function is called, which calls the original function, which conveniently happens to return us a mutable NSAttributedString that we can modify in anyway we want. We could completely replace the return value if we wanted to and just keep the text. Find the list of attributes you can change here.
This solution allows you to globally change all the Picker views in the app with a single call removing the need to register notifications for every view controller where this code is needed (or defining a base class to do the same).
I'm making an html editor component for an app (using UIWebView with contentEditable in iOS 5.0), and got stuck at how to handle UIWebView first responder status
[webView isFirstResponder], [webView becomeFirstResponder] and [webView resignFirstResponder] don't seem to work, and i've no idea how to make the webView become or resign it by code
If anyone knows how to work this out i would be very grateful, thanks in advance!
Here is how I overwrite these methods in a UIWebView subclass (content is the id of the editable element):
-(BOOL)resignFirstResponder {
[self setUserInteractionEnabled:NO];[self setUserInteractionEnabled:YES];
return [super resignFirstResponder];
}
// only works on iOS 6+
-(void)becomeFirstResponder {
self.keyboardDisplayRequiresUserAction = NO; // set here or during initialization
// important note: in some situations (newer iOS versions), it is also required to first call `blur()` on the 'content' element, otherwise the keyboard won't show up as expected
[self stringByEvaluatingJavaScriptFromString:#"document.getElementById('content').focus()"];
}
-(BOOL)isFirstResponder{
if ([[self stringByEvaluatingJavaScriptFromString:#"document.activeElement.id=='content'"] isEqualToString:#"true"]) {
return YES;
}
else {
return NO;
}
}
isFirstResponder will only return true after the keyboard is shown (e.g, it will return false at UIKeyboardWillShowNotification)
In case this is an issue, another way to check if the UIWebView is the first responder is as follows:
+(BOOL)isFirstResponder:(UIView *)v{
for (UIView *vs in v.subviews) {
if ([vs isFirstResponder] || [self isFirstResponder:vs]) {
return YES;
}
}
return NO;
}
-(BOOL)isFirstResponder{
return [[self class] isFirstResponder:self];
}
This way, the returned value will be YES even before/after the keyboard animation finishes (showing or hiding).
I met the same problem recently, but solved it using pure JavaScript. Actually it doesn't need any Objective-C First Responder related methods. I just used the JavaScript to change the UIWebView's content - the targeting HTML element's contentEditable attribute value according to the requirement.
For example, using the following code to hide the Keyboard that called by the UIWebView's editable content:
[webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('target').setAttribute('contentEditable','false')"];
Hope this is helpful. :)
Here is how I overwrite these methods in a UIWebView subclass (content is the id of the editable element):
[_webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('content').focus()"];
But Focus go to 1st point not last point
Call the following lines of code when you want to hide the keyboard.
//wView is your UIWebView
NSString *webText = [wView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
[wView loadHTMLString:webText baseURL:nil];
[webView loadHTMLString:[NSString stringWithFormat:#"%#", htmlString] baseURL:nil];
This works in iOS > 4
I'm writing on objective-C.
I have WebView and local file index.html has
<a href='http://www.google.com' name="666">
How can I get the name attribute?
Thanks!
It depends on when/by what you need to get the name. If you need the name when someone clicks on the link, you can set up some JavaScript that runs when the link is clicked (onclick handler). If you just have the html string, you can use regular expressions to parse the document and pull out all of the name attributes. A good regular expression library for Objective-C is RegexKit (or RegexKitLite on the same page).
The regular expression for parsing the name attribute out of a link would look something like this:
/<a[^>]+?name="?([^" >]*)"?>/i
EDIT: The javascript for getting a name out of a link when someone clicked it would look something like this:
function getNameAttribute(element) {
alert(element.name); //Or do something else with the name, `element.name` contains the value of the name attribute.
}
This would be called from the onclick handler, something like:
My Link
If you need to get the name back to your Objective-C code, you could write your onclick function to append the name attribute to the url in the form of a hashtag, and then trap the request and parse it in your UIWebView delegate's -webView:shouldStartLoadWithRequest:navigationType: method. That would go something like this:
function getNameAttribute(element) {
element.href += '#'+element.name;
}
//Then in your delegate's .m file
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSArray *urlParts = [[request URL] componentsSeparatedByString:#"#"];
NSString *url = [urlParts objectAtIndex:0];
NSString *name = [urlParts lastObject];
if([url isEqualToString:#"http://www.google.com/"]){
//Do something with `name`
}
return FALSE; //Or TRUE if you want to follow the link
}