Although our app works right now, it’s not something you’d want to ship on the App Store – it has at least one major usability problem, and the design is… well… let’s say “substandard”.
Let’s look at the usability problem first, because it’s possible it hasn’t occurred to you. When you read Date.now
it is automatically set to the current date and time. So, when we create our wakeUp
property with a new date, the default wake up time will be whatever time it is right now.
Although the app needs to be able to handle any sort of times – we don’t want to exclude folks on night shift, for example – I think it’s safe to say that a default wake up time somewhere between 6am and 8am is going to be more useful to the vast majority of users.
To fix this we’re going to add a computed property to our ContentView
struct that contains a Date
value referencing 7am of the current day. This is surprisingly easy: we can just create a new DateComponents
of our own, and use Calendar.current.date(from:)
to convert those components into a full date.
So, add this property to ContentView
now:
var defaultWakeTime: Date {
var components = DateComponents()
components.hour = 7
components.minute = 0
return Calendar.current.date(from: components) ?? Date.now
}
And now we can use that for the default value of wakeUp
in place of Date.now
:
@State private var wakeUp = defaultWakeTime
If you try compiling that code you’ll see it fails, and the reason is that we’re accessing one property from inside another – Swift doesn’t know which order the properties will be created in, so this isn’t allowed.
The fix here is simple: we can make defaultWakeTime
a static variable, which means it belongs to the ContentView
struct itself rather than a single instance of that struct. This in turn means defaultWakeTime
can be read whenever we want, because it doesn’t rely on the existence of any other properties.
So, change the property definition to this:
static var defaultWakeTime: Date {
That fixes our usability problem, because the majority of users will find the default wake up time is close to what they want to choose.
As for our styling, this requires more effort. A simple change to make is to switch to a Form
rather than a VStack
. So, find this:
NavigationView {
VStack {
And replace it with this:
NavigationView {
Form {
That immediately makes the UI look better – we get a clearly segmented table of inputs, rather than some controls centered in a white space.
There’s still an annoyance in our form: every view inside the form is treated as a row in the list, when really all the text views form part of the same logical form section.
We could use Section
views here, with our text views as titles – you’ll get to experiment with that in the challenges. Instead, we’re going to wrap each pair of text view and control with a VStack
so they are seen as a single row each.
Go ahead and wrap each of the pairs in a VStack
now, using .leading
for the alignment and 0 for spacing. For example, you’d take these two views:
Text("Desired amount of sleep")
.font(.headline)
Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
And wrap them in a VStack
like this:
VStack(alignment: .leading, spacing: 0) {
Text("Desired amount of sleep")
.font(.headline)
Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
}
And now run the app one last time, because it’s done – good job!
SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until October 1st.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.