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
Related
I have a text in HTML format. I am using the property of NSAttributed string to parse it. It pareses the text nicely and displays on the label. However, the parsing of the anchor tag doesn't make the link clickable. This is the following code that I am using for parsing.
extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return NSAttributedString() }
do {
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return NSAttributedString()
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
When I run the app and give the value to the label as:
text = "<p>This is Google Home</p>"
simpleTextLabel.attributedText = text.htmlToAttributedString
The output on the iOS App looks like following but nothing happens on clicking :
This is Google Home.
How can I make it open in safari?
From your line:
simpleTextLabel.attributedText = text.htmlToAttributedString
We can assume that simpleTextLabel is a UILabel.
It's basic behavior from a UILabel to not be "interactable". You can add a tap gesture on it, but it transform it as a UIButton.
There are some tricks to make it possible with a UILabel, find where exactly it has been tapped, check if there is a link, etc.
Looking for "UILabel Clickable": Create tap-able "links" in the NSAttributedString of a UILabel? etc. There are even a few third party libs.
I (in my humble opinion) consider it as a "hack".
There is a good WWDC 2018 Session: TextKit Best Practices. At 2:34, it explains that if you need to interact with a shown text, prefers UITextView over UILabel.
There is a UITextViewDelegate method just for that: textView(_:shouldInteractWith:in:interaction:)
Note that there a small differences in the rendering of a UITextView and a UILabel. If you superpose them, they won't have the same "start point", the layout is a little different. However, with small changes, it can be the same (for instance: How can I make a UITextView layout text the same as a UILabel?).
Note also that according to the small modifications of a UITextView into a UILabel visual rendering, the user won't notice the difference (and that's in fact what matters, beside that using native methods of the UITextView/UITextViewDelegate make it easily understandable afterwards by another developer, or in a few months if you need to do a small modification).
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’m having trouble swiping right to move focus from a UITableView to a UIButton that is to the right and below the tableview. I’ve set up a UIFocusGuide and I believe I’ve got the geometry correct. (See attached screenshot, created using Pod VisualFocusGuide.)
When I use the built-in QuickLook on the UIFocusUpdateContext, it shows only the table view focus (the highlighted row in one color, and the other rows in another color), but that might be because I can only ever get focus in the table view, so perhaps that context is limited to the table.
I’ve seen a recommendation elsewhere to associate the focus guide with the button instead of the controller’s view, but that didn’t work. Out of desperation, I also tried associating the focus guide with the tableview, but still no luck.
Here are the pertinent methods. Any light anyone can shed will be appreciated! Thanks!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addLayoutGuide(focusGuide)
self.focusGuide.widthAnchor.constraint(equalTo: self.infoButton.widthAnchor).isActive = true
self.focusGuide.heightAnchor.constraint(equalTo: self.tableView.heightAnchor).isActive = true
self.focusGuide.topAnchor.constraint(equalTo: self.tableView.topAnchor).isActive = true
self.focusGuide.leftAnchor.constraint(equalTo: self.infoButton.leftAnchor).isActive = true
self.focusGuide.preferredFocusEnvironments = [self.infoButton]
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocus(in: context, with: coordinator)
guard let nextFocusedView = context.nextFocusedView else { return }
// When the focus engine focuses on the focus guide, we can programmatically tell it which element should be focused next.
switch nextFocusedView {
case self.tableView:
self.focusGuide.preferredFocusEnvironments = [self.infoButton]
case self.infoButton:
self.focusGuide.preferredFocusEnvironments = [self.tableView]
default:
self.focusGuide.preferredFocusEnvironments = []
}
}
I've just gotten this to work. I added another focus guide below the table view, to the left of the button. I sized it to be the width of the tableview and the height of the button. I made its preferredFocusEnvironment the tableView. But that in itself wasn't enough. I then removed the didUpdateFocus method, and left and right swiping started working! Here's the resulting code:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addLayoutGuide(focusGuide1)
self.focusGuide1.topAnchor.constraint(equalTo: self.tableView.topAnchor).isActive = true
self.focusGuide1.leftAnchor.constraint(equalTo: self.infoButton.leftAnchor).isActive = true
self.focusGuide1.heightAnchor.constraint(equalTo: self.tableView.heightAnchor).isActive = true
self.focusGuide1.widthAnchor.constraint(equalTo: self.infoButton.widthAnchor).isActive = true
self.focusGuide1.preferredFocusEnvironments = [self.infoButton]
self.view.addLayoutGuide(focusGuide2)
self.focusGuide2.topAnchor.constraint(equalTo: self.infoButton.topAnchor).isActive = true
self.focusGuide2.leftAnchor.constraint(equalTo: self.tableView.leftAnchor).isActive = true
self.focusGuide2.heightAnchor.constraint(equalTo: self.infoButton.heightAnchor).isActive = true
self.focusGuide2.widthAnchor.constraint(equalTo: self.tableView.widthAnchor).isActive = true
self.focusGuide2.preferredFocusEnvironments = [self.tableView]
}
I am new to swift i am using storyboard and have used navigationcontrollers to connect from one viewcontroller to another. I want to send the name of the image clicked to the next viewcontroller which is connected modally in storyboard from the imageView. I searched lot about transferring data from oneviewcontroller to another viewcontroller connected with navigationcontroller modally but no solution was available. Please let me know if any of the code is required as i dont know how to go ahead with it. I know this might be silliest question but posting this after searching a lot on net.
EDIT according to #uraimo reply.
Do i need to provide name to every segue i created on storyboard?.
I have 2 fixed images on viewcontrollerA and i have placed a uibutton with transparent background and no text on each of them and then ctrl drag to navigation controller of viewcontrollerB for presenting modally and unwinding the backbutton i.e. UIBarButtonItem to viewcontrollerA by ctrl drag the back button of viewcontrollerB to exit of the viewcontrollerB and unwinding it.
This is how i have created navigation from any of the image click out of 3 images of viewcontrollerA to viewcontrollerB and back to viewcontrollerA on back button click of viewcontrollerB.
Please let me know if i am doing anything wrong and will your prepareForSegue code be useful in accomplishing my task.
Basically, both using IB or when you do it programmatically, you have to configure your new viewcontroller with all the data it needs before the segue is performed (or the controller is presented via code).
In your case, just set the image name (your custom view controller class YourViewController should have a specific String property to hold this value) overriding prepareForSegue in the current view controller class:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yourModalSegueIdentifier" {
let imgName= (sender as! UIImageView)
let destination = segue.destinationViewController as UINavigationController
let yourController = destination.topViewController as YourViewController
yourController.imageName= <name here>
}
}
This solves the passing data question.
But in your case, you need the name of the clicked image, and that can be only obtained adding a click event through a UIGestureRecognizer to the UIImageView.
So, you'll need a uigesturerecognized that on click will perform the segue you've created. And also, you will not be able to get the name of the image asset (the one you use the creating an UIImage using imageNamed:) because it's not available anymore, and yo'll have to use the accessibilityIdentifier.
This makes everything way more complicated, it seems it could be done for the most part graphically here and here(but it's not really clear how to do it), but it's usually done via code.
Simplifying it a bit using a global variable:
var currentImage = ""
func viewDidLoad(){
...
...
let aTap = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
aTap.numberOfTapsRequired = 1
//For every image, configure it with recognizer and accessibilityId:
firstImage.userInteractionEnabled = true
firstImage.addGestureRecognizer(aTap)
firstImage.accessibilityIdentifier = "firsImage"
...
}
func imageTapped(recognizer:UITapGestureRecognizer) {
let imageView = recognizer.view as! UIImageView
currentImage = imageView.accessibilityIdentifier
self.performSegueWithIdentifier("yourModalSegueIdentifier", sender: self)
}
And change this:
yourController.imageName= <name here>
to this:
yourController.imageName= currentImage
Update:
Do i need to provide name to every segue i created on storyboard?
Yes, it's the only way to identify them, every UIStoryboardSegue has an identifier. But remember, segues are not the only way to go from a controller to another, if you do it completely programmatically (no segues) you usually call "presentViewController". Segues are a storyboard concept.
Again, regarding the segue name/identifier, you didn't need it until now because you never referenced that segue from your code, you need it for both prepareForSegue and performSegueWithIdentifier. Just select the segue and give it a name on the right inspector pane.
The structure you describe seems ok, the only thing it's that i'm not so sure that the UIButtons are really needed, try with a modal segue from the imageview or directly from the viewcontroller to the destination view controller.
Update 2:
If you are starting and need a free course that will teach you the basics and also make you build a few interesting ios apps i recommend hackingwithswift.
check out how I did this
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "AddItem":
let destination = segue.destination as? UINavigationController
guard let itemViewController = destination?.topViewController as? ItemViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
itemViewController.collection = collection
case "EditCollection":
guard let collectionViewController = segue.destination as? EditCollectionViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
collectionViewController.collection = collection
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}
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.