Day 34 - 100 Days of Swift
Project 7 (part 2)
Day 34 is the second part of the seventh project. You make a DetailViewController
that renders some HTML to display the information from the petition, you add the second tab which displays the popular petitions, and you add a simple error message to be displayed in the case the the petitions can’t be loaded.
The first thing you do is make a DetailViewController
file and give it a WKWebView
as its main view:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit
import WebKit
class DetailViewController: UIViewController {
var webView: WKWebView!
var detailItem: Petition?
override func loadView() {
webView = WKWebView()
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Next, you build the HTML to display and display it. I pulled this out into a setupViews()
method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
guard let detailItem = detailItem else { return }
let html = """
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style> body { font-size: 150%; } </style>
</head>
<body>
<h3>\(detailItem.title)</h3>
<p>\(detailItem.body)</p>
<p>Signatures: \(detailItem.signatureCount)</p>
</body>
</html>
"""
webView.loadHTMLString(html, baseURL: nil)
And then called it from viewDidLoad
:
1
2
3
4
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
Then, all you have to do is instantiate a new DetailViewController
in tableView(_:didSelectRowAt:)
, give it the right petition, and present it:
1
2
3
4
5
6
7
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let detailVC = DetailViewController()
detailVC.detailItem = petitions[indexPath.row]
navigationController?.pushViewController(detailVC,
animated: true)
}
To add the second tab you instantiate a new view controller in application(_:didFinishLaunchingWithOptions:)
, set its UITabBarItem
, and add it to the main UITabBarController
’s array of view controllers:
1
2
3
4
5
6
if let tabBarController = window?.rootViewController as? UITabBarController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "NavController")
vc.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
tabBarController.viewControllers?.append(vc)
}
With that, you have two tabs, but they both load the same list of petitions. To get them to load the right lists you make a slight tweak to loadPetitions
in ViewController
:
1
2
3
4
5
6
7
let urlString: String
if navigationController?.tabBarItem.tag == 0 {
urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
} else {
urlString = "https://api.whitehouse.gov/v1/petitions.json?signatureCountFloor=100000&limit=100"
}
Finally, to display the error message, you add a helper function called showError()
:
1
2
3
4
5
6
7
let alertController = UIAlertController(title: "Loading error",
message: "There was a problem loading the feed. Please check your connection and try again.",
preferredStyle: .alert)
let action = UIAlertAction(title: "Ok",
style: .default)
alertController.addAction(action)
present(alertController, animated: true)
And then you call it in the else
blocks of loadPartitions()
and parse(json:)
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// In loadPartitions
if let url = URL(string: urlString),
let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
showError()
}
// In parse(json:)
if let jsonPetitions = try? decoder.decode(Petitions.self,
from: json) {
petitions = jsonPetitions.results
tableView.reloadData()
} else {
showError()
}
With all that done, it looks like this:

You can find my version of this project at the end of day 34 on Github here.
Reflections
I thought .loadHTMLString
was pretty cool. It is very weird to write HTML inside a Swift string, and then render that to the display, but it works pretty nicely if that is what you need to do. I feel like I got the smallest little taste of what some of the cross platform frameworks are doing behind the scenes to get stuff to work in multiple places.