SwiftUI provides views, controls, and layout structures for declaring your app's user interface. The framework provides event handlers for delivering taps, gestures, and other types of input to your app, and tools to manage the flow of data from your app's models down to the views and controls that users will see and interact with.
Create your own custom views that conform to the View protocol, and compose them with SwiftUI views for displaying text, images, and custom shapes using stacks, list and more. Apply powerful modifiers to built-in views and your own views to customize their rendering and interactivity. Share code between apps on multiple platforms with views and controls that adapt to their context and presentation.
- Initializers
- Modifiers
- inheritance
When passing arguments to its initializer, using modifiers, and through its surrounding environment.
Example
In the below example we are configuring Text view that acts as the body of a TitleView
- struct TitleView: View {
- var title: String
- var body: some View {
- Text(title)
- .font(.headline)
- .italic()
- .foregroundColor(.blue)
- }
- }
The above is an example of direct configuration, as we're explicitly setting up and modifying our Text view by directly calling methods on it. However, SwiftUI also supports indirect configuration.
The indirect configuration can be useful when we want multiple sibling views to adopt the same configuration or styling in the following example in which we configure both a Text and a List to display all of their text using a font, simply by assigning that font to their parent VStack.
Example
- struct ListView: View {
- var title: String
- var items: [Item]
- @Binding var selectedItem: Item?
-
- var body: some View {
- VStack {
- Text(title).bold()
- List(items, selection: $selectedItem) { item in
- Text(item.title)
- }
- }.font(.system(.body, design: .monospaced))
- }
- }
The above shows that the entire SwiftUI view hierarchies that can be configured through their parent are powerful. In SwiftUI views, you can share styles and configurations without having to modify each view separately. Not only does that often lead to less code, but it also establishes a single source of truth for our shred configuration - like fonts, colors -- without requiring us to.
Let's take another example, in which we change an entire navigation stack's accentColor simply by assigning it to our root NavigationView which will be applied to all child views.
- struct ItemListView: View {
- @ObservedObject var items: itemList
- var body: some View {
- NavigationView {
- List(items) { item in
- Text(item.itemName)
- }
- .navigationBarItems(
- trailing: Button(
- action: { ... },
- label: {
-
- Image(systemName: "person.badge.plus")
- }
- )
- )
- }.accentColor(.purple)
- }
- }
However, sometimes we might want to apply a set of styles to a group of views without having to change their relationship to a parent view.
For example, let's say that we're building a view for displaying an address within an app, which consists of a series of stacked Text Views :
- struct AddressView: View {
- var address: Address
- var body: some View {
- VStack(alignment: .leading) {
- Text(address.recipient)
- .font(.headline)
- .padding(3)
- .background(Color.secondary)
- Text(address.street)
- .font(.headline)
- .padding(3)
- .background(Color.secondary)
- HStack {
- Text(address.postCode)
- Text(address.city)
- }
- Text(address.country)
- }
- }
- }
Also, SwiftUI ships a Group type, which lets us treat a set of views as a group without affecting their layout, drawing or position within our overall view hierarchy. Using that type, we can group our two labels together and then apply our set of modifiers to both of them at the same time.
- struct AddressView: View {
- var address: Address
- var body: some View {
- VStack(alignment: .leading) {
- Group {
- Text(address.recipient)
- Text(address.street)
- }
- .font(.headline)
- .padding(3)
- .background(Color.secondary)
- ...
- }
- }
- }
The power of Group is that it applies its modifiers directly to its children, rather that to itself. Compare that to if we would have grouped our labels using another VStack instead, which would have caused the padding and background color to be applied to that stack, rather than to our labels indivisually.
Views versus extensions
As our SwiftUI- based views grow in complexity we likely need to start using multiple ways of growing and sharing our various configurations and styles, in order to keep our code easy to work with. So far, we have mostly been dealing with styling through modifiers, but a major part of our UI configuration also comes down to how we structure our views themselves.
Let's take an example on a form that lets a user sign up for an account within an app.
- struct SignUpForm: View {
- @State private var username = ""
- @State private var email = ""
- var body: some View {
- Form {
- Text("Sign up").font(.headline)
- HStack {
- Image(systemName: "person.circle.fill")
- TextField("Username", text: $username)
- }
- HStack {
- Image(systemName: "envelope.circle.fill")
- TextField("Email", text: $email)
- }
- Button(
- action: { ... },
- label: { Text("Continue") }
- )
- }
- }
- }
In the above example, we are using the same HStack+image +TextField combination twice, and that isn't necessarily a problem given that we're configuring each of our two text fields quite differently. Let's say we wanted to turn that combination into a stand-alone component that we could reuse in other places throughout our app.
Let's take an example that creates a new view type which takes an iconName and title to display, as well as a @Binding reference to the text property that we wish to update whenever our component's text field was edited.
- struct IconPrefixedTextField: View {
- var iconName: String
- var title: String
- @Binding var text: String
- var body: some View {
- HStack {
- Image(systemName: iconName)
- TextField(title, text: $text)
- }
- }
- }
We can now use the above example on SignUpForm and replace our previously duplicated HStack configurations with instances of our new IconPrefixedTextField component
- struct SignUpForm: View {
- var body: some View {
- Form {
- IconPrefixedTextField(
- iconName: "person.circle.fill",
- title: "Username",
- text: $username
- )
- IconPrefixedTextField(
- iconName: "envelope.circle.fill",
- title: "Email",
- text: $email
- )
- }
- }
- }
The above example change will enable us to reuse our new IconPrefixedField type outside of SignUpForm.
Conclusion
SwiftUI offers a number of ways for us to structure our UI code and the way we configure our various views. While many of our custom components are likely going to be implemented as stand-alone View types, building our own extension and modifiers can enable us to share styles and configurations across a codebase in a much more lightweight manner.