Categories
Uncategorized

Custom progress bars in SwiftUI

Have you ever had to implement a certain progress bar into your app, for example, to display an ongoing loading process? Well, in SwiftUI it’s super simple to create these 👇

I also uploaded the progress bars templates to GitHub. So if you want to skip this tutorial, you can just copy and paste the files to your project and initialize the specific bars with multiple customization options.

Simple Progress Bar

Let’s start with creating a simple progress bar like this one:


First, create a new SwiftUI file called SimpleProgressBar.

Create a State property assigned to a CGFloat value. We use this to represent the current progress of our loading process. For example, 0.5 means that 50 percent have loaded so far.

struct SimpleProgressBarDemo: View {
    @State var currentProgress: CGFloat = 0.0

    var body: some View {
        Text("Hello World")
    }
}

The next step to create our progress bar is to replace the default Text view with a ZStack with the leading alignment mode.

var body: some View {
    ZStack(alignment: .leading) {

        }
    }

ZStacks are used for stacking elements on top of each other. Click here to learn more. The first object in our ZStack is the „inner“, static bar. To create this, we use a RoundedRectangle.

ZStack(alignment: .leading) {
            RoundedRectangle(cornerRadius: 20)
        }

We want the bar to be gray, wide and thin.

RoundedRectangle(cornerRadius: 20)
                .foregroundColor(.gray)
                .frame(width: 300, height: 20)

Another, dynamic bar should overlay our static one. Therefore, we place another RoundedRectangle into our ZStack. This rectangle should be as high as the first one but only as wide as it if our currentProgress is 1.0, i.e. 100 percent are loaded. To achieve this, we write:

RoundedRectangle(cornerRadius: 20)
                .foregroundColor(.blue)
                .frame(width: 300*currentProgress, height: 20)

To simulate a loading process, we use a timer that adds 0.1 to our currentProgress every second. We implement a corresponding button that starts the timer.

struct SimpleProgressBarDemo: View {
    
    @State var currentProgress: CGFloat = 0.0
    
    var body: some View {
        VStack {
            ZStack(alignment: .leading) {
                //...
            }
            Button(action: {self.startLoading()}) {
                Text("Start timer")
            }
        }
    }
    
    func startLoading() {
        _ = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            withAnimation() {
                self.currentProgress += 0.01
                if self.currentProgress >= 1.0 {
                    timer.invalidate()
                }
            }
        }
    }
}

By using the withAnimation wrapper, we apply a smooth, default animation to our bar’s progress. Run the view in the live preview and click the button to simulate the loading process.

Circular Progress Bar


First, create a new SwiftUI file called CircularProgressBar. Let’s implement a State for keeping track of the loading progress.

struct CircularProgressBarDemo: View {
    
    @State var circleProgress: CGFloat = 0.0
    
    var body: some View {
        Text("Hello World!")
    }
}

Replace the default Text view with a ZStack again.

ZStack {
            
        }

The first object in our ZStack is the „inner“, static circle. To create it, declare a Circle object.

ZStack {
            Circle()
        }

We don’t want our circle filled out, we just want it to be a ring. To do this we use the .stroke modifier. Next, we modify the width and height of our circle by using the .frame modifier.

Circle()
    .stroke(Color.gray, lineWidth: 15)
    .frame(width: 200, height: 200)

Another, dynamic circle should overlay the static one. Therefore, place a new Circle view into the ZStack. We use the .stroke and .frame modifier again, but this time we use a different color.

Circle()
     .stroke(Color.blue, lineWidth: 15)
     .frame(width: 200, height: 200)

The circle should only be closed when our circleProgress State is 1.0. For example, if the progress state is 0.5, the ring should only be half long. To achieve this we use the .trim modifier (as the first modifier!) and pass the circleProgress to its end argument.

Circle()
    .trim(from: 0.0, to: circleProgress)
    .stroke(Color.blue, lineWidth: 15)
    .frame(width: 200, height: 200)

Now try to change the value of the circleProgress State. You see that our circular progress bar immediately adapts to it.But we don’t want our circular progress bar to start at the top. Therefore, we spin it by 90 degrees.

Circle()
     .trim(from: 0.0, to: circleProgress)
     .stroke(Color.blue, lineWidth: 15)
     .frame(width: 200, height: 200)
     .rotationEffect(Angle(degrees: -90))

If you want, you can also insert a Text indicating how much percent is already loaded.

ZStack {
            //...
            Text("\(Int(self.circleProgress*100))%")
                .font(.custom("HelveticaNeue", size: 20.0))
        }

To simulate the loading process, we implement a corresponding button that fires a timer again.

struct CircularProgressBarDemo: View {
    
    @State var circleProgress: CGFloat = 0.0
    
    var body: some View {
        VStack {
            ZStack {
                //...
            }
            Button(action: {self.startLoading()}) {
                Text("Start timer")
            }
        }
    }
    
    func startLoading() {
        _ = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            withAnimation() {
                self.circleProgress += 0.01
                if self.circleProgress >= 1.0 {
                    timer.invalidate()
                }
            }
        }
    }
}

Tip: Subtract twice the line width from the width and height of the circle to achieve a style like this:

ZStack {
                Circle()
                    //...
                Circle()
                    .trim(from: 0.0, to: circleProgress)
                    .stroke(Color.blue, lineWidth: 15)
                    .frame(width: 200-15*2, height: 200-15*2)
                    .rotationEffect(Angle(degrees: -90))
                //...
            }

Halved-Circular Progress Bar

Creating this cool bar is very similar to what we’ve already done.


As always, we create a State property to know how far the loading process is already. We also replace the default text with a ZStack.

    @State var progress: CGFloat = 0.0
    
    var body: some View {
        ZStack {
            
        }
    }

Again, we start by creating a static circle. Only this time we trim it to the half. We also spin it by 180 degrees. To create a dashed ring we use the .stroke modifier again, but this time we use a different StrokeStyle.

Circle()
                .trim(from: 0.0, to: 0.5)
                .stroke(Color.blue, style: StrokeStyle(lineWidth: 12.0, dash: [8]))
                .frame(width: 200, height: 200)
                .rotationEffect(Angle(degrees: -180))

We equip our dynamic circle with the usual .stroke modifier and trim it depending on our loading process. We also spin it by 180 degrees.

ZStack {
            Circle()
                //...
            Circle()
                .trim(from: 0.0, to: progress/2)
                .stroke(Color.blue, lineWidth: 12.0)
                .frame(width: 200, height: 200)
                .rotationEffect(Angle(degrees: -180))
        }

You can add an according Text again and simulate the loading process with a timer.

struct HalvedCircularBar: View {
    
    @State var progress: CGFloat = 0.0
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .trim(from: 0.0, to: 0.5)
                    .stroke(Color.blue, style: StrokeStyle(lineWidth: 12.0, dash: [8]))
                    .frame(width: 200, height: 200)
                    .rotationEffect(Angle(degrees: -180))
                Circle()
                    .trim(from: 0.0, to: progress/2)
                    .stroke(Color.blue, lineWidth: 12.0)
                    .frame(width: 200, height: 200)
                    .rotationEffect(Angle(degrees: -180))
                Text("\(Int(self.progress*100))%")
                    .font(.custom("HelveticaNeue", size: 20.0))
            }
            Button(action: {self.startLoading()}) {
                Text("Start timer")
            }
        }
    }
    
    func startLoading() {
        _ = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            withAnimation() {
                self.progress += 0.01
                if self.progress >= 1.0 {
                    timer.invalidate()
                }
            }
        }
    }
}

Activity Indicator

Although this is not really a progress bar, we want to have a look at how to create an activity indicator in SwiftUI.


We declare a State again, but this time for keeping track of the current indicator’s position by its degree.

@State var degress = 0.0

As usual, we use a static and a dynamic circle. We trim the dynamic circle by a fixed value. To rotate the whole dynamic circle, we use the .rotationEffect modifier and make it dependent on the value of our degrees State.

Circle()
                .trim(from: 0.0, to: 0.6)
                .stroke(darkBlue, lineWidth: 5.0)
                .frame(width: 120, height: 120)
                .rotationEffect(Angle(degrees: degress))

To present a loading process, we can simply use a repeating timer that starts as soon as the view has loaded. The timer adds a few degrees to our State at fast time intervals. Once our circle has one full time, we set our degree State back to 0.

struct ActivityIndicator: View {
    
    @State var degress = 0.0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.6)
            .stroke(darkBlue, lineWidth: 5.0)
            .frame(width: 120, height: 120)
            .rotationEffect(Angle(degrees: degress))
            .onAppear(perform: {self.start()})
    }
    
    func start() {
        _ = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { timer in
            withAnimation {
                self.degress += 10.0
            }
            if self.degress == 360.0 {
                self.degress = 0.0
            }
        }
    }
}

Conclusion 🎊

Great, we just learned how easy it is to create beautiful progress bars in SwiftUI. If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Categories
Uncategorized

New into iOS development – Should I learn SwiftUI or UIKit & Storyboards

A question that many new iOS developers are facing is: Should I start learning SwiftUI first or should I start with building apps the „old“ way using storyboards and the UIKit framework ❓⠀

When you consider working with SwiftUI, you must be aware of the fact, that apps built with SwiftUI are only working on devices running iOS 13+. However, the adoption rate of iOS users is very high, especially when being compared to Android. So it’s legit to say that you only want to support devices meeting this requirement. But the main problem that comes when working with SwiftUI only, is that this framework is still pretty new and rough. That means that there are not as many resources to work with as with storyboards and UIKit. Plus, that there are still some features that SwiftUI itself is not capable of, for example implementing some more complex controls.⠀

Differentiate between UIKit and storyboards ❗️

First, you must distinguish between UIKit and storyboards. UIKit is a framework that defines the core elements of iOS apps, for example UIImages, UILabels, UITextField, just to name a few. Storyboards on the other side are just a tool to arrange and compose your interface by using these elements.

SwiftUI is another way of composing your interface in a way more intuitive way. SwiftUI on its own also defines such elements as UIKit does (Images, Texts etc.), but at the current state not as much as UIKit. However, SwiftUI will definitely be the future of iOS development and the main framework to work with. And it’s super easy to get started with SwiftUI with it and it has a really high learning curve which is especially great for beginners. So starting to learn iOS development by working with SwiftUI can be really motivating!

SwiftUI + UIKit 🚀

To fill the holes of what SwiftUI is currently not capable of, it’s a big advantage to also know the UIKit framework. This framework will still be an important part of the iOS dev world for at least the next few years. It’s also important to be aware that potential employers will most likely demand UIKit knowledge and skills from you, at least for the next couple of years.⠀

Regarding working with storyboards: At this point, it’s totally possible to build apps not using them but SwiftUI. To build the interfaces you can rely only on SwiftUI + UIKit. So although storyboards are not deprecated yet (and likely won’t be until the next few years), there is no requirement to learn to work with these if you are new to iOS development.

Our recommendation 🎊

