UPDATE: This feature is now built-in starting with iOS 13 via the insetGrouped table view style!
The Wallet app was redesigned in iOS 12.2 with a fresher, more rounded look. This is most prominent in the table views scattered throughout the app — sections are rounded at the edges, similar how a detail table view looks on iPad. It looks really good everywhere, though, so let’s try to recreate it!
The iOS 12.2 Wallet app has a fresher, more rounded look.
I’ll be using my app SuperHomework for this, but of course the code should work on any iOS app. I’m also using the iOS 12.2 beta (it should work on any iOS 12.x release, though) and Swift 4.2, the latest versions at the time of writing. I should also note that part of this implementation was taken from this answer and part of this answer on Stack Overflow.
Let’s get started! The first thing you’ll notice is how the section insets are increased in the Wallet app. I was experiencing issues setting UITableView.contentInset (which you would most likely want to do if you can get it working), so instead I just applied constraints to the left and right edges of the table view using SnapKit and set the background color of the main view to that of the table view, giving a seamless look.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next, I created a UITableView extension to make the following implementation easier to apply to many different table views.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Inside the useRoundedSectionCorners method, I disabled the default separator line that runs through all of the cells and adds a section border — we’ll write our own separator in a bit. I also removed all of the excess padding around the edges of the table view that section headers/footers use to indent themselves.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now let’s jump into display(withRoundedSectionCorners:at:). The first thing we need to do is determine which modifications to make on the cell — that is, we don’t want to round the corners of a cell that’s in the middle of a section and whatnot. We can do that by passing the cell’s indexPath to the method and doing some math:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next, we’ll round the corners of the cell based on the calculations we just did. This is achieved using a UIBezierPath with our desired corner radius (12pt in this case to match the Wallet app).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Finally, we need to draw our own separator line for cells that are in the middle of a section, if the section has multiple rows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now we’re all set to implement this in our own table view! We can do so by calling useRoundedSectionCorners()on our table view in our view controller’s viewDidLoad() and by calling display(withRoundedSectionCorners:at:) inside our table view delegate’s tableView(_:willDisplay:forRowAt:) method, like so:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Intended for students, my app SuperHomework deals a lot with assignments in the form of JSON. Because Swift is a type-safe programming language, though, I had to convert every form of JSON object sent from the backend into its own type. My initial implementation was something along the lines of this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…but this is repetitive, and it means that I have to make my instance types implicitly unwrapped optionals for the initializer to work — yuck. (And the app could still crash later on, too!) In addition, there’s no simple way to alert the user what exactly went wrong.
What can I do to fix this? The answer: throwable initializers.
Swift 2 introduced the try/catch error handling method we all know and love from other popular programming languages like C++, Java, Python, and so on. It’s easy to set up — wrap the throwing code inside a do block, bolt on try to the front, and catch the error.
https://gist.github.com/Wilsonator5000/95157d251d1f789dbc779fb728bd7375
You’ll notice that if randomNumber returns an even number, the do block exits and "Error: EvenNumberError" will be printed to the console. Otherwise, the program will continue inside the do block and eventually you’ll receive "Hooray, no errors!".
So how can we apply this principle to initializers that could fail? Unfortunately Swift’s builtin types use failable initializers (initializers that return nil when they cannot create the object) which can be cumbersome to use at times and kind of undermine the point of try/catch, so let’s start by improving some built-in types using throwable initializers.
Creating our own throwable initializers
In order to make things a bit easier, I created a generic InitializableFromInput protocol that will take care of the heavy lifting for me, particularly for handling optional inputs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…and here’s the code for the InvalidInputError type that I used above — it’s pretty self-explanatory, and we’ll use it a lot when implementing the non-optional-input initializer:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now we can begin to implement throwable initializers for a few built-in types. Let’s start with Int. The Int type has an initializer that accepts a string, but returns nil when the string does not contain a valid integer. Instead of returning an optional, let’s create a throwable initializer by adopting the InitializableFromInput type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We can extend this idea to various other types, like URL, Date, and so on:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
To make it easier to deal with JSON like I mentioned with Assignment at the beginning, I created a quick little Dictionary extension that adopts throwable initializers. If the key does not exist in the dictionary or is not of the correct type, the initializer throws a KeyError.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now that we have implemented throwable initializers throughout our lower-level types, we can extend their use to our larger data types. Finally we can get rid of our force-wraps or implicitly-unwrapped optionals and replace it with nice, clean, type-inferenced code!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In this guide, we learned how to apply the idea of throwable initializers to our code and make it more concise and type-inferenced. We created a generic protocol called InitializableFromInput so our implementation works with any type, and extended Swift’s built-in types like URL, Int and any RawRepresentable enum in addition to our own higher-level data types to make our lives easier.
Extending knowledge: throwable casting
To take it to another level, we can even go so far as to replace as? and as! with our own throwable casting function. This can be useful in a number of cases; try to think of your own!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There are a few current limitations of my implementation of throwable initializers; if you have a better way, please let me know!
You can only specify one InputType for the protocol and implement only one fromInput(_:) initializer.
Technically fromInput(_:) isn’t actually an initializer — rather, a static method that returns an instance of its member type. This is due to the fact that Swift doesn’t allow the delegation of failable initializers (ie. ones that return an optional) to non-failable initializers (in our case, the ones that throw InvalidInputError).
Thanks for reading! If you enjoyed the article, make sure to share it. If you want to try out my latest app SuperHomework, you can do so by following the link here or learning more at the official website.
When you’re writing an iOS app, you have to deal with view controllers that manage the app’s user interface and respond to events (such as tapping a UIButton or entering text in a UITextField). But sometimes, you need to respond to events outside of the current view controller — for example, in a to-do list app, you want the table view that displays the list to update said list when the user adds a new item. If you want to easily allow options or details the be added to an item during its creation, you’ll probably have two separate view controllers: one for the entire list, and one for item creation.
But how would you pass the information between the two view controllers? Well, you could use a global or shared variable:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
… but this is messy, and the ItemsTableViewController would still have to check for the value of the variable every so often manually!
There’s a better way. Let’s dive in.
Think of delegates like “subscribers” or “slaves”
When you create a delegate, you typically use it in one of two ways: to have a class respond to the events of another class (usually a view controller responding to the events of another view controller or an asynchronous helper class) or perform an action for another class (like a view controller showing an alert when the Internet connection goes down). We will discuss both ways here.
Delegates as Subscribers
If you want a class to be notified when something happens, you can use delegation. In our example, the “delegate” will be our ItemsTableViewController.
The first thing we need to do is define a set of functions that the delegate must follow. This is to ensure that the delegator (the class sending the events) is able to call certain defined functions from that delegate. We can do this with a protocol:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Notice that we do not add code to the function — that task is up to the class that uses the protocol.
Now, any class that conforms to that protocol must also include its methods:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Now that our view controller conforms to the protocol, the delegator knows that the view controller is a valid class to send actions to.
We’ll fill out the above function in a minute, but first, let’s create our delegator (in this case, the CreateItemViewController):
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Any class that wants to become this view controller’s delegate must assign itself to the delegate object. Notice that the object is of type ToDoItemEventsDelegate — this means that only classes that conform to ToDoItemEventsDelegate are allowed to assign themselves to the object. Also, the object is optional because having a delegate is not required, only if it is assigned.
To send an event to the delegate, you can just call the function from the delegate object (if there is one — notice the optional binding). How do we know that the function exists within the delegate object? Because we know that the delegate object must conform to the protocol we made, which includes that function.
Now when a new item is added, the ItemsTableViewController’s toDoItemAdded(_:) function will be called and any code within it will be executed:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
You already have a delegate in your iOS project right now — the AppDelegate. If you open it up (under AppDelegate.swift), you can see that it conforms to UIApplicationDelegate and contains a few functions that respond to events from iOS, like your app opening, closing, going in the background, or receiving a notification.
Delegates as Slaves
You could also think of delegates as slaves, performing the action for another class that is unable to do so. For example, you could have a helper class that detects the status of the Internet connection:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
If you wanted to display an alert when the Internet connection was lost, you could use a delegate to show the alert on behalf of the helper class:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Now whenever your InternetConnectionHelper detects a change in the status, it will call the view controller’s statusChanged(_:) function and the view controller can display an alert or update its views accordingly.
But Wait! What if I want more than one delegate at a time?
Delegates are assigned per instance, which means that every instance of the delegator can have its own delegate (that is, this code is OK):
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
In a singleton class, you could have an array of delegates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
When a class wants to become a delegate for the singleton, it can just append itself to the array:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
And if you want the delegator to send an event to its delegates, just iterate over the array:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
So that’s how delegation works in Swift! All you really need is:
A protocol that describes what functions the delegate class should follow. For a class to even have a chance of becoming a certain delegate, it must conform to that protocol.
A delegator class that other classes can assign themselves as delegates to. The delegator is also responsible for sending the events to its delegate(s).
A delegate class(es) that assigns themselves as the delegator’s delegate. As long as it conforms to the delegate protocol specified, it can receive events and perform actions on behalf of the delegator.
Now that you know how delegates work, you’ll also understand why, for example, you can have a view controller conform to UITableViewDelegate, implement the methods, and have them “magically called” in the background — in fact, they are called by iOS UIKit delegators.
TPSHomeworkBot, a solution that unifies where students get their homework, will be launching at Tyngsborough Middle School on Monday, October 16. Here’s what you need to know.
How it works
TPSHomeworkBot started as an idea over the last few days counting down to the start of Eighth Grade, while I was trying to find a better way to keep track of my homework. Agenda books were big and clunky and never worked for me, and I had tried using services like Trello or a calendar, but those didn’t work either. I had always had an idea of using my own, custom-built service in the back of my mind, and early versions of a “homework app” had never been automatic or efficient and slowly died away. I needed something that would just work every single day, but to do that I needed to build it from scratch. So I sat down at the Linux terminal on my Raspberry Pi and got to work.
TPSHomeworkBot runs off a Raspberry Pi Zero that’s on 24/7.
The program is written in Python, a flexible and easy-to-use programming language that comes preinstalled on the Pi. The Pi’s default OS (“Raspbian”) is configured to run the program every day at around 2 PM.
My plan for TPSHomeworkBot was to have it automatically create a PDF of the teacher’s blogs (so that one could print out their homework agenda when they got home) and additionally create grade-based summaries for the assignments (for example, “8th grade has homework in Math and History tonight” or “6th grade has no homework tonight”). All of this information would be posted on Twitter under the account @TPSHomeworkBot for the PDFs, and @TPSHomeworkBot6, @TPSHomeworkBot7, and @TPSHomeworkBot8 for the summary.
So I’m all set up, and on the first day of school I talk to my principal, Mr. Pollet, about the idea. He was happy to help me get it out to students, and we set off to find information about the TMS teachers and their blogs for use in TPSHomeworkBot.
My further plans for TPSHomeworkBot, drafted out on my whiteboard.
Mr. Pollet’s and my initial spreadsheet for various teacher’s blogs and information.
Once I had this information I was able to add it into my program and create both the PDF and the summary accounts.
Now it was off to testing! Here are two examples of a PDF post and a summary post:
TPSHomeworkBot should be happily welcomed by students, as it means that they don’t have to keep track of homework by writing it down in a $4 agenda book they carry around everywhere — instead they get a notification on a device they already own. They don’t have to install anything, either, as TPSHomeworkBot is entirely Twitter-based, and setup is as easy as following the account(s)!
The only thing students have to do is scan the Twitter QR code with their phone to follow the account. Here’s a poster that will be hung around the school.
What this means for parents
While some parents already opt-in to some teachers’ Remind101 service, this does not cover all the assignments that their child is given. TPSHomeworkBot pulls directly from teachers’ blogs, which most post daily on, which means that any assignment the teacher uploads will be posted on the Twitter feed. Now parents will be able to keep track of their child’s work using the same system as their child. And since every teacher at TMS uses a blog, the service is unified and only requires one platform — one that most parents and students already use.
Stay tuned for Monday!
TPSHomeworkBot will be launched on Monday and should have a great impact on our school and the way students stay informed of their assignments. In a few weeks, I will post again about the progress and usage of TPSHomeworkBot. If you have any suggestions, feel free to contact me on Twitter at @wgramer03. Until then, stay tuned!
You must be logged in to post a comment.