Building a SwiftUI Weather App with Lasso, Part 2: Adding the SwiftUI Views

Charles Pisciotta
WW Tech Blog
Published in
9 min readNov 17, 2021

In Part 1 of this tutorial, we set up the API and the Lasso building blocks for our SwiftUI app. If you didn’t read the first article in this series, be sure to check it out here.

Now comes the next step: implementing SwiftUI Views with Lasso. In this part, we’ll implement the Home screen to illustrate key concepts when using Lasso and SwiftUI together. To see the implementation of the Detail and Map screens, be sure to review the source code.

If you’re unfamiliar with Lasso, see the documentation and WW Tech articles that introduce Lasso concepts.

This article assumes some knowledge of SwiftUI. As a result, this article excludes parts of the code and does not explain SwiftUI concepts; instead, this focuses on Lasso concepts and changes.

The project source code can be found here. As we go through this tutorial, be sure to follow along with the original project source code to add the necessary code blocks and files.

Defining a Screen

If you’re familiar with SwiftUI, you know that a View can be as small as an icon and as large as an infinitely scrolling list. As a result, Lasso defines a Screen as a View that is not the child of a parent View — for example, a home screen, a profile screen, a modal sheet, etc.

In this sample application, we have three Screens: Home, Detail, and Map. Each has child Views, but each is not a child of any higher-level View.

We’ll start by creating the Home screen. This will serve as the entry point of our app where users can search by zip code or select a recently searched city. To configure this screen, we’ll conform WeatherHomeView to LassoView.

WeatherHomeView.swift

Let’s take a look at the LassoView protocol.

Screen.swift

You’ll notice there is one requirement: A LassoView must have a ViewStore property called store. The high-level views must conform to LassoView to leverage static func createScreen(with store: ConcreteStore) -> Screen when using a ScreenModule. This function wraps a SwiftUI View in a UIHostingController under the hood.

The store property automatically conforms to ObservableObject. This conformance allows us to use the @ObservedObject property wrapper, which automatically re-renders a SwiftUI View when state changes occur.

Implementing the View

Next, we’ll begin implementing the components of our Home screen using SwiftUI. Before we do that, let’s take a look at the Home screen and remind ourselves of the major screen components.

Home Screen Breakdown
Home Screen Breakdown

The screenshot shows roughly three major components to our Home screen: a navigation bar title, a search bar, and a recent searches list. To further modularize this screen, we’ll extract the content of the list as a recent search row.

Recent search row

We’ll start by writing the list row component to display a recent search. To do this, we’ll create a View that accepts a RecentSearch data model instance. Using this data model, we’ll design a UI component with native SwiftUI components, such as VStack and Text, to display the city name and zip code from the search instance.

RecentSearchPreview.swift

You can see RecentSearchPreview doesn’t conform to LassoView because this View is not considered a Screen and does not have any ObservedObjects.

Later, we’ll wrap this UI component with a NavigationRow, a custom UI component that accepts the view’s ViewStore and an Action value.

WeatherHomeView.swift

Leveraging Xcode’s Preview Canvas, we can preview our UI implementation in Xcode — without running the simulator — using mock data. Here is a screenshot of our current implementation.

Recent Search Row
Recent Search Row

Recent search list

Next we’ll build on our recent search row component by implementing the recent searches list. To accomplish this, we’ll use the following SwiftUI components:

  • List: The foundational UI component that arranges our data and rows.
  • Section: This will group our recent search rows and add a header title.
  • ForEach: This iterates over an Identifiable collection and returns a View.
  • onDelete: A ViewModifier that executes our ViewStore’s logic when the user swipes to delete a row.
  • listStyle: A native ViewModifier that customizes our List’s appearance.
WeatherHomeView.swift

You can see how only a few lines of code are required to build a list in SwiftUI in comparison to UIKit; more importantly, note how easily Lasso fits into building these common components.

Using Xcode’s Preview Canvas, let’s take a look at the newly implemented List. To do this, we can override the State of our HomeScreenStore with mock RecentSearch values.

Recent Search List
Recent Search List

Creating the search bar with a SwiftUI Binding

When implementing our Home screen’s search bar, we’ll need to use a SwiftUI Binding to observe changes to the search bar text. If you’ve used SwiftUI before, you’re probably used to the $ prefix syntax when creating a Binding. This allows for easy read-and-write access on state values. For example, you might have a SwiftUI View that looks like this:

Traditional SwiftUI Binding

At this point, you can’t leverage that syntax when using Lasso. Lasso’s read-only restriction on State prevents this behavior. As a result, you’ll have to set up your Binding with a bit more code when creating the search bar. For example, you might set up your search bar Binding like this:

Lasso + SwiftUI Binding

Let’s take a look at each of the steps involved.

  1. First, we’ll create a function that accepts a parameter matching the Binding’s wrapped type. This function will return an Action from store that will update the appropriate State properties in the LassoStore by calling update.
  2. Next, we’ll create a Binding using the binding method. This uses a WritableKeyPath on the State to determine which property to observe. Then we’ll pass in our function from step 1, which matches the input and output of the action parameter.
  3. Last, we’ll continue implementing SwiftUI components as usual. The only difference is that we exclude the $ prefix because we created a Binding directly.

You must implement the update logic in your LassoStore with the corresponding Action to update values appropriately. Otherwise, your State property will remain constant.

Putting it all together