To sum it up, we can encourage new iOS developers to start with learning SwiftUI instead of working with storyboards. But at this point, it’s a very big advantage to know at least the basics of UIKit in case it’s not possible to implement the wanted app functionality using SwiftUI only. Fortunately, is super easy to interface between SwiftUI and UIKit. In our SwiftUI eBook, we show you how to do this step-by-step!

Also make sure, you check out our free SwiftUI tutorials!

Categories
Uncategorized

Property Wrappers in SwiftUI – Understanding @State, @ObservableObject, @EnvironmentObject etc.

Property wrappers in SwiftUI provide us with variables with a specific logic depending on the type of the property wrapper. This logic is the core of the data flow concept in SwiftUI. To understand how they work and when to use which one, we explain every one step-by-step 👇

@State

State is probably the most frequently used property wrapper in SwiftUI.

You can easily declare a State by putting the @State keyword in front of a variable.

struct ContentView: View {

    @State var myVariable = 1

    var body: some View {
        //...
    }
}

Hint: State properties must always be related to a view in SwiftUI. So make sure you always declare them inside a View struct (but not inside the View’s body)!

So, what does a State property do? Well, you can read out and manipulate data just as you do with regular variables in Swift. But the key difference is that every time the data of a State changes, the related View gets rebuilt.

To see this in action, take a look at this example:

struct ContentView: View {
    
    @State var myInteger = 1
    
    var body: some View {
        VStack {
            Text("\(myInteger)")
            Button(action: {self.myInteger += 1}) {
                Text("Tap me!")
            }
        }
    }

We have an Integer variable wrapped into a State. In the related view, we have a Text object reading out the State’s data and a button. When we tap on the button, the State’s value get’s increased. As we learned, this manipulation causes the whole view to rerender and the Text to eventually displaying the updated value!

If you want to see another example how to use this functionality in practice, we recommend you to read our Login Page tutorial, where we talk about using States for implementing Text Fields.

@Binding

As we learned above, State properties must always relate to a specific View. But sometimes we want to have access to a State property from the outside, for example from child views.

For creating such a reference, we use the @Binding property wrapper. Suppose you want to create a Binding from a child view to the view containing the State property, just declare a variable inside the child view and mark it with the @Binding keyword.

struct ChildView: View {
    
    @Binding var myBinding: String
    
    var body: some View {
        //...
    }
}

You can then create the binding to the State by initialising the child view with referring to the State by using the “$” syntax.

struct ContentView: View {
    
    @State var myBinding = "Hello"
    
    var body: some View {
         ChildView(myBinding: $myBinding)
    }
    
}

You can then use the Binding as you do with States. For example when you update the Binding property, it also causes the State to change its data which then causes the view to rerender as well.

struct ContentView: View {
    
    @State var myInteger = 1
    
    var body: some View {
        VStack {
            Text("\(myInteger)")
            OutsourcedButtonView(myInteger: $myInteger)
        }
    }
    
}

struct OutsourcedButtonView: View {
    
    @Binding var myInteger: Int
    
    var body: some View {
        Button(action: {self.myInteger += 1}) {
            Text("Tap me!")
        }
    }
}

In the example above, the MotherView contains a State property holding an Integer as well as a Text for displaying that data. The ChildView contains a Binding to that State and a Button for increasing the Binding’s property value. When we tap the button, the State’s data get updated through the Binding which causes the MotherView to rerender and eventually displaying the new Integer.

Again, we recommend you to read our Login Page tutorial if you want to see how to implement Bindings in practice.

@ObservableObject and @ObjectObserver

ObservableObjects are similar to State properties which you already know. But instead of (re)rendering a view depending on its assigned data, ObservableObjects are capable of the following things:

● OberservableObjects are wrapping classes not variables

● These classes can contain data, for example, a string assigned to a variable

● We can bind one or multiple views to the ObservableObject (or better said, we can make these views observe the object).

● The observing views can access and manipulate the data inside the ObservableObject. When a change happens to the ObservableObject’s data all observing views get automatically rerendered, similar to when a State changes

Here’s an example on how to implement such an ObservableObject:

import SwiftUI
import Combine

struct ContentView: View {
    
    @ObservedObject var myObservedObject: MyObservableObject
    
    var body: some View {
        VStack {
            Text("\(myObservedObject.myInteger)")
            Button(action: {self.myObservedObject.increaseInteger()}) {
                Text("Tap me!")
            }
        }
    }
    
}

class MyObservableObject: ObservableObject {
    
    let objectWillChange = PassthroughSubject<MyObservableObject,Never>()

    var myInteger = 1 {
        didSet {
            objectWillChange.send(self)
        }
    }
    
    func increaseInteger() {
        myInteger += 1
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(myObservedObject: MyObservableObject())
    }
}
#endif

Hint: Make sure you import the Combine Framework and conform to the @ObservableObject protocol. If you want to learn how to do this, check out this tutorial!

We can make a view observe such an ObserableObject by creating an @ObservedObject property. The view can then access the ObservableObject and also manipulate its data. In the example above, the View calls the ObservableObject’s increaseInteger function which increases the value of the myInteger variable inside the MyObservableObject. This manipulation causes the observing ContentView to rerender with eventually showing the updated value.

ObservableObjects are great for sharing data to multiple views, as you learn in the How to navigate in SwiftUI tutorial. Give it a try!

@EnvironmentObject

An EnvironmentObject is a property wrapper for a data model which, once initialised, can share data to all view’s of your app. The cool thing is, that an EnvironmentObject is created by supplying a ObservableObject.

Using EnvironmentObjects is suitable for passing data to several views at once without needing an “initialisation chain”. The exact explanation of what I mean by this can be found in this tutorial, where we use an EnvironmentObject to navigate through our SwiftUI app!

Conclusion 🎊

Today we talked about property wrappers in SwiftUI which are crucial for handling the data flow concept used within SwiftUI. Make sure you check out the linked tutorials!

If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Have any questions about this article? Write it in the comments below 👇

Categories
Uncategorized

How to create an onboarding screen in SwiftUI #2 – Implementing the Page Control

Hello and welcome to this tutorial! In this post, we will learn how to create an onboarding screen in SwiftUI. By doing this we learn a lot about interfacing with UIKit in SwiftUI. In the last part, we started creating our onboarding screen by implementing an UIViewController with feeding it with SwiftUI views. In this part, we will create the rest of our UI, including the “Next” button, the texts and the dotted page indicator. By doing this we will learn how to create a Binding between a UIKit object and a SwiftUI view. We also learn how to insert UIViews into SwiftUI.

Here’s what we are going to achieve:

Where we are 🏛

As a little reminder, here’s the architecture we use for creating our onboarding screen.

The overall OnboardingView contains a UIViewController, better said a UIPageViewController, which itself holds several subviews

The overall OnboardingView contains a UIViewController, better said a UIPageViewController, which itself holds several subviews

We already implemented the UIPageViewController including the SwiftUI subviews and enabled the swipe functionality.

What’s left is to create a “Next” button for manually going to the next subview of the PageViewController. In addition to that, we want to create two text sections for displaying information depending on the currently shown subview. Eventually, we want to include a Page Control for indicating the user, which page is currently being shown.

Keeping track of the current page 👁

To update the PageViewController when we click on the “Next” button (which we will implement in a moment), we need to keep track of which page of the PageViewController should be currently displayed.

To do this, let’s create a State property which serves as the “source of truth”, meaning that the PageViewController should get updated according to the value of this State.

So let’s insert a currentPageIndex State inside the OnboardingView. Since when the app launches the first page should be presented, we set the initial value to 0:

struct OnboardingView: View {
   //...
    
   @State var currentPageIndex = 0
   var body: some View {
      //...
    }
}

Depending on this State, the Text objects and the PageViewController should get updated. We’ll see how to accomplish this in a moment.

But first, let’s implement a Button for changing the State’s value when the user taps on it!

Implementing the Button and the Text objects 👷‍♂️

Wrap the PageViewController instance inside the OnboardingView into a VStack and insert a Button object.

var body: some View {
        VStack {
PageViewController(viewControllers: subviews)
                .frame(height: 600)
Button() {
            }
        }
    }

The button should contain a white arrow with a yellow, round background:

Button() {
    Image(systemName: "arrow.right")
                    .resizable()
                    .foregroundColor(.white)
                    .frame(width: 30, height: 30)
                    .padding()
                    .background(Color.orange)
                    .cornerRadius(30)
            }

If you want, you can outsource the button’s content as an external view like this:

struct OnboardingView: View {
//...
    
var body: some View {
        VStack {
PageViewController(viewControllers: subviews)
                .frame(height: 600)
Button() {
ButtonContent()
            }
        }
    }
}
struct ButtonContent: View {
var body: some View {
Image(systemName: "arrow.right")
        .resizable()
        .foregroundColor(.white)
        .frame(width: 30, height: 30)
        .padding()
        .background(Color.orange)
        .cornerRadius(30)
    }
}

As said, the currentPageIndex State needs to be updated when the user taps the button. Therefore, let’s write an action that adds one to the State’s value. But be aware that we want to display the first page again when the user reached the last page and taps the button. We can take this into account by writing a suitable if-else statement:

Button(action: {
if self.currentPageIndex+1 == self.subviews.count {
self.currentPageIndex = 0
                } else {
self.currentPageIndex += 1
                }
            }) {
ButtonContent()
            }

To see if this works properly, let’s also insert a (temporary) Text object for showing the current value of the currentPageIndex.

VStack {
PageViewController(viewControllers: subviews)
                .frame(height: 600)
Button(action: {
if self.currentPageIndex+1 == self.subviews.count {
self.currentPageIndex = 0
                } else {
self.currentPageIndex += 1
                }
            }) {
ButtonContent()
            }
Text("Currently shown page: \(currentPageIndex)")
        }

We can now try out this functionality by running a live preview and tapping on the button. You see, that every time we tap on it, the currentPageIndex State gets updated which triggers the whole view to rerender with updating our Text.

But instead of this temporary Text, we want to display two Text blocks for displaying information depending on the currently shown subview.

So let’s insert two arrays, one for the titles and one for the captions.


struct OnboardingView: View {
//...
    
var titles = ["Take some time out", "Conquer personal hindrances", "Create a peaceful mind"]
var captions =  ["Take your time out and bring awareness into your everyday life", "Meditating helps you dealing with anxiety and other psychic problems", "Regular medidation sessions creates a peaceful inner mind"]
@State var currentPageIndex = 0
var body: some View {
//...
    }
}

Next, insert two Text objects into your OnboardingView, each of it displaying a String of the arrays subscript by the currentPageIndex value. At this point, you can delete the temporary “Current Page” Text object.

VStack {
PageViewController(viewControllers: subviews)
                .frame(height: 600)
Text(titles[currentPageIndex])
                .font(.title)
Text(captions[currentPageIndex])
                .font(.subheadline)
                .foregroundColor(.gray)
                .frame(width: 300, height: 50, alignment: .leading)
                .lineLimit(nil)
//...
        }

Your preview should now look as follows:

If you want, you can run the app again and see if the Text’s get updated properly when you tap the button. As you probably already noticed, the PageViewController isn’t updating it’s displayed subview yet. Let’s change this by binding it to our currentPageIndex State!

Bind the PageViewController to the currentPageIndex State

Binding the PageViewController to the currentPageIndex State means that when the State gets updated the PageViewController gets updated too and that when the Binding gets updated the State gets updated. Doing this is pretty straight forward.

Just insert a @Binding property named currentPageIndex into the PageViewController.

struct PageViewController: UIViewControllerRepresentable {
    
