Follow along at https://www.hackingwithswift.com/100/34.
This day covers the second part of Project 7: Whitehouse Petitions
in Hacking with Swift.
You can find the entire project I made to follow along in the folder of Day 33, but Day 34, in particular, focuses on several specific topics:
- Rendering a petition:
loadHTMLString
- Finishing touches:
didFinishLaunchingWithOptions
During our initial introduction to WKWebView
, I was wondering whether or not there were still any compelling use cases for it now that SFSafariViewController
has been introduced. WKWebView.loadHTMLString
might be one example.
If we have some kind of dynamic HTML string that we want to use for rendering, this method can give us a style of creating our own views that SFSafariViewController
doesn't offer. Our current app is a bit of a toy example — I'd probably create a custom, native UI and feed our petition data to its various elements — but it serves as a solid proof of concept. I can imagine a scenario where an API only returns HTML. And in that case, we could feed it straight in to loadHTMLString
(perhaps after also checking for malicious script injections 👮).
One of the first hooks generated in a project's AppDelegate.swift
file, application(_:didFinishLaunchingWithOptions:)
is where we can perform set-up operations for our app when we know that it's just about to start running.
And that's the perfect place for configuring our UITabBarController
, wiring up the two child view controllers we need it to manage.
if let tabBarController = window?.rootViewController as? UITabBarController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "Petitions Nav Controller")
navController.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
tabBarController.viewControllers?.append(navController)
}
In some cases, a storyboard might do, but on the scale of storyboard to code, the fact that our tab bar's two view stacks are pretty much identical pushes tips things towards the latter ⚖️.
Our two view controllers do have on minor difference: their API URLs. One way to handle this is to have the view controller check its position within the tab par and infer which URL it should use:
if navigationController?.tabBarItem.tag == 0 {
apiURLString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
} else {
apiURLString = "https://api.whitehouse.gov/v1/petitions.json?signatureCountFloor=10000&limit=100"
}
I prefer a more top-down approach, though. Rather than making the petitions view controller aware of our tab-bar designs, we can store our API urls in an enum
, and then have our code in application(_:didFinishLaunchingWithOptions:)
decide which controllers get to use which URLs. Iterating on the earlier didFinishLaunchingWithOptions
example, I came up with this:
if let tabBarController = window?.rootViewController as? UITabBarController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: StoryboardId.petitionsNavController)
let petitionsViewController = navController.children.first as? PetitionsListViewController
petitionsViewController?.apiURLString = PetitionsAPI.popularPetitions
navController.tabBarItem = UITabBarItem(tabBarSystemItem: .topRated, tag: 1)
tabBarController.viewControllers?.append(navController)
}
(Only one modification is used here, because I did leave the PetitionsAPI.popularPetitions
as the default for the PetitionsListViewController
).