Contact Tracing: 7 Ways Google and Apple are Protecting User Privacy
Android iOSContact tracing apps are already being built without a standard of privacy set; regardless of the efficacy of contact-tracing as a solution, there's a...
The best user interfaces are interactive and responsive to the user. UIKit is a capable framework that lets you build apps that users expect, but it can be tedious at time, not to mention having the flavor of an Objective-C toolkit.
SwiftUI takes advantage of Swift’s modern language features, giving us a modern framework that hits modern programming buzzwords.
It’s Declarative. Describe your UI in broad strokes using simple code.
It’s Compositional. You take existing views and connect them together and arrange them on the screen as you want.
It’s Automatic. Complex operations, like animations, can be done as a one-liner.
It’s Consistent. SwiftUI can be run on all of Apple’s platforms. It will take care to configure your UI (such as padding distance between on-screen controls, or dynamic type)
so that it feels at home on whatever platform it’s running, whether it’s a Mac or the AppleTV.
Views are the heart of SwiftUI. According to Apple:
Views are defined declaratively as a function of their input
A View in SwiftUI is a struct that conforms to the View
protocol. They are unrelated to UIView
s (which are classes). A SwiftUI View is a value type, composed of the data that explains what you want to display.
Here’s a simple view:
struct SimpleView : View {
var body: some View {
Text("I seem to be a verb")
}
}
Here SimpleView
conforms to the View
protocol. To appease the protocol, you need to provide a body
, which is an opaque type. By using some new syntax, some View
, we’re declaring that body
is some kind of View
, without having to specify a
concrete type. Check out Swift Evolution Proposal 244 for more about opaque types.
The body
is some View that describes what should be on the screen. In this case it’s a simple text label with a default configuration. Your body
can be composed of multiple Views, such as a stack that arranges images horizontally.
Because body
is a View, and it’s an opaque type, nobody really cares if a given View
ends up being a single on-screen entity or a bunch of views composed in a stack or a list. This lets us compose complex hierarchies easily.
SwiftUI View
s are value types, so we really don’t have stored properties to modify their look and feel. We need to declare our look and feel intentions to customize our views.
SwiftUI has ViewModifiers
, such as .foreground
to change the foreground color or .font
to change a view’s font, that applies an attribute to a View
. These modifiers also return the view they’re modifying, so you can chain them:
Text("SwiftUI is Great!")
.foreground(.white)
.padding()
.background(Color.red)
This declarative syntax is very easy to read, letting you accomplish a lot with little code.
You can build hierarchies of views. In UIKit, we use UIStackView
to make vertical and horizontal stacks of UIView
s. In SwiftUI, there’s an analogous HStack
and VStack
views that let you pile views horizontally or vertically.
This view positions one label above another.
VStack {
Text("Swift UI is Great!")
.foreground(.white)
.padding()
.background(Color.red)
Text("I seem to be a verb")
}
There’s also ZStack
that lets you stack views on top of each other, like putting a label on top of an image.
You’re welcome to nest multiple stacks to compose complex view hierarchies. The stack views will automatically provide platform-appropriate defaults and if you don’t like the defaults, you can provide your own alignment and spacing.
SwiftUI lists look like tableviews in UIKit. In SwiftUI, the framework does the heavy lifting, freeing you from data source boilerplate.
Setting up a list with hard-coded views is really easy. Setting it up is exactly like setting up a stack:
struct ListView : View {
var body: some View {
List {
Text("Look")
Text("A")
Text("TableView")
}
}
}
The views inside the closure of a list serve as static cells.
The List
also has an optional data parameter (that conforms to the Identifiable
protocol) where you can provide some data to drive the creation of the cells inside the table.
Here’s a struct of a contact:
struct Contact: Identifiable {
let id = UUID()
let name: String
}
Having a unique identifier in your identifiable data helps with list selection and cell manipulation.
And here’s a View that hardcodes a list of contacts:
struct ListView : View {
let contacts = [Contact(name: "Arya"), Contact(name: "Bran"), Contact(name: "Jon"), Contact(name: "Sansa")]
var body: some View {
List(contacts) { contact in
Text(contact.name)
}
}
}
This is the magic bit:
List(contacts) { contact in
Text(contact.name)
}
This is telling the list: “for every Identifiable
in this list of contacts, make a new Text
view whose text contents are the contact’s name. You can add new folks to contacts
, and the list will contain more rows.
Static hard-coded lists are great for initial prototyping of your ideas, but it’d be nice for the list to be dynamic, such as populating it with data from the internet. Or insert and
delete new rows, and to rearrange things.
WWDC 2019 session 226 Data Flow Through SwiftUI tells us:
In SwiftUI data is a first class citizen
To manage state within our app, SwiftUI provides stateful binding to data and controls using property wrappers. Check out Swift Evolution Proposal
258 for more about property wrappers.
Here we’re using the the @State
property wrapper to bind an isEnabled
property to a toggle control:
struct EnabledTogglingView : View {
@State private var isEnabled: Bool = false
var body: some View {
Toggle(isOn: $isEnabled) {
Text("Enabled")
}
}
}
Another quote from session 226, Data Flow Through SwiftUI:
SwiftUI manages the storage of any property you declare as a state. When the state value changes, the view invalidates its appearance and recomputes the body.
The state becomes the source of truth for a given view.
@State
is great for prototyping, and for properties that are intrinsic to a view (such as this enabled state). To actually separate your data layer from your view layer, you need BindableObject
.
So say we’re setting up a preference panel, or some kind of configuration panel with a sequence of toggle buttons. First, a model object for each individual setting:
class Settings: BindableObject {
var didChange = PassthroughSubject<Bool, Never>()
var isEnabled = false {
didSet {
didChange.send(self.isEnabled)
}
}
}
Notice that Settings
is a reference type (a class), which conforms to the BindableObject
protocol. The PassthroughSubject
doesn’t maintain any state – just
passes through provided values. When a subscriber is connected and requests data, it will not receive any values until a .send()
call is invoked.
struct BindableToggleView : View {
@ObjectBinding var settings: Settings
var body: some View {
Toggle(isOn: $settings.isEnabled) {
Text("Enabled")
}
}
}
take another look at this line of code:
@ObjectBinding var settings: Settings
That’s weird. It’s not an optional then what’s it initialized to? Well, because settings
is declared with the @ObjectBinding
property wrapper, this tells the framework that it will be receiving data from some bindable object outside of the view. This object binding doesn’t need to be initialized.
You inject a BindableObject
into the View
that should use it. Here’s how our Settings
gets hooked up to this BindableToggleView:
BindableToggleView(settings: Settings())
Your view does not care where the data lives or how it is populated disk, cache, network, osmosis. The view simply knows that it will be binding to some object when available.
The @ObjectBinding
property wrapper creates a two-way binding. Whenever a mutation occurs the view automatically re-renders.
@ObjectBinding
requires dependency injection to provide the data object. That can be inconvenient.
To make this object available anywhere within your app without dependency injection you can use the environment. New with SwiftUI you have an environment object that contains all kinds of information like screen size, orientation, locale, date format, etc. If you stick an object into the environment then that object can be accessed anywhere within your app. Think of it as a dictionary that is globally scoped within your app.
So how do you use it? The @EnvironmentObject
property can help you with this.
struct BindableToggleView : View {
@EnvironmentObject var settings: Settings
var body: some View {
Toggle(isOn: $settings.isEnabled) {
Text("Enabled")
}
}
}
In the above example, everything remains the same as seen previously, except you replace @ObjectBinding
with @EnvironmentObject
.
To inject an object into the environment you call the .environmentObject
method.
ContentView().environmentObject(Settings())
An instance of Settings
is now injected into the environment and is available to you anywhere within your app. When the object mutates then every view bound to it will receive the update and re-render its view. You no longer have to maintain a mental model of all the dependency-injections to keep your user interface in sync.
Animations in SwiftUI work like any view modifier. Basic animations can be achieved using the withAnimation
method.
Here’s a View with two text labels:
struct ContentView: View {
@State var scale: CGFloat = 1
var body: some View {
VStack {
Text("This text grows and shrinks")
.scaleEffect(scale)
Text("Scale it")
.tapAction {
self.scale = self.scale == 1.0 ? 5.0 : 1.0
}
}
}
}
Tapping the second text label alters the size of the first text label, alternating its size between normal and five-times larger. The scaling happens instantly without any animation.
It’s a one-liner to make it animate smoothly.
Text("Scale it")
.tapAction {
withAnimation {
self.scale = self.scale == 1.0 ? 5.0 : 1.0
}
}
withAnimation
can take an argument that lets you customize the animation. You can call it with a .basic
animation, or you can have more fun with .spring
animation that gives you control to make a beautiful spring effect.
SwiftUI is a delight to use. The focus is on creating an app because SwiftUI gets out of your way, taking care of a lot of annoying details. Design rich, interactive user interfaces by composing reusable views. Ensure data is coming into the system and not worry about keeping the view and model layers in sync. Finally, easily add animations to make views come alive. If you’re interested to learn more about iOS development & Swift programming, stay updated with our books & bootcamps.
Contact tracing apps are already being built without a standard of privacy set; regardless of the efficacy of contact-tracing as a solution, there's a...
At Big Nerd Ranch, our iOS team has created a library called Deferred to help you work with values that have not yet been...