    @Binding var currentPageIndex: Int
    
    //...
    
}

Since we can now refer to the currentPageIndex, we can update the updateUIViewController function for showing the subview depending on the currentPageIndex State’s value, which we’ve set to an initial value of 0.

func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [viewControllers[currentPageIndex]], direction: .forward, animated: true)
    }

Now we have to update the initialisation of the PageViewController inside the OnboardingView with binding it to the currentPageIndex State of the OnboardingView:

var body: some View {
        VStack {
            PageViewController(currentPageIndex: $currentPageIndex, viewControllers: subviews)
                .frame(height: 600)
            //...
        }
    }

Let’s run our app again!

Now every time, we tap the “Next” button, the State gets updated which also updates the currentPageIndex Binding. What then happens is that the PageViewController gets updated which triggers the updateUIViewController function and eventually shows the next subview!

But now try to swipe..you notice that the Text’s don’t get updated! That’s because we’ve not yet told the PageViewController to update the currentPageIndex when the user swipes. Let’s change this!

Update the State when the user swipes 🆕

Updating the currentPageIndex can be done with a delegate pattern. The appropriate method to use is the didFinishAnimating function. Since, as you learned in the last part, the right place to insert delegate methods is the Coordinator subclass, let us conform this class to the UIPageViewControllerDelegate protocol and insert the didFinishAnimating method there:

class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        
        //...
        
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
                let visibleViewController = pageViewController.viewControllers?.first,
                let index = parent.viewControllers.firstIndex(of: visibleViewController)
            {
                parent.currentPageIndex = index
            }
        }
        
    }

The method gets called when the swiping of the user is finished. When that’s the case, we then grab the shown subview and locate its position inside the viewControllers array. Depending on that value, we can then update the currentPageIndex binding which then also updates the currentPageIndex State of the OnboardingView.

Similar as we did with the data source pattern in the last tutorial, let’s assign the Coordinator as the PageViewController’s delegate inside the makeUIViewController function.

func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator
        
        return pageViewController
    }

Okay, let’s run our app again and see if that works. Great, every time we swipe, the currentPageIndex State of the OnboardingView gets updated trough the updated Binding property of the PageViewController which eventually updates the Text objects of our OnboardingView accordingly!

Inserting an UIPageControl and updating the view’s composition 🖌

Last but not least, we want to have a dotted indicator for showing the user how many pages the PageViewController has and which one is currently being displayed.

Such an indicator is part of the UIKit framework and called UIPageControl, which is an UIView. In the last tutorial, we saw how to insert an UIViewController. Inserting UIViews into SwiftUI views is very similar to do this.

Create a new Swift file called PageControl.swift and make sure the UIKit and SwiftUI frameworks are imported. Then create a struct called PageControl and conform it to the UIViewRepresentable protocol:

import Foundation
import UIKit
import SwiftUI

struct PageControl: UIViewRepresentable {
    
}

Our page control needs to know how many pages the PageViewController has. It also needs to know which page is currently being displayed. So let’s create a property numberOfPages and create a Binding to the currentPageIndex State of the OnboardingView.

struct PageControl: UIViewRepresentable {
    
    var numberOfPages: Int
    
    @Binding var currentPageIndex: Int
    
}

Similar to the UIViewControllerRepresentable protocol, the UIViewRepresentable protocol has two mandatory functions which need to be implemented. The makeUIView and the updateUIView function. The makeUIView function is used for initialising the UIView for the first time. The updateUIView function gets called every time the UIView gets updated.

Let’s start with creating the makeUIView function (make sure you indicate to return an UIPageControl object after the arrow):

 func makeUIView(context: Context) -> UIPageControl {
        
    }

Inside this function, we create an UIPageControl. We set the number of dots by using the built-in numberOfPages property and assigning it to the numberOfPages property we declared above. We then modify the color of the PageControl and return it.

 func makeUIView(context: Context) -> UIPageControl {
        let control = UIPageControl()
        control.numberOfPages = numberOfPages
        control.currentPageIndicatorTintColor = UIColor.orange
        control.pageIndicatorTintColor = UIColor.gray

        return control
    }

We then implement the updateUIView function. Every time the currentPageIndex State of the OnboardingView gets updated the currentPageIndex Binding of the PageControl gets too. This then triggers the updateUIView function. So let’s use this function to tell the PageControl which dot of the PageControl should be highlighted when the user goes to another page.

struct PageControl: UIViewRepresentable {
    
    //...
    
    func updateUIView(_ uiView: UIPageControl, context: Context) {
        uiView.currentPage = currentPageIndex
    }
    
}

Now we just need to initialise a PageControl instance inside our OnboardingView with passing the number of pages and binding it to the currentPageIndex State:

var body: some View {
        VStack {
            //...
            PageControl(numberOfPages: subviews.count, currentPageIndex: $currentPageIndex)
        }
    }

This is a great opportunity for reordering the elements of our UIView and adding some paddings to make it look more nicely. If you want, you can just copy and paste the code below.


var body: some View {
        VStack(alignment: .leading) {
            PageViewController(currentPageIndex: $currentPageIndex, viewControllers: subviews)
                .frame(height: 600)
            Group {
                Text(titles[currentPageIndex])
                    .font(.title)
                Text(captions[currentPageIndex])
                .font(.subheadline)
                .foregroundColor(.gray)
                .frame(width: 300, height: 50, alignment: .leading)
                .lineLimit(nil)
            }
                .padding()
            HStack {
                PageControl(numberOfPages: subviews.count, currentPageIndex: $currentPageIndex)
                Spacer()
                Button(action: {
                    if self.currentPageIndex+1 == self.subviews.count {
                        self.currentPageIndex = 0
                    } else {
                        self.currentPageIndex += 1
                    }
                }) {
                    ButtonContent()
                }
            }
                .padding()
        }
    }

Awesome! Let’s run our app and try everything out!


You can download the whole source code from GitHub.

Conclusion 🎊

That’s it! We’ve successfully created our own onboarding screen in SwiftUI. We learned a lot about interfacing with UIKit and experienced the great power of this synergy. We learned how to implement UIViewControllers and UIViews into SwiftUI views and saw how to embed SwiftUI views into UIKit objects!

If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Have any questions about this article? Write it in the comments below 👇

Categories
Uncategorized

How to create an onboarding screen in SwiftUI #1 – Embedding a UIPageViewController

Hello and welcome to this tutorial! In this post, we will learn how to create an onboarding screen in SwiftUI. By doing this we learn a lot about interfacing with UIKit in SwiftUI. In this part, we will create our onboarding screen by implementing an UIViewController with feeding it with SwiftUI views. By doing this, we will learn how to integrate UIViewControllers into SwiftUI and how to transform SwiftUI Views into UIViewControllers!

Here’s what we are going to achieve:


To get started, create a new Xcode project called “Onboarding” and store it wherever you want. Make sure “Use SwiftUI” is selected. After creating the project, import the images into your Assets.xassets folder and make sure you name them correctly.

Our app’s architecture 🏛

Before diving into the coding part, let’s discuss our onboarding app’s architecture.

It simply consists of one single SwiftUI Content View, called OnboardingView. This content view contains two SwiftUI Text objects and an “arrow” Button. But it also contains a UIPageViewController (which is part of the UIKit), which itself contains three different subviews. The subviews are plain SwiftUI views, but which need to be transformed into UIViewControllers because the embedded UIPageViewControllers only accepts UIViewControllers. In addition to that, the OnboardingView contains an UIPageControl to indicate the current page that’s displayed, which is also part of the UIKit framework.

The overall OnboardingView contains a UIViewController, better said a UIPageViewController, which itself holds several subviews

The overall OnboardingView contains a UIViewController, better said a UIPageViewController, which itself holds several subviews

For creating this SwiftUI-UIKit synergy based app, it’s best practice to go from detail to general, meaning that we start with creating our subviews, which we then use to feed our UIPageViewController.

Creating the subviews 🖌

As you saw in the graphic above, the subviews are of a very simple composition. They only consist of one overall image. We could create one single SwiftUI view for every subview but in this example, it’s more efficient to compose a single one and initialise it multiple times with different images.

So let’s create a File-New-File, select SwiftUI view and click on “Next”, name it Subview and then click on “Create”.

As said, we want to dynamically display different images when initialising this view later on. So let’s declare a variable imageString for this purpose.

import SwiftUI

struct Subview: View {
    var imageString: String
    var body: some View {
        //...
    }
}

Next, insert an Image object with passing the imageString variable. To properly frame the image, also add some modifiers.

var body: some View {
        Image(imageString)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .clipped()
    }

Now we need to update our previews struct for showing a sample image.

struct Subview_Previews: PreviewProvider {
    static var previews: some View {
        Subview(imageString: "meditating")
    }
}

This is how your subview should look like now:

That’s all! Theoretically, we could now use this view for creating our subviews.

But as said, we can’t directly use SwiftUI views for feeding a UIPageViewController. Instead, we first need to transform them into UIViewControllers.

Transforming SwiftUI Views into UIViewControllers 🔄

But first, we have to create our onboarding view, especially for holding our UIPageViewController, as you saw in the App’s hierarchy section. To do this, create a new SwiftUI file named OnboardingView.

As mentioned, the OnboardingView will be the place to embed our UIPageViewController in.

Because of this, it’s also the right place to initialise our subviews for passing them to the UIPageViewController (which we will create in a moment). So let’s create an array for storing our subviews:

struct OnboardingView: View {
    
    var subviews = [
        
    ]
    
    var body: some View {
        //...
    }
}

Because our UIPageViewcontroller will only accept UIViewControllers we have to transform our subviews into UIViewControllers. We can simply do that by declaring an UIHostingController for every subview respectively. UIHostingControllers are used for wrapping SwiftUI views in order to use them within the UIKit framework.

struct OnboardingView: View {
    
    var subviews = [
        UIHostingController(rootView: Subview(imageString: "meditating")),
        UIHostingController(rootView: Subview(imageString: "skydiving")),
        UIHostingController(rootView: Subview(imageString: "sitting"))
    ]
    
    var body: some View {
        //...
    }
}

At this point, you can delete the default ContentView.swift file since we won’t need it anymore. But then we have to set our OnboardingView as the root view when the app launches. We do this within the scene function inside the scenedelegate.swift file.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: OnboardingView())
            self.window = window
            window.makeKeyAndVisible()
        }
    }

Great, we successfully prepared our subviews for feeding our UIPageViewController, which we will create now!

Creating the UIPageViewController 🚀

So we just learned how to convert SwiftUI views into UIViewControllers using an UIHostingController. But how can we embed a ViewController of the UIKit framework, like a PageViewController is one, into our SwiftUI OnboardingView?

