Showing posts with label HTML. Show all posts
Showing posts with label HTML. Show all posts

Monday, June 4, 2018

How to convert HTML to NSAttributedString?

Well, simply don't. Use WKWebView instead.
This will allow you to insert pictures, reuse CSS-styles, execute scripts etc.

Wait, why not to use NSAttributedString?

While Apple provides several methods to initialize an instance of NSAttributedString from an HTML data, like: init(data:options:documentAttributes:), they are all extremely slow, about 1000 times slower than WKWebView which is as fast as Safari itself. Additionally, NSAttributedString supports a limited set of HTML attributes.

You may find many third-party solutions on GitHub (DTCoreText and many others). They can be faster, then the built-in converter, but still slower comparing to WebKit. I tried some, and they all were buggy and most are poorly supported after iOS7 release brought those "initWithHTML" methods.

The only advantage of NSAttributedString is the ability to put it in UI elements like UILabel or UIButton.

WKWebView example

Here follows super-advanced example demonstrating widest possible abilities of displaying rich and interactive text with HTML, CSS, and JavaScript. To make things convenient and reusable the HTML document is split into parts:

Tutorial_main.html

The main HTML that contains necessary elements that supposed to be involved in every page of the tutorial. Including:
  • A <head> block containing:
    • A raw CSS <style> section, containing easy-configurable constants.
    • An external CSS "stylesheet".
    • The extremely important "viewport" <meta> tag, required to scale content of a WKWebView correctly.
    • A <script> tag that provides Tutorial.js script. Not important enough to be listed here.
  • A customisable <body> section.
<html> <head> <style type="text/css"> :root { --softTextColor: %@; --darkTextColor: %@; --actionColor: %@; --backgroundColor: %@; --bodyFontSize: %@; --titleFontSize: %@; } </style> <link rel="stylesheet" type="text/css" href="Tutorial.css" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="Tutorial.js"></script> </head> <body> %@ </body> </html> The %@ format specifiers will be replaced later with strings. Tutorial.css and Tutorial.js are contained in the Main Bundle root folder, so you don't need to specify baseURL to the WKWebView. Yes, you can refer local files, even use anchor links.

Tutorial.css

body { color: var(--softTextColor); font-family: "-apple-system"; font-size: var(--bodyFontSize); margin-top: 0px; text-align: left; -webkit-user-select: none; -webkit-touch-callout: none; background-color: var(--backgroundColor); } h1 { text-align: center; font-size: var(--titleFontSize); font-weight: 300; color: var(--darkTextColor); margin-top: 0.51em; margin-bottom: 0; margin-left: 0; margin-right: 0; } a { color: var(--actionColor); text-decoration: none; } It's handy to keep CSS in a separate file. You can see some constants which already were listed before.

step2_body.html

<h1>Сonnection</h1> <p>Connect your device to your computer using the USB cable.</p> <div class="spoiler"> <p>You can sync the device with iTunes using Wi-Fi. In this case you will need the USB cable only once: to activate wireless syncing. <a href="https://support.apple.com/en-us/HT203075">Read more.</a></p> </div> <div class="centered"> <a class="showMoreButton" href="showmore://"> <img src="show_more.svg" width=20 height=15 />show more </a> <a class="showLessButton" href="showless://"> <img src="show_less.svg" width=20 height=15 />show less </a> </div> The body of the second step. Look at two buttons, there is something interesting about them. First, they use .svg images, because WKWebView supports vector graphics. (Images are also placed in the root directory of the Main bundle). Also, buttons use custom-scheme anchors, e.g.: href="showmore://". These anchors allows to intercept button clicks on the Swift-side using the webView(,decidePolicyFor:,decisionHandler:) method of WKNavigationDelegate.

Assemble

Time to put all the things together. func assembleHTMLString(bodyFileName: String) -> String? { // All resources are contained in the Main Bundle let bundle = Bundle.main guard let mainHTMLURL = bundle.url(forResource: "Tutorial_main", withExtension: "html") else { print("No main HTML!") return nil } // The contents of Tutorial_main is not just a String, it's a format (see below) guard let mainHTMLFormat = try? String(contentsOf: mainHTMLURL, encoding: .utf8) else { print("Failed to parse \(mainHTMLURL.lastPathComponent)!") return nil } guard let bodyURL = bundle.url(forResource: bodyFileName, withExtension: "html") else { print("No body with name \(bodyFileName)!") return nil } guard let htmlBody = try? String(contentsOf: bodyURL, encoding: .utf8) else { debugLog("Failed to parse \(bodyFileName)!") return nil } // Prepare some necessary arguments... let arguments = [ // Following values will be used as CSS constants UIColor.lightGray.hexString, // softTextColor UIColor.gray.hexString, // darkTextColor UIColor.green.hexString, // actionColor UIColor.white.hexString, // backgroundColor "15px", // bodyFontSize "22px", // titleFontSize htmlBody // the body, yes ] // And here we ASSEMBLE! Finally. let assembledHTMLString = withVaList(arguments) { // The `arguments` substitute %@ in the `format`, one by one. (NSString(format: mainHTMLFormat, arguments: $0)) as String } return assembledHTMLString } Wondering where is hexString property of UIColor coming from? – Read about the Power of Extensions.

Configure WKWebView

You can configure it on your own. But if you don't know how to disable zooming, here is some help: webView.scrollView.delegate = self extension TutorialViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return nil // Just return nil here } } When you have the assembled HTML string, there is not much left to do: webView.loadHTMLString(assembledHTMLString, baseURL: Bundle.main.bundleURL)
This all may seem overcomplicated to you, but once you implement the logic, you will have a very useful tool.

Saturday, May 26, 2018

How do I highlight code in the blog

May be you are a developer who has plenty of ideas to share, and you want to start your own blog? In this case you need a code highlighting first!

How do I highlight code here? Well, first of all I import Google code-prettify script, basically every post in this blog starts with the line: <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
To add some Swift code, like this: let reality = true, I use the code tag: <code class="prettyprint lang-swift">let reality = true</code> Here prettyprint feeds our code block to the prettifier and lang-swift is a language hint. In this Stackoverflow answer you can find a list of possible hints.

I apply custom CSS to the code block. In Blogger you can do it following the next path: Theme > Customize > Advanced > Add CSS. code { white-space: pre-wrap; // Required to keep formatting, still can wrap long lines font-size: 15px; line-height: 100%; background-color: #f8f7f1; padding: 4px 2px 4px 1px; } Additionally, to create nice code blocks like above, I use the block class: code.block { display: block; padding: 0.4em 0.15em 0.4em 0.15em; margin: 0.4em 0 0.4em 0; }

How to Record Calls on iPhone