Author:
Last Updated: 22 September, 2022
This presentation provides explanations for basic building blocks and how to apply this to creating a simple Shopping List app.
- The Goal
- Data in Swift
- Thinking about Layout
- Styling with Modifiers
- Abstraction (to remove Duplication)
- Data + Design = …
- Next Steps
Click above to view the slides. Presentation given by Frank Anderson on 25 September, 2022 during Fall 2022 Hack Session 2.
Download the sample code here. Please play with and manipulate it to see how different parts of the app work.
The Goal
Data in Swift
To build an interface, we need some data to show first. Let’s define some examples.
We’ll start with defining one item at Wollaston’s, a WollyItem
. SwiftUI likes to use Structs, so let’s define a struct SwiftUI can identify with some properties to denote what type of WollyItem we have.
We’re representing a real item, so let’s think about the attributes we need to include for our code as well such that we accurately represent the real world.
// Represents an item Frank would get at Wollaston's
struct WollyItem: Identifiable { // marked Identifiable, requirement for List in SwiftUI
var id = UUID() // UUID is an easy way to make unique identifiers that satisfy the `var id` requirement
var title: String // Use : to require definition when instantiated
var imageName: String = "tag.fill" // use = to define a default
var price: Double // Use : to require definition when instantiated
}
Now that we have the ability to define an item, let’s define an example list of these items.
// Represents Examples of WollyItems
struct Examples {
// static instances of each item
// Great if we want to preivew a single item (DetailView, ListRowView, etc.)
static let muffin = WollyItem(
title: "French Toast Muffin", price: 2.5)
static let coffee = WollyItem(
title: "Iced Coffee", price: 4.49)
static let bagel = WollyItem(
title: "Bagel", price: 1.65)
static let cookie = WollyItem(
title: "Cookie", price: 1.99)
static let brownie = WollyItem(
title: "Brownie", price: 1.8)
// static list of many items
// great for sampling a whole group (cart, list, etc.)
static let wollyItems = [muffin, coffee,
bagel, cookie,
brownie]
}
Thinking about Layout
We’ve got some data, so now let’s build an interface. Rule #1 of SwiftUI is that everything is a View. You can combine and manipulate Views to build up an interface. Rule #2 is that each View structure can only return one view.
Because of the limitation of Rule #2, we use Stacks, Groups, and other Layouts to combine views.
Using Stacks, you can build up different Views to build interfaces. By default, Stacks center views inside of them, and views take up only the space they require to render. You can just adjust how much space views take up using .frame()
and .padding()
. You can adjust the amount of space between views in a stack using SomeStack(spacing:)
and you can adjust the alignment of views in stacks using SomeStack(alignment:)
.
Styling with Modifiers
You can style individual views and groups of views using Modifiers
. Modifiers build up in order from top to bottom, so if you want some buffer space between a view and its background modifier, you’d apply padding
and then a background
. If you want more space between the background
and the next element, you’d apply another padding
. It’s hard to explain with words, so play around with it a bit on your own and see how switching the order changes the view. (Keyboard Shortcut tip: cmd + option + [ or ] lets you move code up and down lines.)
Abstraction (to remove Duplication)
Using reusable components to simplify our work and reduce duplication let’s us be more efficient programmers. It also maintains cohesion across you app since you don’t have to define every modifier and detail manually every time.
To create reusable components, you extract complex views into their own subviews.
To extract subviews, you can follow three quick steps:
- Create a new SwiftUI file which will have a struct of type View and a body which returns some View.
- Cut the parts from the main view and paste them into the body of the subview.
- Wait for Xcode to produce errors, pointing to missing variables that were in the main view which are not accessible in the subview. Create the necessary receivers (normally @Binding variables, @ObservedObject variables, or let constants) and pass in the values from the main view.
Something else you can do to remove duplication is to simplify modifiers into custom ViewModifiers/Extensions. There are some examples of this in the demo project which you can download. Look to DemoGuide.md
to see where each of the examples are linked.
Data + Design = …
Now it’s time to connect our views and data together to build a responsive application. A key thing to remember about declarative UI (like SwiftUI or React) is that the views are a product of their states. States are the values of the instance variables in a given View’s structure. States that the views can respond to (not just display) are marked using property wrappers in SwiftUI. Examples of these are @State, @Binding, @ObservedObject, @Environment, @StateObject, etc.
When passing data around, it’s important to remember there should be one source of truth, and the other views should merely refer to the source, not recreate it. The two most common property wrappers to definen sources of truth are @State and @StateObject.
To refer to sources of truth, use @Binding or @ObservedObject when you want to make changes. If you just want to display immutable data, use let.
Here’s an example from our code where we define sources of truth:
And here’s how we pass those sources of truth to sub-views that can modify and view the state:
Using these principles, you can pass data around your app. If you want more global data, do some research into .environmentObject()
and @StateObject
.
Next Steps
- Look at the code for the sample project, linked at the top of this block.
- Check out the Next Steps block at the bottom of this page for a list of resources.