First of all, let’s create a Swift file called PageViewController. Make sure you import the SwiftUI and UIKit framework. Then create a struct called PageViewController.

import Foundation
import UIKit
import SwiftUI

struct PageViewController {
    
}

We want to work with our subviews in this file. Therefore, declare an appropriate array for holding them, which will get “filled” with the UIHostingControllers when initialising the PageViewController inside the OnboardingView.

struct PageViewController {
    
    var viewControllers: [UIViewController]
    
}

In this file, we will create our UIPageViewController for embedding it into the OnboardingView. For being able to do that, we must conform it to the UIViewControllerRepresentable protocol.

struct PageViewController: UIViewControllerRepresentable {
    
    //...
    
}

The UIViewControllerRepresentable protocol has three mandatory methods:

  • makeUIViewController: This method is used for creating the UIViewController we want to present.
  • updateUIViewController: This method updates the UIViewController to the latest configuration every time it gets called .
  • makeCoordinator: This method initialises a Coordinator which serves as a kind of a servant for handling delegate and datasource patterns and user inputs. We will talk about this in more detail later.

Let’s start by creating our UIPageViewController by using the makeUIViewController method. Because we want to return a UIPageViewController we have to indicate this behind the arrow like that:

func makeUIViewController(context: Context) -> UIPageViewController {
        
    }

Let’s create the UIPageViewController and return it:

func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)

        return pageViewController
    }

The updateUIViewController will get called every time the UIPageViewController gets updated, for example when the number of content views of it are changing. Because we use a static number of view controllers for our PageViewController, this function will only get called once, namely when the UIPageViewController is being rendered first. When this happens, we have to configure our PageViewController with setting the first view controller of the viewControllers array to display first. We do this by using the setViewController method.

func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [viewControllers[0]], direction: .forward, animated: true)
    }

At this point, our PageViewController is ready to be embedded! We can now insert it into our OnboardingView by passing our Subviews. Let’s also add a frame to it.

struct OnboardingView: View {
    
    //...
    
    var body: some View {
        PageViewController(viewControllers: subviews)
            .frame(height: 600)
    }
}

Hint: We will implement the Text objects, the buttons and the Page Control, in the next part of this tutorial.

Great, we successfully implemented an UIPageViewController into a SwiftUI view! But when running the app in live mode, you notice that we are currently not able to swipe back and forth. This is because we didn’t yet implement a datasource pattern for telling our Page View Controller what subview to display when the user swipes back or forth.

Let’s change this by implementing a Coordinator!

Implementing the Coordinator 👷‍♂️

A Coordinator is implemented by creating a subclass inside the PageViewController, which we can then use for implementing the common datasource functions we need for telling our PageViewController which subview to display when the user swipes.

The Coordinator is setup by inserting a Coordinator subclass into the PageViewController with having a parent variable (of the superclass type, in our case that’s PageViewController) and a init function which we need for initialising the Coordinator in our PageViewController.

struct PageViewController: UIViewControllerRepresentable {
    
    //...
    
    class Coordinator: NSObject {
        
        var parent: PageViewController

        init(_ pageViewController: PageViewController) {
            self.parent = pageViewController
        }
        
    }
    
}

We can now initialise the Coordinator by calling the makeCoordinator method of the UIViewControllerRepresentable protocol.

struct PageViewController: UIViewControllerRepresentable {
    
    //...
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIPageViewController {
        //...
    }
    
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        //...
    }
    
    class Coordinator: NSObject {
        
        //...
        
    }
    
}

As said we can use our Coordinator for implementing common Cocoa patterns, like delegates, data source and responding to user input. We want our Coordinator to act as the data source for our UIPageViewController. So let’s conform the Coordinator Subclass to the UIPageViewControllerDataSource protocol.

struct PageViewController: UIViewControllerRepresentable {

//...
    
class Coordinator: NSObject, UIPageViewControllerDataSource {

}

This protocol requires one function for returning the right ViewController after the currently displayed ViewController in our viewControllers array and one for returning the right ViewController before the currently displayed ViewController of our viewControllers array.

So let’s add the viewControllerBefore and viewControllerAfter methods for telling our PageViewController which view controller of the viewControllers array to display when the user swipes.

class Coordinator: NSObject, UIPageViewControllerDataSource {
        
        //...
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            //retrieves the index of the currently displayed view controller
            guard let index = parent.viewControllers.firstIndex(of: viewController) else {
                 return nil
             }
            
            //shows the last view controller when the user swipes back from the first view controller
            if index == 0 {
                return parent.viewControllers.last
            }
            
            //show the view controller before the currently displayed view controller
            return parent.viewControllers[index - 1]
            
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            //retrieves the index of the currently displayed view controller
            guard let index = parent.viewControllers.firstIndex(of: viewController) else {
                return nil
            }
            //shows the first view controller when the user swipes further from the last view controller
            if index + 1 == parent.viewControllers.count {
                return parent.viewControllers.first
            }
            //show the view controller after the currently displayed view controller
            return parent.viewControllers[index + 1]
        }
    }

Since our Coordinator subclass now conforms to the UIPageViewControllerDataSource protocol, we can assign it as the data source of our PageViewControllers inside our makeViewController method.

func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        
        pageViewController.dataSource = context.coordinator
        
        return pageViewController
    }

Great! Now every time the user swipes to a new subview, the Coordinator’s viewControllerAfter/viewControllerBefore method gets called and tells the PageViewController to display the previous or next view controller!

We can check if that’s works by running our app/the OnboardingView preview in live mode and swiping. You see, that every time we swipe, the correct subview get displayed to us!

This is what we’ve accomplished so far:


You can download the source code from GitHub (Part1/App Onboarding folder)

Conclusion 🎊

Great, we just learned how to use a UIPageViewController in SwiftUI! Beside that, we also learned how to feed this controller with SwiftUI views by using UIHostingControllers. In the next article, we will finish our app onboarding, by adding the Texts, setting up the dotted page indicator and implementing a button which the user can use for going to the next view of the PageViewController.

If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Have any questions about this article? Write it in the comments below 👇

Categories
Uncategorized

How to build a login page in SwiftUI #2 – Outsource bindings and implement State-depending views

This is the second of two parts of our tutorial on how to build a login page just with SwiftUI, for the first one click here.

Welcome to this two-part tutorial series on how to create a full-functional login page by just using SwiftUI. In the last part, we talked about implementing Text Views and learn the basics concept of data flow in SwiftUI by using State properties. In this part, we will extend this knowledge by learning how to create a binding between different views and how to show views dynamically.

Here is what we are going to create:

What you will learn 💡

What you will learn in this part of the tutorial:

  • How to outsource views that are bound to State properties
  • How to represent several views depending on the current State
  • How to move up the view’s content when the keyboard is toggled

How to outsource views depending on a @State property

As said in the last part of this tutorial, outsourcing views that are bound to a State, like our Text Fields are, is a little bit special. Let’s try out to outsource our username Text Field by CMD-clicking on it and selecting Extract subview. Lets call it UsernameTextField.

We get an error saying “Use of unresolved identifier ‘$username'”. This is because when outsourcing a view, it gets wrapped into its own struct. But the UsernameTextField struct doesn’t own such a property which can get bind to the TextField object. To fix this, we need to insert an appropriate property into our outsourced UsernameTextField struct.

But: Instead of creating a State property on its own we want to instead have a property that derives its content from the username State of our ContentView in order to enable data flow between them! To do this, we declare a @Binding property that’s also called username above our UsernameTextField’s body and since we want to derive its content from the username State of the ContentView we don’t assign any data to it. Bindings are used for creating a data connection between a view (here: our outsourced UsernameTextField) and its source of data (here: our username State)

“Use a binding to create a two-way connection between a view and its underlying model.”

Apple
struct UsernameTextField : View {
@Binding var username: String
var body: some View {
return TextField("Username", text: $username)
            .padding()
            .background(lightGreyColor)
            .cornerRadius(5.0)
            .padding(.bottom, 20)
    }
}

To create the “link” between our ContentView and our UsernameTextField we initialise the latter by passing the username State of our UsernameTextField into it! As we did in the last part of this tutorial, we use the dollar-sign syntax again for binding objects.

struct ContentView : View {
@State var username: String = ""
@State var password: String = ""
var body: some View {
        VStack {
WelcomeText()
UserImage()
UsernameTextField(username: $username)
SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
Button(action: {print("Button tapped")}) {
LoginButtonContent()
            }
        }
        .padding()
    }
}

Now our outsourced UsernameTextField is connected to the username State of our ContentView!

What happens now when the user enters something is that the entered character gets passed from the UsernameTextField to the username property of the outsourced view which serves as a “bridge” and passes the data itself through the binding to the username State of the ContentView.

As learned in the last part the whole view gets then rerendered. The TextField object reads the content of the username State of the ContentView via accessing the username Binding of the UsernameTextField view and using it as a “data bridge”.

Let’s repeat this process for the Secure Field!

1. Outsource the Secure Field by CMD-clicking on it and selecting extract subview. Let’s call it PasswordSecureField.

2. Insert a Binding property without any content into the extracted PasswordSecureField.

struct PasswordSecureField : View {
    
    @Binding var password: String
    
    var body: some View {
        return SecureField("Password", text: $password)
            .padding()
            .background(lightGreyColor)
            .cornerRadius(5.0)
            .padding(.bottom, 20)
    }
}

3. Link the outsourced PasswordSecureField to the State of the ContentView by initialising it inside the ContentView with binding the password State to it.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            UsernameTextField(username: $username)
            PasswordSecureField(password: $password)
            Button(action: {print("Button tapped")}) {
               LoginButtonContent()
            }
        }
        .padding()
    }
}

Great, we finished outsourcing our Text and Secure Field! Let’s go further with designing our view for a failed and successful authentication!

Designing the view for failed and successful user authentication ✍️↔️

A basic approach in SwiftUI is to set up the interface individually for every relevant situation that can occur during the app’s runtime. In our example there are three cases that can occur:

  • Case 1: The user did nothing but entering it credentials into the Text Fields. Especially he didn’t start the authentication process by clicking on the login button. In this situation we can use the default view we created as it is and don’t need to modify anything.
  • Case 2: The user entered the authentication process by tapping the login button and it failed. In this case, we want to display a text saying that the entered information was wrong.
  • Case 3: The user entered the authentication process by tapping the login button and it succeeded. In this case, we want to display a text saying the login was successful.

How our view should look for Case 1, Case 2 and Case 3

Handling the second and the third case is done by using State properties as well!

Let’s start by handling the second case, meaning that the authentication process did fail. For this purpose, we declare a State property of a boolean type and set its default value to false.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    @State var authenticationDidFail: Bool = false
    
    var body: some View {
        
        //[...]
      
    }
}

When the State’s value becomes true that means that the authentication did fail. If this is the case we want to insert a Text object between the password Secure Field and the login Button. Therefore we write:

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    @State var authenticationDidFail: Bool = false
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            UsernameTextField(username: $username)
            PasswordSecureField(password: $password)
            if authenticationDidFail {
                Text("Information not correct. Try again.")
            }
            Button(action: {print("Button tapped")}) {
               LoginButtonContent()
            }
        }
        .padding()
    }
}

