Swift Retrieve HTML data from Webview - html

I am trying to obtain the data within the header of a web page that is being displayed in a UIWebView.
How do I get the raw (unformatted) HTML string from the UIWebView?
Also, I'm using iOS 9.
My question is similar to Reading HTML content from a UIWebView , but this post is from 6 years ago.

From the top answer on the question you linked:
NSString *html = [yourWebView stringByEvaluatingJavaScriptFromString:
#"document.body.innerHTML"];
would translate into Swift:
let html = yourWebView.stringByEvaluatingJavaScriptFromString("document.body.innerHTML")
stringByEvaluatingJavaScriptFromString returns a optional, so you'd probably want to later use an if let statement:
if let page = html {
// Do stuff with the now unwrapped "page" string
}

swift 4:
if let html = self.webView.stringByEvaluatingJavaScript(from: "document.body.innerHTML"){
}

Don't forgot get the html when the page render finished, if you got html too early, the result will be empty.
func webViewDidFinishLoad(webView: UIWebView) {
print("pageDidFinished")
if let html = webView.stringByEvaluatingJavaScriptFromString("document.documentElement.outerHTML") {
print("html=[\(html)]")
}
}

Related

Can you display a html <img> tag inside a UILabel in Swift?

Can you show a html <img src=''> inside a NSAttributedString used in an UILabel?
Yes you can, even though Apple doesn't recommended it:
The HTML importer should not be called from a background thread (that is, the options dictionary includes documentType with a value of html). It will try to synchronize with the main thread, fail, and time out. Calling it from the main thread works (but can still time out if the HTML contains references to external resources, which should be avoided at all costs). The HTML import mechanism is meant for implementing something like markdown (that is, text styles, colors, and so on), not for general HTML import.
An UILabel's attributedText can be used to render html img tags.
Here an example:
let str = "<img src='https://www.codeterritory.com/assets/screenshot_sidiusnova_04-f5422e9916fb114e73484758278c284e.jpg'>"
let data = str.data(using: String.Encoding.unicode)!
do {
let attrStr = try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html], documentAttributes: nil)
let label = UILabel(frame: UIScreen.main.bounds)
label.attributedText = attrStr
UIApplication.shared.windows.first!.addSubview(label)
} catch let error {
print(error)
}

swift: The HTML data converted to NSAttributed string doesn't make the links clickable in label

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).

How to convert UITextView's Attributed Text to simple HTML code? Swift

I have tried everything... Many articles and stackoverflow posts but I just can't seem to get it right. Scenario: I get back html code from a web service, very simple example is:
"<b>TEST</b>"
I convert this string to attributedString like this:
extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return NSAttributedString() }
do {
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
} catch {
return NSAttributedString()
}
}
}
And then I display it like this:
self.textView.attributedText = htmlTextFromWebService.htmlToAttributedString
Perfect, my UITextView displays "TEST" as bold.
Now the problem: I am trying to send it back but the bold is gone.
Here's how I'm doing that:
let attrString = NSAttributedString(string: self.textView.text)
var resultHtmlText = ""
do {
let r = NSRange(location: 0, length: attrString.length)
let att = [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType]
let data = try attrString.data(from: r, documentAttributes: att)
if let h = String(data: data, encoding: .utf8) {
resultHtmlText = h
}
} catch {
print("FAILED TO CONVERT TO HTML")
}
resultHtmlText is now:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px}
span.s1 {font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt}
</style>
</head>
<body>
<p class="p1"><span class="s1">TEST</span></p>
</body>
</html>
But the web service cannot read this and plus, the bold tag is gone!
So ultimately, how can I get resultHtmlText to be this:
"<b>TEST</b>"
I just want simple html tags as my result to send to the web service!
Thanks.
Maddy's answer identifies the narrow technical issue that your code was pulling the plain String from the text property of UITextView rather than the full NSAttributedString available from the attributedText property. That answer addresses the issue of your text losing the bold styling. If you correct your code as indicated, the bold styling will be preserved, but not in the manner that you desire. More specifically, the bold styling will be included in the style sheet portion of the header of the HTML document--it will not appear as an inline <b> tag.
However, your question is broader. If I may, the essence of your question is focussed on how to make a proper round trip with the HTML code? Without knowing more about your use case, I'm not sure I can fully answer that question. For what they are worth, I offer the following comments.
DISPLAYING HTML
If you have HTML code to display, the best way to do so in iOS is using a WKWebView, not a UITextView. There are LOTS of web view examples on StackOverflow and elsewhere, such as this tutorial.
For your use case, you'll want to use the loadHTMLString(:baseURL:) to insert the HTML code into the web view. A very stripped down example using the variable name from your code, as follows:
// ... embedded somewhere in a UIViewController
let webView = WKWebView()
self.view = webView
webView.loadHTMLString(htmlTextFromWebService, baseURL: nil)
EDITING HTML
If you also need to allow the user to edit the text, then you have a MUCH BIGGER TASK ahead of you. In that case, I suggest exploring GitHub for examples of attributed text editors built on WKWebView, like RichEditorView.
SENDING HTML CODE TO YOUR WEB SERVICE
Your example uses the NSAttributedString method data(from:documentAttributes:) to create an HTML code representation of an attributed string. That method creates a fully-formed HTML document, with a full header, and uses a style sheet in the header to provide the styling.
You state the the web service is unable to handle this text. I suspect your web service takes merely raw HTML code without the header, style sheet, etc. If so, the data(from:documentAttributes:) method isn't going to work for this purpose.
One solution is to avoid the round-trip conversion issues by working with the original HTML code at all times via WKWebView. See, above.
Another solution is to write your own, simple NSAttributedString-to-HTML parser. It seems like you might be using very simple text and styling. If so, the implementation of this sort of parser should be straightforward. If you need an example of that sort of parser, leave a comment, and I will provide some sample code.
Change
let attrString = NSAttributedString(string: self.textView.text)
To
let attrString = self.textView.attributedText
This will give back the attributes but it will still give a full HTML result.