Consistent with the screenshot of our app, we’ve implemented nearly every component of our Home screen. The last step that remains is to put them all together and add our navigation bar title to the Screen. Realizing that our components align vertically, we’ll use a VStack to build the Screen.

WeatherHomeView.swift

Looking at this implementation, let’s highlight a few important facts.

  • Navigation: You’ll see right away that we’re defining the navigation bar title without embedding the root view in a NavigationView. That’s because our Lasso Flow is handling the navigation stack for us. You must omit NavigationView too.
  • Bindings: If you’re familiar with SwiftUI, you’ve probably used Bindings to handle changes to state properties. With Lasso, State properties are read-only. This prevents the traditional $ syntax that is normally used with native SwiftUI components, such as Alert and TextField. As a work-around, Lasso provides a binding method on the store property.
  • View methods: Many SwiftUI components have methods such as onTapGesture, onAppear, etc. In our case, the list elements are using the onDelete modifier. Many of these methods allow you to execute logic when appropriate. Looking at our project’s source code, you’ll see that Lasso extends many of these methods to accept the view’s ViewStore and an Action value — keeping the business logic in the LassoStore.

Using Xcode’s Preview Canvas one final time, we can see that our Home screen is complete.

Completed Home Screen Preview
Home Screen Preview

Our navigation bar title doesn’t appear in the preview because our preview instance is not embedded in a NavigationView.

Checking in

At this point, we’ve implemented the Home screen of our app. We’ve built the user interface using small reusable components. Additionally, we’ve maintained separation of concerns by isolating the business logic in a LassoStore. Most importantly, we’ve demonstrated the ability to build a scalable app with Lasso and SwiftUI.

When implementing the Detail screen, we can follow a process similar to the one we followed for the Home screen. When implementing the Map screen, the project follows a slightly different process by using UIViewRepresentable as a wrapper around a UIKit MKMapView component. To learn more about UIViewRepresentable, check out Apple’s documentation here and see this project’s implementation in the file named MKMapView+SwiftUI.swift. Regardless, be sure to check out the project’s source code to learn more about the remaining implementation details.

In the rest of this article, we’ll focus on some of the features that Lasso introduces to support SwiftUI, such as wrappers around native SwiftUI components and View methods. Additionally, we’ll consider the need for a Lasso StoreModule in a SwiftUI application and how we might implement one in an app.

Using Lasso’s extended SwiftUI Views

Lasso extends a lot of SwiftUI components, such as Button, Alert, TextField, etc. Let’s look at Button, for example. Traditionally, you might define one like this:

SwiftUI Button — Traditional Usage

Lasso makes interfacing with SwiftUI components a bit easier by adding convenience initializers where possible. Looking at a similar example, here’s how you might now define a Button with the same behavior.

SwiftUI Button — Lasso Version

The two examples look very similar. The latter intends to add some convenience, while also maintaining a slightly more Swifty approach.

Other examples

Lasso extends some other SwiftUI components for convenience purposes. Here are a couple of examples to illustrate possible use cases.

TextField

Lasso SwiftUI TextField

Alert.Button

Lasso SwiftUI Alert.Button

NavigationLink

You’ll notice that we’re not using NavigationLink in this sample app and the Lasso source code.

Currently, NavigationLink doesn’t interface well with Lasso because NavigationLink is tightly coupled with navigation logic and instantiates a View. Lasso is opinionated in that this logic should live within a Flow and LassoStore. When using Lasso, it’s recommended that you use components like Button instead of NavigationLink to handle navigation behavior.

Check out NavigationRow.swift to see how the behavior of NavigationLink is maintained while abstracting the navigation and view instantiation logic.

Using Lasso’s View methods

Similar to the View initializers, there are several convenience wrappers around native View methods. For example, let’s look at onAppear(perform:). Traditionally, you might use this as follows:

OnAppear Traditional Usage

Using the Lasso version, you can now use onAppear like this:

OnAppear Lasso Extension

While the two examples maintain the same behavior, you might find the second keeps the call site a bit more Swifty.

Other examples

Lasso further extends some SwiftUI View methods for convenience purposes. Here are a couple of other examples to illustrate possible use cases.

onDisappear

Lasso SwiftUI onDisappear

onTapGesture

Lasso SwiftUI onTapGesture

onLongPressGesture

Lasso SwiftUI onLongPressGesture

Using a StoreModule

In this sample app, we created our Home screen using a ScreenModule. However, we might encounter future scenarios where a StoreModule is the more appropriate implementation option, such as when state and logic are shared across multiple Screens. Let’s take a closer look at how we might have implemented that functionality.

Lasso StoreModule Example

The example shows that this implementation remains nearly the same as in UIKit. There are only two differences.

  • We instantiate the View with the ViewStore.
  • For UIKit interoperability, we wrap the View instance with UIHostingController and return the UIHostingController.

Wrapping up

This sample app is just the beginning of using Lasso with SwiftUI. Be on the lookout for more WW Tech articles and expect more updates to Lasso as SwiftUI matures and becomes increasingly common in production code.

If you’re interested in contributing to Lasso, we encourage you to fork the GitHub repository (found here) and open a pull request.

In the meantime, be sure to check out this tutorial’s source code here.

Metadata

Author: Charles Pisciotta, iOS Engineering Intern

Special thanks to Steven Grosmark, author of the Lasso framework, for his help on this tutorial project.

Notes

Are you interested in joining the WW team? Check out the careers page at WW.com to view technology job listings, as well as open positions on other teams.

--

--