Let’s take a look at the Preview Simulator: Where is our created Text? Answer: Because we set the value of the authenticationDidFail State to true, we currently don’t simulate the case that the authentication did fail. Let’s change this:

struct ContentView : View {
    
    //[...]
    
    @State var authenticationDidFail: Bool = true
    
    var body: some View {
        
        //[...]
        
    }
}

Now we are simulating a failed authentication and therefore see our inserted Text object! Let’s give it a red color and move it a little bit up:

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        VStack {
            //[...]
            if authenticationDidFail {
                Text("Information not correct. Try again.")
                    .offset(y: -10)
                    .foregroundColor(.red)
            }
            //[...]
    }
}

This is how your Preview Simulator should look like now:

Perfect, we styled our view for a failed authentication (our Case 2). Let’s set the authenticationDidFail State to false again to simulate our default view (Case 1).

Let’s repeat this workflow for a successful authentication (Case 3). Again, we need to declare a State property for handling this case. Let’s set it to true to simulate how the view should look if the login process succeeds:

struct ContentView : View {
    
    //[...]
    
    @State var authenticationDidFail: Bool = false
    @State var authenticationDidSucceed: Bool = true
    
    var body: some View {
        
        //[...]
        
    }
}

If it succeeds, we want a Text to pop out saying that the login succeeded! But we don’t want this text to be within the VStack but on top of it! How do we achieve this?

Fortunately, it’s really straight forward. Just wrap the VStack itself into a ZStack. All objects inside a ZStack gets stacked on top of each other.

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        ZStack {
            VStack {
                WelcomeText()
                UserImage()
                UsernameTextField(username: $username)
                PasswordSecureField(password: $password)
                if authenticationDidFail {
                    Text("Information not correct. Try again.")
                        .offset(y: -10)
                        .foregroundColor(.red)
                }
                Button(action: {print("Button tapped")}) {
                    LoginButtonContent()
                }
                }
                .padding()
        }
    }
}

All objects inside a ZStack get stacked on top of each other.

“ZStack: A view that overlays its children, aligning them in both axes.”

Apple

So let’s add a Text object saying “Login succeeded” to the ZStack by inserting it below the VStack.

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        ZStack {
            VStack {
                //[...]
            if authenticationDidSucceed {
                Text("Login succeeded!")
            }
        }
    }
}

You now see that the Text is positioned on top of our VStack.

Let’s modify this Text by styling the font and adding a green background. Also add a .animation modifier with a default animation. This modifier causes the Text to pop up with a decent animation as we will see later.

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        ZStack {
            VStack {
                //[...]
            if authenticationDidSucceed {
                Text("Login succeeded!")
                    .font(.headline)
                    .frame(width: 250, height: 80)
                    .background(Color.green)
                    .cornerRadius(20.0)
                    .foregroundColor(.white)
                    .animation(Animation.default)
            }
        }
    }
}

This is what your preview should look like now:

We are done with handling our third case, so lets set the authenticationDidSucceed State to false again for simulating our default case!

struct ContentView : View {
    
    //[...]
    
    @State var authenticationDidFail: Bool = false
    @State var authenticationDidSucceed: Bool = false
    
    var body: some View {
        
        //[...]
        
    }
}

Great! We are done view designing our views that should depend on which State is currently “active”. Let’s hop into implementing our code logic for toggling these States appropriately.

Implementing the logic for toggling the States 🤓

So we just defined how our view should look depending on the different States. But how do we toggle these States?

Pretty simple: When the user taps the login button, we need to compare the entered username and password with the stored credentials.

We do not have stored the correct password and username yet. Let’s change this by inserting them below our lightGreyColor constant

import SwiftUI

let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)

let storedUsername = "Myusername"
let storedPassword = "Mypassword"

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        //[...]
    }
}

If the entered information is correct we want to set the authenticationDidSucceed property to true which then causes the view to rerender with showing the “Login succeeded” Text. Let’s write this into the action parameter of our button:

struct ContentView : View {
    
    //[...]
    
    var body: some View {
        
        ZStack {
            VStack {
                //[...]
                Button(action: {
                    if self.username == storedUsername && self.password == storedPassword {
                        self.authenticationDidSucceed = true
                    }
                }) {
                    LoginButtonContent()
                }
                }
                .padding()
            //[...]
        }
    }
}

If the entered information is not the same as the stored, we want to instead set the authenticationDidFail State to true!

struct ContentView : View {
//[...]
    
var body: some View {
        ZStack {
            VStack {
//[...]
                Button(action: {
if self.username == storedUsername && self.password == storedPassword {
                        self.authenticationDidSucceed = true
                    } else {
                        self.authenticationDidFail = true
                    }
                }) {
LoginButtonContent()
                }
                }
                .padding()
//[...]
        }
    }
}

That’s all! Let’s try it out. Run your app in the “normal simulator”. Try to enter the wrong credentials and hit login! What happens now, is that the code inside the action checks if the credentials are correct. Because that is not the case, it toggles the authenticationDidFail State to be true. The view, therefore, rerenders with showing the “Information not correct” Text.

If you enter the right credentials, the authenticationDidSucceed State gets toggled which causes the view to show up the “Login succeeded” Text! Note how the Text pops out with a smooth animation. This is the one we defined earlier by applying the .animation modifier to the Text object.

Maybe you noticed, that when first entering the wrong and then the right information we see both: The disclaimer that the entered information was wrong and the “Login succeeded” Text.

But we don’t want to see both, instead, we want the disclaimer Text to disappear when the login succeeded. We can simply implement this by updating our action:

struct ContentView : View {
//[...]
    
var body: some View {
        ZStack {
            VStack {
//[...]
                Button(action: {
if self.username == storedUsername && self.password == storedPassword {
                        self.authenticationDidSucceed = true
                        self.authenticationDidFail = false
                    } else {
                        self.authenticationDidFail = true
                    }
                }) {
LoginButtonContent()
                }
                }
                .padding()
//[...]
        }
    }
}

This makes sure that the authenticationDidFail State is false when the credentials are correct which guarantees that the disclaimer text is not shown when the successful login text is.

Lets rerun our app, write down wrong credentials and tap the login button. After seeing the “Information not correct” Text enter the right credentials and hit the login button again. See how the “Information not correct” Text disappears and how the “Login succeeded” Text shows up instead!

Prevent the keyboard from hiding the Text View

There is one last thing we have to talk about in order to provide a good user experience: When the user taps on a Text Field and enters his password or username, the toggled keyboard hides the Text Field. This causes the user to not be able to see what he enters, which can be pretty annoying.

We outsourced this topic! To learn how to prevent the keyboard from hiding your view’s content, take a look at this tutorial.

Conclusion 🎊

That’s it! We are finished with creating our login page app in SwiftUI. You learned a lot stuff like using States, Binding and how data flow in SwiftUI works. How to manage outsourced views and data input and little tweaks like how to move content up when the user is writing something!

I uploaded the whole source code to GitHub if you want to check it out!

I hope you enjoyed this tutorial. Make sure you subscribe to our newsletter to not miss any updates, a lot of stuff about SwiftUI is coming. Also visit us on Facebook and Instagram. If you have any questions do not hesitate to contact us or leave a comment below!

Categories
Uncategorized

How to build a login page in SwiftUI #1 – Mastering Text Fields and understanding @State

This is the first of two parts of our tutorial on how to build a login page just with SwiftUI, here is the second one!

Welcome to this two-part tutorial series on how to create a full-functional login page by just using SwiftUI. In this part, we will compose a more complex UI and implement features such a Text Fields and Buttons and get to know the basic data flow concepts of SwiftUI.

Here is what we are going to create:

What you will learn 💡

What you will learn in this part of the tutorial:

  • How to compose a complex interface in SwiftUI
  • How to use Text Fields
  • How to use Secure Fields for more user data privacy
  • Understanding data flow in SwiftUI including @State properties
  • How to implement Buttons in SwiftUI

Project creation 🆕

Let’s start with creating a new project for our app. Open Xcode 11 and create a new Xcode project. Then select Single View App and click on Next. Give the project a suitable name and make sure “Use SwiftUI” is selected. Then click on Create.

We want to display the user’s profile image in the app, so let’s import a sample image into the Assets folder. Make sure you name the file appropriately.

We will also use a certain grey color multiple times. So before starting with creating the UI, copy the variable below and insert it above your ContentView struct inside your ContentView.swift file.

import SwiftUI

let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)

struct ContentView : View {
    var body: some View {
        Text("Hello World")
    }
}

Start creating the UI ✍️

Let’s start creating the interface of our login page! As you saw in the demo video, all elements are aligned vertically, so let’s delete the sample Text object and insert a VStack instead. In this stack, we put in all the objects we need for our UI. These objects automatically get stacked, you guessed it, vertically.

struct ContentView : View {
    var body: some View {
        VStack {
            
        }
    }
}

Let’s start with the “Welcome!” text at the top of our login page. For this, just insert a Text object containing a “Welcome!” string. We also apply some modifiers to it including a .padding modifier for extending the space between the Text and the Image below it which we will create in a moment.

struct ContentView : View {
    var body: some View {
        VStack {
            Text("Welcome!")
                .font(.largeTitle)
                .fontWeight(.semibold)
                .padding(.bottom, 20)
        }
    }
}

To outsource the created Text view, CMD-click on the Text object and select “Extract subview”. Let’s name this view WelcomeText.

Under your WelcomeText place an Image object. The Image should display the profile image of the user. To do this, enter the name of the image which we imported into the Asset folder earlier.

struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            Image("userImage")
        }
    }
}

We also add some modifiers for scaling the Image and making it round (Check this Instagram post for more details!). Again, we also want a bottom padding.

struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            Image("userImage")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .clipped()
                .cornerRadius(150)
                .padding(.bottom, 75)
        }
    }
}

We outsource this view too and call it UserImage.

This is how your preview should look now:

Understanding @State and mastering Text Fields 🤓

Now comes the first one of our two Text Fields. The Text fields enable the user to write down their credentials in order to log in. To create the first one, insert a TextField object into your ContentView’s body. For now, we need two parameters for our TextField: the text binding and the title parameter, which provides us with a placeholder when the TextField is empty.

import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            UserImage()
            TextField(title:, text:)
        }
    }

Creating a TextField without feeding the necessary parameters causes errors. We will get rid of these in a moment! But first of all, let’s talk about these parameters:

The second parameter accepts a binding. What does this mean?

In a nutshell, SwiftUI needs a property which it can use to store the information that the user enters. This property then processes the information and displays it again to the user.

Let’s create such a property by declaring a variable above our ContentView’s body called username which accepts a String. By default, i.e. when the user does not have anything entered, this variable should contain an empty String.

struct ContentView : View {

	var username: String = “"
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField(title:, text:)
        }
    }
}

But: Currently, this property is only able to read and store the information which an object such as the created TextField passes to it. But it must also be able to automatically render our view depending on its stored content. To do this, mark the variable as a @State.