Objective C Using NSScanner to obtain <body> from html

I am trying to create an iOS app simply to extract the section of a web page.
I have the code working to connect to the URL and store the HTML in an NSString
I have tried this, but I am just getting null strings for my result
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
// Create a new scanner and give it the html data to parse.
while (![newScanner isAtEnd])
{
[newScanner scanUpToString:#"<body>" intoString:NULL];
// Scam until <body> tag is found
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
// Everything up to the end tag will get placed into the memory address of the result string
}
I have tried an alternative way...
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
// Create a new scanner and give it the html data to parse.
while (![newScanner isAtEnd])
{
[newScanner scanUpToString:#"<body" intoString:NULL];
// Scam until <body> tag is found
[newScanner scanUpToString:#">" intoString:NULL];
// Go to end of opening <body> tag
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
// Everything up to the end tag will get placed into the memory address of the result string
}
This second way returns a string which starts with >< script... etc
If Im honest I don't have a good URL to test this with and I think It may be easier with some help on removing the tags within the body too (like <p></p>)
Any help would be very much appriciated
I don't know why your first method didn't work. I assume you defined bodyText before that snippet. This code worked fine for me,
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlData = #"This is some stuff before <body> this is the body </body> with some more stuff";
NSScanner* newScanner = [NSScanner scannerWithString:htmlData];
NSString *bodyText;
while (![newScanner isAtEnd]) {
[newScanner scanUpToString:#"<body>" intoString:NULL];
[newScanner scanString:#"<body>" intoString:NULL];
[newScanner scanUpToString:#"</body>" intoString:&bodyText];
}
NSLog(#"%#",bodyText); // 2015-01-28 15:58:00.360 ScanningOfHTMLProblem[1373:661934] this is the body
}
Notice that I added a call to scanString:intoString: to get past the first "<body>".

Populate a hyperlink attributed string for UIActivityViewController activities

There are lots of answers on SO that show devs how to make a string from HTML content or place a URL in a string, but my question is how to make an HTML string.
I'm trying to create a string that will return in HTML format or at least not show the URL.
So for example web devs would do this to hide the URL:
Visit Us at Google.com!
I can easily translate that to a string by doing so:
NSString *urlLink = #"www.google.com";
NSString *string = [NSString stringWithFormat:#"Visit Us at %#", urlLink];
But that doesn't replace the link with a hyperlink word of my choosing.
I'm aware that the device dictates if its a hyperlink depending on how you display it. i.e., text fields, text views, or you can force open it etc.
What i'm trying to do is:
#define APPSTORELINK #"www.appstorelink.com"
#implementation Config
+(NSString *)appstorelink {
return APPSTORELINK;
}
+(NSString *)mmsmetadata {
NSString *string = [NSString stringWithFormat:#"I shared this publication with the [Name of my iPhone App] iPhone App", APPSTORELINK];
return string;
}
So I can easily call it here or app wide:
NSArray *shareItems;
UIImage *snapshot = [self imageFromView:self.view];
shareItems = #[[Config mmsmetadata], snapshot];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
activityController.excludedActivityTypes = #[UIActivityTypePostToFlickr, UIActivityTypeAssignToContact, UIActivityTypeMail, UIActivityTypePostToVimeo];
[activityController setCompletionWithItemsHandler:(UIActivityViewControllerCompletionWithItemsHandler)^(NSString *string, BOOL completed) {
So in short, how can I make the string HTML format out of the box? My main concern is I want to hide the URL and replace it with an HTML tag, or otherwise if you have a better solution. Can't find anything on SO.
Any thoughts? I'm probably overthinking this. I'm sure theres an easier way
EDIT
Before even posting I have been aware of NSAttributedString and that was the first thing I attempted. However, the issue isn't setting an attribute, thats the easy part, the part that is defining my question is how to set it so it will DISPLAY as attributed when using activities in the UIActivityViewController
Here is how I set it, but the outcome was the same as above so I figured it would be easier to use an HTML tag:
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"I shared this publication with the Army Pubs iPhone App!"];
NSRange selectedRange = NSMakeRange(0, [string length]);
NSURL *linkURL = [NSURL URLWithString:APPSTORELINK];
[string beginEditing];
[string addAttribute:NSLinkAttributeName
value:linkURL
range:selectedRange];
[string addAttribute:NSForegroundColorAttributeName
value:[UIColor blueColor]
range:selectedRange];
[string addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
range:selectedRange];
[string endEditing];
return string;
However, it still displays as plain text in the Message or Mail composers. So think MFMailComposeViewControllerDelegate how there is a setting for isHTML. If it's set to yes it strips all the HTML tags and displays the text as a hyperlink. For example:
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:YES];
I want to emulate that when the activities are called from within a UIActivityViewController
This is the output currently even if I do it with the attributed string I tried first it just displays as plain text by stripping the HTML tag but doesn't make it a link
See link option in attributed strings.
The link attribute specifies an arbitrary object that is passed to the NSTextView method
clickedOnLink:atIndex: when the user clicks in the text range
associated with the NSLinkAttributeName attribute. The text view’s
delegate object can implement textView:clickedOnLink:atIndex: or
textView:clickedOnLink: to process the link object. Otherwise, the
default implementation checks whether the link object is an NSURL
object and, if so, opens it in the URL’s default application.