struct ContentView : View {

	@State var username: String = “"
    var body: some View {
        
        VStack {
            //...
        }
    }
}

A @State property is connected to the view. A @State property is permanently being read by the view. That means that every time the @State property gets changed/updated, the view gets rerendered and eventually displays the content depending on the @State’s data.

To make this more clear, take a look at our TextField example. The TextField is connected to the username State property which contains an empty string as default. When the user enters a character, for example, the letter “A”, this character is passed to the @State property that is connected to the TextView. Because the State gets updated from an empty String to a String “A” it automatically triggers rerendering the view. The TextField then updates itself by reading out the content of the State property, which is now the string “A” and displays it to the User. This is how the user eventually sees what he has entered!

Every time the State gets updated, it causes the view to rerender. Every time the view (re)renders, the State’s content gets read

“State: A persistent value of a given type, through which a view reads and monitors the value.”

Apple

Back to our TextView. Since we created an appropriate State property, we can bind it to our TextField. To bind objects to a State property we have to insert the properties name with a dollar sign.

struct ContentView : View {

	@State var username: String = “"
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField(title: , text: $username)
        }
    }
}

When the TextField is empty (because the String of the State is empty, like it is at default) we don’t want the TextField to display nothing. Instead, we want to display a placeholder. We can do this by passing a Text object as the text argument. We can then delete the parameter’s name.

import SwiftUI

struct ContentView : View {
    
    @State var username: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
        }
    }
}

The TextField itself doesn’t have any border or background by default. To change this we have to add some modifiers to it. We also add a .padding modifier again for extending the space between this TextField and the one we will create in a moment.

struct ContentView : View {
    
    @State var username: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
        }
    }
}

Great, we are finished with setting up our first Text View! Outsourcing objects which are connected to a State is a little bit special. Thus, we will do this later.

Next, we want a similar TextField for the user’s password. We could use a “normal” TextField object again. But we want to provide our login form with a little bit of privacy by changing the fields content to dots when the user enters his password.

Doing this is really simple, instead of inserting a TextField object we insert an object called SecureField. The SecureField needs the same arguments as a TextField. Therefore we create another @State property called password and bind it to the SecureField. We also insert an appropriate placeholder. For styling the SecureField like the TextField, just copy & paste the modifiers of the TextField to the SecureField.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
        }
    }
}

Also, add an overall .padding modifier to the VStack itself for the fields not touching the edge of the screen.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
           //[…]        
		}
        .padding()
    }
}

This is what your preview should look like now:

Finish the UI by creating the Login button 🛠

Last but not least, let’s insert the Login Button. As described here, to create a button we start with creating the content inside the button. This should be a Text containing “LOGIN”. To modify the Text’s style and insert a green background apply the following modifiers to it.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
	    //[…]
            Text("LOGIN")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(Color.green)
                .cornerRadius(15.0)
        }
            .padding()
    }
}

Next, outsource this Text object and call it LoginButtonContent. Now we need to wrap the LoginButtonContent view into a Button. The button needs to know what to do when the user taps it. For now, we just want to print something out (for testing purposes). We will implement our authentication logic in the next part of this tutorial.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
			//[…]
            Button(action: {print("Button tapped")}) {
               LoginButtonContent()
            }
        }
        .padding()
    }
}

Conclusion 🎊

Let’s see where we are. To check out the full functionality, run the app in the “real” simulator, not the preview one. We can now use the text fields to type in the user’s credentials! Note how the password entered by the user gets invisible.

Hint: If you want to use the simulator keyboard and it doesn’t open automatically when clicking on the text fields, click on the simulator and choose on Hardware -> Keyboard -> Toggle Software Keyboard in the macOS toolbar. If you then click on a TextField, the keyboard should open automatically.


This is a great point to remember how the State data flow works: If you enter something it gets passed to the @State property. Because the State’s content gets updated, it automatically triggers rerendering the view. Then, the TextField reads out the State’s content and displays it to the user. This cycle gets executed every time the user enters something.

I uploaded to project’s progress to GitHub if you want to check it out!

Great, we accomplished a lot so far! We composed a complex UI containing a TextField, a SecureField and a Button. We also learned what states are and how the basics of data flow in SwiftUI!

In the next part we will implement our apps functionality including user authentication and displaying different views depending on whether the authentication was successful or not. We also apply some minor improvements like moving the content up when the keyboard opens.

I hope you enjoyed this tutorial. Make sure you subscribe to our newsletter to not miss any updates, a lot of stuff about SwiftUI is coming. Also visit us on Facebook and Instagram. If you have any questions do not hesitate to contact us or leave a comment below!

Categories
Uncategorized

SwiftUI – Mastering Table Views (Lists) #2

This is the second part of the Mastering Table Views (Lists) series, here is the first part!

Welcome to the second part of our Mastering Table Views (Lists) in SwiftUI series! In this series we will rebuild a Food Delivery App with Apple’s newest UI-Framework SwiftUI! We will get to know basic concepts of SwiftUI and learn how to apply them. If you don’t know what SwiftUI is and what you need in order to run it, have a look at this article.

Here is a video of the finished app we are going to build in this series.

In this part we will create a detailed view that contain the products of the category that is selected by the user. We will learn how to connect the detailed view with the main view and how to pass data through them.

Building our data model 🛠

Because we want to provide our detailed view with the several products, we have to create an according data model for handling that products.

Let’s create a new Swift file by clicking File – New – File and then selecting Swift File. Let’s call this file Food. Make sure the Foundation and SwiftUI Kit is imported. For handling our food products we create a class called Food inside this file.

import Foundation
import SwiftUI

class Food {

}

Each product should contain the product’s title and the price of the product. Therefore we create appropriate attributes.

class Food {

   let title: String
   let price: Double

}

Every Food instance should belong to one of the four categories: burger, pasta, pizza and dessert. To be able to select and distinguish between these four different options we have to create an enum.

“An enumeration is a group of values that are related […] The easiest way to think about enums is as structured lists of related items. A few examples: Colors: red, green, blue, purple, yellow, etc. Ice cream flavors: vanilla, chocolate, stracciatella, butter pecan, etc.”

learnappmaking.com

Let’s create a new file named Helper to place that enum in. We call the enum Categories and insert four different cases for our four categories.

enum Categories {
    case burger
    case pasta
    case pizza
    case dessert
}

Now we can switch back the our Food.swift file and insert an attribute category of the type Categories. Each instance created out of this class will now be assigned to a certain category.

class Food {
    
    let title: String
    let price: Double
    let category: Categories
    
}

Because we will use our Food data model to wrap multiple instances of it into a List later on, we need it to conform to the Identifiable protocol. This is required to pass custom class instances into Lists in SwiftUI (it’s also the reason why we imported the SwiftUI kit into our Food.swift file). The Identifiable protocol has only one mandatory requirement: It needs the class to contain an attribute (for the list) to identify every instance by a unique id. Therefore we simply declare an id attribute of the type Int.

class Food: Identifiable {
    
    let title: String
    let price: Double
    let category: Categories
    let id: Int
    
}

Our Food model now contains everything we need, so let’s add the according init function:

class Food: Identifiable {
    
    let title: String
    let price: Double
    let category: Categories
    let id: Int
    
    init(title: String, price: Double, category: Categories, id: Int) {
        self.title = title
        self.price = price
        self.category = category
        self.id = id
    }
    
}

Providing the product data ➡️

Now that we created our Food data model we are able to import our products which we will use to feed our detailed list.

We will use a separate file for this data, so create a new Swift file called FoodData.

Let’s use an array containing all the products we want for our app. Each product is a instance of the Food data model and must therefore be initialised with the according product title, price, category and id.

Feel free to copy and paste the array below into your FoodData.swift file. Of course you can edit this array to your wishes!


let foodData: [Food] = [
    Food(title: "Margherita", price: 5.99, category: .pizza, id: 1),
    Food(title: "Prosciutto", price: 6.89, category: .pizza, id: 2),
    Food(title: "Funghi", price: 6.99, category: .pizza, id: 3),
    Food(title: "Calzone", price: 6.99, category: .pizza, id: 4),
    Food(title: "BBQ Burger", price: 9.90, category: .burger, id: 5),
    Food(title: "Cheeseburger", price: 7.90, category: .burger, id: 6),
    Food(title: "Vegan Burger", price: 8.90, category: .burger, id: 7),
    Food(title: "Pulled Pork Burger", price: 11.90, category: .burger, id: 8),
    Food(title: "Spagetthi Bolognese", price: 8.90, category: .pasta, id: 9),
    Food(title: "Penne all'arrabbiata", price: 7.90, category: .pasta, id: 10),
    Food(title: "Aglio e olio", price: 7.90, category: .pasta, id: 11),
    Food(title: "Cheesecake", price: 3.99, category: .dessert, id: 12),
    Food(title: "Cupcake", price: 2.99, category: .dessert, id: 13),
    Food(title: "Icecream", price: 2.99, category: .dessert, id: 14)
]

Creating our food detail rows 🖌

Let’s create our UI for the detailed products view. We want to have a list again for this. As you learned in the last part of this tutorial, lists contain of rows with the data to display.

We first want to create the UI of these rows before we make a list out of them. Therefore we create a new file but now of the type SwiftUI View. Let’s call it DetailRow.swift.

Each DetailRow should contain three objects: A Text object containing the title of the product, another Text object containing the according price and a Button which notifies us when the user orders the product.

Let’s start with our first Text object. We can use the default Text object which currently contains “Hello World” for this, but change it to, for example, “BBQ Burger”. Don’t worry, we will make this dynamic by using our data model in a moment. But for now, let’s use static values for building up the UI.

Next, we want another Text object for the price of the product. The title Text and the price Text should be stacked vertically, so CMD-click on the Text object and select “Embed in VStack”. Now we can insert a second Text object containing the price of the food, for example, “10.00 $”. Both text object should be aligned on the “left side”, so we insert the .leading option for the alignment of the VStack.

struct DetailRow : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("BBQ Burger")
            Text("10.00 $")
        }
    }
}

We want our title text object to be of a more emphasised look. Therefore we apply the .font modifier with the .headline option to it. We do the same with the price Text object but select the .caption option. Additionally, we want a little padding for the title Text, so we add a .padding modifier for the text to be 10 points away from the upper bound.

struct DetailRow : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("BBQ Burger")
                .font(.headline)
                .padding(.top, 10)
            Text("10.00 $")
                .font(.caption)
        }
    }
}

Next, let’s create the “Order” Button. To do this, we have to embed the VStack into an HStack. CMD-click on the VStack and select “Embed in HStack”. Now we can insert another Text object next to the both Text objects.


struct DetailRow : View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Text("ORDER")
            }
    }
}

To make this new Text to be part of a Button, select it and click on “Embed in a Button”.

But let’s delete the onTrigger argument and instead insert the action parameter followed by curly braces manually. The action argument of a Button accepts a closure that contains what happens when the user taps on the Button. We want to be notified, therefore we write a appropriate print statement into the curly braces.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                }
            }
    }
}

We want to change the Button’s size, so we apply a .frame modifier to it and insert the width and height we want for the Button. We also want an orange background for it that should be rounded. Therefore we apply the .background and .cornerRadius modifier to it.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
    }
}

Last but not least, we want the Text inside the Button to be of a white font, so we insert a .foregroundColor modifier below the text object.

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
    }
}

To push both objects inside the HStack, the VStack containing the two Text objects and the Button, to the left and right edges of the screen we insert a Spacer between them. We also want all objects to be a little bit away from all edge, therefore we apply an overall .padding modifier

struct DetailRow: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("BBQ Burger")
                    .font(.headline)
                    .padding(.top, 10)
                    Text("10.00 $")
                        .font(.caption)
                }
            Spacer()
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
            .padding(20)
    }
}

Nice, we created the UI for our DetailRows. Now, let’s make the content of these rows dynamic!

Making the food details rows dynamic 🔄

Instead of static data, each row of our detailed food list should display the data of the certain Food instance. There, in our DetailRow class we have to declare a variable of the type Food above our body.

struct DetailRow: View {
    
    var food: Food
    
    var body: some View {
        //...
    }
}

This variable will be assigned to a Food instance for every row that will be initialised. In our RowView’s body we use the properties of the food variable for displaying the products title and price.

struct DetailRow : View {
    
    var food: Food
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(food.title)
                    .font(.headline)
                    .padding(.top, 10)
                    Text("\(food.price) $")
                        .font(.caption)
                }
            Spacer()
            Button(action: {print("Order received")}) {
                    Text("ORDER")
                        .foregroundColor(.white)
                }
                .frame(width: 80, height: 50)
                .background(Color.orange)
                .cornerRadius(10.0)
            }
            .padding()
    }
}

Note: Due to a bug in Xcode 11 beta, it’s currently not possible to round Double values to certain decimal places. I will update this as soon as there exist a solution!

Because we used a dynamic variable which is not yet assigned to a Food instance, we need to update our DetailRow_Previews struct and initialise a sample Food instance to provide the preview simulator with sample data to display. For this purpose we can simply choose the first object of our FoodData array.

Our UI should now look as follows:

Building our food detail list 🆕

Now that we created the UI for the rows displaying the products data, we can use these to build our view with the detailed list of the foods.

Let’s create a new SwiftUI file called DetailView and delete the default Text object. For now, this view should contain a list with all the objects inside the FoodData array. Let’s create such a List:

struct DetailView: View {
    
    var body: some View {
        List() {
            
        }
    }
    
}

The List needs the data it should contain as the input. This data must be distinguishable by an id. We did this by conforming the Food class to the identifiable protocol and assigning all instances inside the foodData array to unique id’s. Therefore we can simply insert the foodData array into our list.

struct DetailView: View {
    
    var body: some View {
        List(foodData) {
            
        }
    }
    
}

Inside the curly braces of the List object we have to insert a closure which determines what happens with all the objects in the foodData array. We want to initialise one row for every object in this array. Therefore we write:

struct DetailView : View {
    
    var body: some View {
        List(foodData) { food in
            DetailRow(food: food)
        }
    }
    
}

Great! Our preview now contains a list with our DetailRows for every object in our FoodData array!

Of course the DetailView should only display the foods of the category which the user selected. Thus, the list should only contain the objects of the foodData array which is of the currently selected category.

We therefore declare a variable of the Categories enum type which represents the category that the user selected on the main screen.

struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        //...
    }
}

To filter the data of the foodData array by the selected category we have to create a helper function. We put a function called filterData inside our Helper.swift file. This helper function takes the selected category as its input and returns a new array containing the filtered food data.

func filterData(by category: Categories) -> [Food] {

}

To filter the data we declare an empty array inside the functions body. What we now do is to cycle trough all objects of the foodData array and check for every element if it is of the same category as the input category. If this is true it adds the object to the filtered array. If our function cycled through element it eventually returns the filtered array to us.

func filterData(by category: Categories) -> [Food] {
    var filteredArray = [Food]()
    
    for food in foodData {
        if food.category == category {
            filteredArray.append(food)
        }
    }
    
    return filteredArray
}

Back to our DetailView: Instead of the whole foodData array we can now insert the filterData function into the lists which takes the currentCategory and then passes the filteredArray to the list.

We now have to update our DetailView_Previews struct again by determining a sample category.

struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        List(filterData(by: currentCategory)) { food in
            DetailRow(food: food)
        }
    }
}

#if DEBUG
struct DetailView_Previews : PreviewProvider {
    static var previews: some View {
        DetailView(currentCategory: .burger)
    }
}
#endif

Our preview should now look as follows:

Next, we have to connect the ContentView and the DetailView and pass the category selected by the user.

Connecting the views ⛓

To enable a connection from the ContentView to the DetailView we have to wrap the List of our ContentView containing the different CategoryViews into a so-called NavigationView.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                CategoryView(imageName: "burger", categoryName: "BURGER")
                CategoryView(imageName: "pizza", categoryName: "PIZZA")
                CategoryView(imageName: "pasta", categoryName: "PASTA")
                CategoryView(imageName: "cake", categoryName: "DESSERTS")
            }
        }
    }
}

If we stack views into a NavigationView, like we stacked the List with its CategoryViews into a NavigationView, they become part of a navigation hierarchy which enables routing to other views like our DetailView.

“NavigationView: A view for presenting a stack of views representing a visible path in a navigation hierarchy.”

Apple

To enable routing to the DetailView by clicking on a CategoryView we have to wrap these into NavigationButtons. Let’s start by wrapping the “pizza” CategoryView into a NavigationButton. The destination of this NavigationButton is the DetailView. Because we are currently handling the “burger” CategoryView we want to initialise the DetailView with the .burger case of our Categories enum.

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView(currentCategory: .burger)) {
                    CategoryView(imageName: "burger", categoryName: "BURGER")
                }
                CategoryView(imageName: "pizza", categoryName: "PIZZA")
                CategoryView(imageName: "pasta", categoryName: "PASTA")
                CategoryView(imageName: "cake", categoryName: "DESSERTS")
            }
        }
    }
}

Let’s repeat this process for the three remaining CategoryViews.


struct ContentView : View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView(currentCategory: .burger)) {
                    CategoryView(imageName: "burger", categoryName: "BURGER")
                }
                NavigationLink(destination: DetailView(currentCategory: .pizza)) {
                    CategoryView(imageName: "pizza", categoryName: "PIZZA")
                }
                NavigationLink(destination: DetailView(currentCategory: .pasta)) {
                    CategoryView(imageName: "pasta", categoryName: "PASTA")
                }
                NavigationLink(destination: DetailView(currentCategory: .dessert)) {
                    CategoryView(imageName: "cake", categoryName: "DESSERTS")
                }
            }
        }
    }
}

We can now try out the functionality of our app by running the preview simulator in live mode. To do this, click on the play button next to the simulator. Great! We can now click on the different categories and a detailed list with all products of the selected category gets presented to us!

Handling the Navigation Bars 👁

Currently the Navigation Bar in our ContentView is empty. To change this add a .navigationBarTitle modifier to the List (not to the Navigation View!). We can now insert a Text object containing “Food Delivery”


struct ContentView : View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView(currentCategory: .burger)) {
                    CategoryView(imageName: "burger", categoryName: "BURGER")
                }
                NavigationLink(destination: DetailView(currentCategory: .pizza)) {
                    CategoryView(imageName: "pizza", categoryName: "PIZZA")
                }
                NavigationLink(destination: DetailView(currentCategory: .pasta)) {
                    CategoryView(imageName: "pasta", categoryName: "PASTA")
                }
                NavigationLink(destination: DetailView(currentCategory: .dessert)) {
                    CategoryView(imageName: "cake", categoryName: "DESSERTS")
                }
            }
                .navigationBarTitle(Text("Food Delivery"))
        }
    }
}

We also want a Navigation Bar title for our DetailView. But it should not display a static text, but instead the name of category the user selected. To do this we need to write a function that returns a appropriate string to us depending on the currentCategory. Let’s insert this function into our Helper.swift file


func categoryString (for category: Categories) -> String {
    switch category {
    case .pizza:
        return "Pizza"
    case .burger:
        return "Burger"
    case .pasta:
        return "Pasta"
    case .dessert:
        return "Desserts"
    }
}

We can now add a .navigationBarTitle modifier to the List of our DetailView and insert our created method as the Text object’s input. To make to Navigation Bar to be small we can use the .inline option as the .displayMode.


struct DetailView : View {
    
    var currentCategory: Categories
    
    var body: some View {
        List(filterData(by: currentCategory)) { food in
            DetailRow(food: food)
        }
            .navigationBarTitle(Text(categoryString(for: currentCategory)), displayMode: .inline)
    }
}

You can find the full source code on GitHub!

Conclusion 🎊

Perfect, let’s run our app again, but this time in the “normal” simulator! Everything works fine: we can select each category and get a list of all of the products of that category.

We are now finished with creating our Food Delivery app in SwiftUI! We learned a lot stuff like creating lists with custom rows, using data models to feed our lists and how to transfer data between different views.

I hope you enjoyed this tutorial. Make sure you subscribe to our newsletter to not miss any updates, a lot of stuff about SwiftUI is coming. Also visit us on Facebook and Instagram. If you have any questions do not hesitate to contact us or leave a comment below!

Categories
Uncategorized

SwiftUI – Mastering Table Views (Lists) #1

This is the first part of the Mastering Table Views (Lists) series, if you are looking for the second one, click here!

Welcome to the first part of our Mastering Table Views (Lists) in SwiftUI series! In this tutorial we will rebuild our Food Delivery App, which we explained in our Table Views Guide, but this time with Apple’s newest UI-Framework SwiftUI! We will get to know the basic concepts of SwiftUI and learn how to apply them. If you don’t know what SwiftUI is and what you need in order to run it, have a look at this article.

Here is a video of the finished app we are going to build in this series:



Project creation and image import 🆕

Let’s start with creating a new project and importing the images we will need later on. Open Xcode 11 and create a new Xcode project. Then select Single View App and click on Next. Give the project a suitable name and make sure “Use SwiftUI” is selected. Then click on Create.

Make Sure “Use SwiftUI” is selected

Xcode starts with the ContentView.swift file opened and which shows you a kind of split screen. Before we get to know the SwiftUI interface of Xcode 11, let’s import the images we will need for creating our category cells. To do this, drag and drop the files into the Assets.xcassets folder. Make sure the images are named appropriately.

We’ve done everything necessary for setting up our project. So let’s dive into learning SwiftUI!

SwiftUI Basics 👁

Let’s get started with learning the very basics of SwiftUI that we need to know to build our Table View! Make sure you have opened the DefaultView.swift file. The code you see in the left section of your screen consists of two blocks: The ContentView struct and the ContentView_Previews struct. The ContentView struct contains the body of your UI. Here we create all the components we need for our UI, arrange and modify them. The ContentView_Previews struct then renders this body and displays it in the Preview Simulator on the right section of your screen.

Note: If the Preview Simulator is not running yet click on the “Resume” button in the upper right corner. Usually, changes are adopted automatically by the simulator. But sometimes, especially when larger changes are made, clicking the Resume button is necessary.

The right section contains the preview simulator, but sometimes clicking the “Resume” button is necessary

The right section contains the preview simulator, but sometimes clicking the “Resume” button is necessary

By default, the body contains a Text object with the content “Hello World”. A Text object in SwiftUI is similar to a Label in UIKit. Let’s change the content of the Text to “Food Delivery”. You can see that it directly affects the UI in the Preview Simulator! To modify a SwiftUI object we can make adjustments within the code or CMD-click the object directly in the Preview Simulator.

CMD-click on the Text object in the simulator and click on “Inspect”. Here we can adjust the object, for instance change the text type. For example, select “Large Title” as the font. As you see, the adjustment has a direct effect on the code. Another example, but this time within the written code: To change the font color, we can add a according modifier to the code. Below the .font modifier we can add a .color modifier and select for example green as the font color.

struct ContentView : View {
var body: some View {
       Text("Hello World")
            .font(.largeTitle)
            .foregroundColor(Color.green)
    }
}

Creating a “cell” with SwiftUI 🖌

But instead of texts, we want to have pictures, which will contain the appropriate texts for the categories. Thus, we delete the Text object and insert an Image object instead which we initialize by using the name of the image.

struct ContentView : View {
    var body: some View {
        Image("burger")
    }
}

The image currently fills out the entire screen of the device, so we need to give the image a frame which serves as a fixed-sized container for it. We do this by applying a .frame modifier to the Image object and giving the image frame a suitable width and height. In order for the image to not exceed the frame we have to add a .clipped Modifier. The .clipped modifier cuts out the area of the object that exceeds the specified frame.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Next, we need to resize the image so that it fits into the frame without distorting the dimensions of the image. To change the scaling of an image, we must use the .resizable modifier first. The .resizable modifier must be applied to enable changing the scaling of an image. It is important that the .resizable modifier is the first modifier of the image object.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Next, we apply the .aspectRatio modifier. This modifier scales the image so that it fits into the frame by using the specified dimensions. For keeping the original dimensions of the Image we can choose between the .fill or .fit contentMode. “Fill” scales the image so that the entire frame gets filled out with the image. “Fit” scales the image so that the entire image is visible within the frame. In our example we choose .fill as the content mode.

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 150)
            .clipped()
    }
}

Last but not least, let’s round the corners of the image. We do this by applying the .cornerRadius modifier (as the last modifier!).

struct ContentView : View {
    var body: some View {
        Image("burger")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 150)
            .clipped()
            .cornerRadius(20.0)
    }
}

Your preview should now look as follows:

For our category cell we want a text with the name of the category that gets stacked on top of the Image object. To stack objects in SwiftUI we use so-called ZStacks. We put the image object into a ZStack like this:

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
        }
    }
}

All objects within a ZStack are placed on top of each other. Let’s stack a Text object onto the Image object.

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
        }
    }
}

To make the Text object to be of a custom font and size we use the custom function within the .font modifier. Let’s also change the text color to white.

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(UIImage(named: "burger")!.size, contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
        }
    }
}

Here’s the current look of our cell:

Creating the list 💡

Of course we don’t want only one category for our Food Delivery App, but instead we need a total of four categories. To avoid writing the same code four times we make it reusable by CMD-clicking on the ZStack and selecting “Extract as subview” and giving it the name “CategoryView”. If this option isn’t available you can manually create a subview like this:


import SwiftUI

//We deleted the ZStack first, extracted it as a subview below, and inserted this subview
struct ContentView : View {
    var body: some View {
        CategoryView()
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

//Here is the extracted subview
struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
    }
}

The ZStack is now outsourced as a separate view which we can easily insert into our body.

Hint: Extracting views as subviews is best practice in SwiftUI and helps you making your code more reusable and clear.

We want to have four categories for our Food Delivery App. To achieve this, we use something called Lists in SwiftUI. A list is like a Table View you know from UIKit and contains multiple rows of data in a single column.

“List: A container that presents rows of data arranged in a single column.”

Apple

Lets create such a list and embed our CategoryView inside it. To have a total of four category “cells” (it’s not 100 percent correct to call the rows of a list cells) we have to insert three more CategoryViews into the List.

struct ContentView : View {
    var body: some View {
        List {
            CategoryView()
            CategoryView()
            CategoryView()
            CategoryView()
        }
    }
}

Hint: Maybe your cells are not centred anymore. To fix this, insert a Spacer inside the ContentView’s ZStack between the Image and the Text object.

struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
    }
}

The cells are a little too close together. To increase the distances we can add paddings. A padding serves as a gap next to an object. We want to add paddings above and below our CategoryViews to increase the distance between them. So we add two suitable .padding modifiers to the ZStack of the extracted CategoryView.

struct CategoryView : View {
    
    var body: some View {
        return ZStack {
            Image("burger")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text("BURGER")
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Here’s what your Preview Simulator should display so far:

Making the list dynamic 🔄

Of course not every CategoryView should contain the same image with the same text. To change this we insert two variables into our CategoryView above its body, a variable imageName and a variable categoryName. We want to use these variables to initialize the image and to insert the correct text. The values for the variables will then be entered at the initialization of the CategoryViews. So we adapt our CategoryView accordingly and use the variables for our image and the text instead of static values.

struct CategoryView : View {
    
    var imageName: String
    var categoryName: String
    
    var body: some View {
        return ZStack {
            Image(imageName)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text(categoryName)
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Now we have to adjust the initializations of the CategoryViews in the ContentView body and specify suitable values.

import SwiftUI

struct ContentView : View {
    var body: some View {
        List {
            CategoryView(imageName: "burger", categoryName: "BURGER")
            CategoryView(imageName: "pizza", categoryName: "PIZZA")
            CategoryView(imageName: "pasta", categoryName: "PASTA")
            CategoryView(imageName: "cake", categoryName: "DESSERTS")
        }
    }
}

Great! Here’s how our Food Delivery app looks so far:

And here’s the whole code we have written so far:

CategoryView.swift:


import SwiftUI

struct ContentView : View {
    var body: some View {
        List {
            CategoryView(imageName: "burger", categoryName: "BURGER")
            CategoryView(imageName: "pizza", categoryName: "PIZZA")
            CategoryView(imageName: "pasta", categoryName: "PASTA")
            CategoryView(imageName: "cake", categoryName: "DESSERTS")
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

struct CategoryView : View {
    
    var imageName: String
    var categoryName: String
    
    var body: some View {
        return ZStack {
            Image(imageName)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 300, height: 150)
                .clipped()
                .cornerRadius(20.0)
            Spacer()
            Text(categoryName)
                .font(.custom("HelveticaNeue-Medium", size: 50))
                .foregroundColor(.white)
            }
                .padding(.top, 5)
                .padding(.bottom, 5)
    }
}

Conclusion 🎊

Finished! With SwiftUI we have created a dynamic TableView as a list. In the next part of this series we will create a second screen containing the individual products of each category and learn how to connect different views with SwiftUI and how to transfer data between them.

Categories
Uncategorized

Everything you need to know about SwiftUI to get started

Boom .. here is it .. this thing Apple announced at the WWDC2019 and the reason why the whole iOS development community is going mad.

If you are into iOS development, you probably heard about SwiftUI in the last days. Everybody is writing about it and everyone is really excited. But at this very moment there are not many resources about SwiftUI und many are confused by it. But be sure, after reading this guide, you’ll know exactly what SwiftUI is about and how to get started using it.

What is SwiftUI?! 🤯

First of all, let’s talk about what SwiftUI is and what it is used for. Apples describes its new innovation as follows:

“SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift.”

Apple

So basically, SwiftUI is a completly new way of building user interfaces for apps. Until now the most common way to build the UI in Xcode was to use storyboards and XIB files. With SwiftUI, Apple provides an alternative to these.

With SwiftUI you can build your app by using a new declarative syntax. With this you describe how your interface should look like and what it should do.

With the declarative syntax you describe in code how your view should look like

You see that the syntax really differs from what you saw before in iOS programming. This is because it follows a declarative approach instead of an imperative one. Imperative programming was the common programming approach. But what’s the difference? In a nutshell: With imperative programming we write how we react when the state of our app changes, and accordingly execute code. With declaraive programming instead, we describe the interface of our app for all possible states in advance. Here’s a great article if you want to dive deeper.

In addition to a more simple and intuitive syntax, with SwiftUI Xcode finally serves us with a live preview while building the app’s UI. Everything we write will be instantly be shown our live preview which saves us much time compared to building the app and running it in the simulator every time.



Not only do we have a live preview with SwiftUI while writing our code, we can even edit our interface in the preview, which affects our code immediatly. This link between code and preview makes programming with Swift and Xcode even easier and more intuitive.



There are some more advantages coming with SwiftUI:

  • Connection similar to outlets and actions you know from using storyboards are permanently checked by the compiler. So no runtime errors anymore due to lost connections!
  • SwiftUI makes it easy to develop for mutiple platforms like iOS, macOS, watchOS etc.

Are storyboards dead? 😵

To keep it short: Storyboards aren’t dead. As the past has shown, Apple isn’t throwing away established concepts overnight. A comparison: Meanwhile it is 5 years ago that Swift replaced Objective-C as the main programming language for iOS development. Nevertheless, iOS apps can still be written in Objective-C without any problems. What we can assume, however, is that Apple will focus strongly on interface development with SwiftUI in the future. So although it will take time to establish (SwiftUI will be officially released together with Xcode 11 this fall), we recommend you to get in touch with it.

The same applies to UIKit, including UIViewControllers. With SwiftUI it’s still possible to include the different objects of the UIKit and to integrate them into SwiftUI.

“SwiftUI works seamlessly with the existing UI frameworks on all Apple platforms. For example, you can place UIKit views and view controllers inside SwiftUI views, and vice versa.”

Apple

How do I run SwiftUI? 🏃‍♀️

SwiftUI comes with Xcode 11 which will be published this fall. However, SwiftUI is already available as part of the beta version of Xcode 11.

Currently, there are two ways to run SwiftUI:

1: Mac OS XY (Catalina) beta + Xcode 11 beta

In order to use SwiftUI properly, you need to run the Xcode 11 beta on a Mac with MacOS Catalina, which is currently only available as a beta version too.

Here’s a great tutorial on how to install MacOS Catalina beta and Xcode 11 beta!

2: MacOS Mojave + Xcode 11 beta (limited possibilties)

If you don’t want to switch to Catalina beta, you can still explore SwiftUI in Xcode 11 running on a Mac with Mojave.

This option does not offer you all the features of SwiftUI, but lets you try out the basic concepts.

Here’s a great video on how to achieve this!

How do I start learning SwiftUI? 📚🤓

Now you are probably excited and want to get started with SwiftUI right away. Here are some resources to help you explore and learn SwiftUI.

Conclusion 🎊

Hopefully, you enjoyed reading this guide and now know about SwiftUI and how to get started.

Make sure you stay tuned and sign-up to the newsletter.