Creating a login screen in SwiftUI
Tags: ios, swift, swiftui
Published: 2020-02-11 07:52:06

---
title: Creating a login screen in SwiftUI
published: true
description: 
tags: ios, swift, swiftui
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/xjpzav2s8qbrmnnz1qv0.png
series: CometChat SwiftUI course
canonical_url: https://www.cometchat.com/tutorials/creating-a-login-screen-in-swiftui-3-7/
---

In the last part of this SwiftUI course, you've learned all about how to create and arrange SwiftUI views to craft beautiful interfaces. In this part, you'll put those skills to test by building out a login screen for your app.

You'll build a login screen with an email field, some information and a button at the bottom. Besides just building the screen, we'll also add a way to navigate from the welcome screen you created in the last part to the new login screen.

![Creating a login screen in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/11183jdsil8je1e8l6j3.png)

Ready to get started?

> You can find the [finished project code on GitHub](https://github.com/cometchat-pro-tutorials/swift-ui-chat).

## Kicking things off

To create the login screen, create a new SwiftUI View file called **LoginView.swift**. For now, change the body to display a text saying "Log In":

```swift
struct LoginView: View {
  
  var body: some View {
    Text("Log In")
  }
  
}
```

You'll fill up this placeholder screen later, but you'll first need a way to navigate to the screen.

> I am creating this course in collaboration with CometChat, a modern chat platform to help you add chat to you Swift app. During the next few weeks, we’ll be releasing installments of our free SwiftUI course here on dev.to! In the course, you’ll dive deep into SwiftUI by building a real-world production-scale chat app, learning SwiftUI in a practical way, on a scale larger than a simple example app. Follow me to get notified of future parts of this course! You can also follow [@CometChat](https://twitter.com/cometchat) on Twitter see the course of [CometChat’s blog](https://www.cometchat.com/blog/).

[![](https://thepracticaldev.s3.amazonaws.com/i/avpunu4hqp1ut7enf5tq.png)](https://www.cometchat.com)

## Navigation in SwiftUI

Two SwiftUI views will help you with navigation: `NavigationView` and  `NavigationLink`. `NavigationView` is SwiftUI's version of `UINavigationController`: It manages a stack of views and shows a navigation bar on top of them. 

`NavigationLink` is similar to a `Button`, but it triggers presenting a new view in the navigation stack when it's pressed. To work correctly, a `NavigationLink` needs to be nested inside a `NavigationView`. In UIKit terms, if `NavigationView` is the navigation controller, `NavigationLink` is a segue.

### Adding a SwiftUI Navigation View and a navigation bar

In iOS apps, you'd often have one navigation controller that will be the initial view controller of your app. You can achieve a similar effect in SwiftUI by making the initial view a `NavigationView`.

The initial view is determined by the `SceneDelegate`. If you look inside **SceneDelegate.swift** you'll find an implementation of `scene(_:willConnectTo:, options:)` that creates a content view and adds it to the window.

Instead of creating a plain `WelcomeView`, change `contentView` so that you wrap the welcome screen in a navigation view:

```swift
let contentView = NavigationView { WelcomeView() }
```

`contentView`, just like any other SwiftUI view, can be arbitrarily complex. The scene delegate is a good place to add global configuration, like changing colors, adding routing, subscribing to global events and other changes that affect the whole app.

Run the project now and you'll notice a giant space on the top of your welcome screen.

![SwiftUI fixing Navigation default padding](https://thepracticaldev.s3.amazonaws.com/i/wmrcrapreylduvltenuv.png)

This space is taken up by an empty navigation bar. Let's look at how to change the look and content of this bar.

### Styling the navigation bar in SwiftUI

Let's fill up the navigation bar with a title on the welcome screen. Open **WelcomeView.swift**, and add a navigation bar title to the top-most `VStack`:

```swift
var body: some View {
  VStack(alignment: .leading) {
    ...
  }
  .navigationBarTitle("Create an account")
}
```

If you run the project, you might notice there's now _two_ texts saying "Create an account". 

![Adding a navigation bar title in SwiftUI](https://thepracticaldev.s3.amazonaws.com/i/gklq8furyp21nvblllaa.png)

Remove the one inside the `VStack`:

```swift
var body: some View {
  VStack(alignment: .leading) {

    /* Remove from here...
    Text("Create an account")
      .modifier(BodyText())
      .padding()
    ... to here*/

    Text("Connect with people around the world")
      .modifier(TitleText())
      .padding([.bottom, .leading, .trailing])
    
    ...
}
```

The view looks the same as it did before, but the text is now part of the navigation bar.

> **Note**: If you're wondering how to get the default-looking iOS navigation bar, you can use `navigationBarTitle(_:displayMode:)` and pass in `.inline` as the display mode.

Since your view is wrapped inside a `NavigationView`, you can now use `NavigationLink` to present the login screen. Wrap the primary button in `body` inside a `NavigationLink` (instead of a `Button`):

```swift
var body: some View {
  VStack(alignment: .leading) {
    
    ...
    
    VStack(spacing: 30) {
      // Change this line:
      NavigationLink(destination: LoginView()) {
        PrimaryButton(title: "Log In")
      }
      
      Button(action: { }) {
        SecondaryButton(title: "Sign Up")
      }
    }
    
    ...
  }
}
```

Just like `Button`, `NavigationLink` will wrap around a view and make it interactable. Instead of calling a function, though, the navigation link will present the provided view when it's tapped.

If you run the project now and tap the log in button, you'll be taken to your login view.

![Presenting a new view from a button in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/y744bb3lgha0ir0spmxx.gif)

The view inside the navigation link can be anything you want, but be aware that views that respond to touches, like buttons, will _consume_ the touch and it won't propagate to the navigation link. In other words, buttons inside navigation links won't trigger the presentation. Watch out for those hungry buttons, lest they eat your touches!

## Making a reusable SwiftUI text field

Now that you can navigate to the login view, you'll start building out that view. 

Since you're building a login form, you'll first build a reusable text field that you can use throughout your app. Small reusable views are a leitmotif of SwiftUI apps! Once you're done, it will look like this:

![Making a text field in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/n4clu40uffa2vqhvklmk.png)

Already you can see the views you'll combine to create this text field. In a vertical stack, you'll need `Text` for the title, a `TextField` to edit the text, an `Image` to show the icon and a way to display the line on the bottom of the view.

Before you start working on the view, let's first add the little email icon. You can find the image [here](https://github.com/cometchat-pro-tutorials/swift-ui-chat/raw/master/CometChat/CometChat/Assets.xcassets/email.imageset/email.png). Drag it over to your **Assets.xcassets** file and name it **email**.

Next, create a new SwiftUI View file called **ErrorTextField.swift**. It's called error text field because you'll add the ability to show an error if the text is invalid. Change the struct to the following:

```swift
struct ErrorTextField: View {
  
  let title: String
  let placeholder: String
  let iconName: String
  let text: Binding<String>
  let keyboardType: UIKeyboardType
  let isValid: (String) -> Bool
  
  init(title: String,
    placeholder: String,
    iconName: String,
    text: Binding<String>,
    keyboardType: UIKeyboardType = UIKeyboardType.default,
    isValid: @escaping (String)-> Bool = { _ in true}) {
    
    self.title = title
    self.placeholder = placeholder
    self.iconName = iconName
    self.text = text
    self.keyboardType = keyboardType
    self.isValid = isValid
  }
  
}
```

It looks like a lot of code but don't worry — it's a boilerplate initializer that sets up all the necessary properties of the view. You'll need a title and a placeholder, the image name of the icon, a function to validate the text and the keyboard type of the text field.

You'll also need a **binding** to the text. A binding is similar to a state variable, but it's used to bind data between two different views. For instance, you can provide the text field with a binding to your text. The text field will change the text, and your view will be re-drawn. 

Think of binding as a state variable that you can pass to other views. It's kind of like giving someone your phone number and telling them to call you when something changes.

In this case, you'll receive a binding to the text that you'll pass along to the `TextField` view.

Since this view can show an error, add a computed property that will determine whether an error should be shown:

```swift
var showsError: Bool {
  if text.wrappedValue.isEmpty {
    return false
  } else {
    return !isValid(text.wrappedValue)
  }
}
```

There's no point in showing the error if the text is empty. If the text is _not_ empty, you'll use the provided text validation function to determine if an error should get shown.

Next, create the `body` for this view by adding the title to a stack:

```swift
var body: some View {
  VStack(alignment: .leading) {
    Text(title)
      .foregroundColor(Color(.lightGray))
      .fontWeight(.bold)
  }
}
```

To make your life easier, you'll also modify the preview to show the different possible states of the text field all at once:

```swift
struct ErrorTextField_Previews: PreviewProvider {
  static var previews: some View {
    Group {
      ErrorTextField(
        title: "Email",
        placeholder: "test@email.com",
        iconName: "email",
        text: .constant(""))
        .padding()
        .previewLayout(.fixed(width: 400, height: 100))
      
      ErrorTextField(
        title: "Email",
        placeholder: "test@email.com",
        iconName: "email",
        text: .constant("some@email.com"))
        .padding()
        .previewLayout(.fixed(width: 400, height: 100))
      
      ErrorTextField(
        title: "Email",
        placeholder: "test@email.com",
        iconName: "email",
        text: .constant("someemail.com"),
        isValid: { _ in false })
        .padding()
        .previewLayout(.fixed(width: 400, height: 100))
    }
  }
}
```

> **Note**: You'll notice the text is `.constant("some value")`. The `constant` is a factory static function that creates a binding that never changes. It's useful for testing and previews, like in the above example.

Just like in the previous part of this SwiftUI course, you create multiple previews by adding views into a `Group`.

![Previewing multiple states of a SwiftUI view at once.](https://dev-to-uploads.s3.amazonaws.com/i/nd01c4o8kibtmzqvgqhn.png)

Now that you can see what you're doing, let's add the text field and the email icon.

### Laying out SwiftUI views horizontally

With that out of the way, you can continue building the view by adding the text field and the icon. To make sure the text field and the icon are next to each other, you can use an `HStack`:

```swift
var body: some View {
  VStack(alignment: .leading) {
    
    Text(title)
      .foregroundColor(Color(.lightGray))
      .fontWeight(.bold)
    
    // New code:
    HStack {
      TextField(placeholder, text: text)
        .keyboardType(keyboardType)
        .autocapitalization(.none)
      Image(iconName)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 18, height: 18)
    }
  }
}
```

You're already familiar with `VStack`. Well, `HStack` works the same, except horizontally. You arrange a text field and an image in a horizontal stack.

![Creating a horizontal stack with HStack in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/18e2umevewe4ra4xvg1x.png)

The `TextField` is the SwiftUI equivalent of UIKit's `UITextField`, except it's plain by default — which is exactly what we need.

> **Note**: If you want to style a text field to look like a regular UIKit text field, you can call `textFieldStyle(RoundedBorderTextFieldStyle())` on the text field. The rounded border style is the one used by the good old `UITextField`.

In the previous part of this SwiftUI course, you learned about making image views expand to match their parent. In this case, you want the image to have a fixed size. That's why you call `frame` and pass it an exact width and height.

### Displaying basic shape views in SwiftUI

Finally, you'll add the border on the bottom of the text field. Since the border is just a plain rectangular view with no content, you can use a `Rectangle`:

```swift
var body: some View {
  VStack(alignment: .leading) {
    Text(title)
      .foregroundColor(Color(.lightGray))
      .fontWeight(.bold)
    
    HStack { ... }
    
    // New code:
    Rectangle()
      .frame(height: 2)
      .foregroundColor(showsError ? 
        .red : 
        Color(red: 189 / 255, green: 204 / 255, blue: 215 / 255))
  }
}
```

As its name suggests, `Rectangle` is just, well, a rectangle. Perfect for displaying borders or backgrounds. You set its height to 2 and leave other dimensions up to SwiftUI. You'll also set its color to red if `showsError` is true, otherwise, you'll use a light gray color.

![Using SwiftUI Rectangle to create a border for a view](https://dev-to-uploads.s3.amazonaws.com/i/yflmxkahw82k8a32ojel.png)

`Rectangle` is only one of a few basic shape views. There's also `RoundedRectangle`, `Circle`, `Capsule` and `Ellipse`. Remember these when you need to draw shapes — there's no need to resort to `CAShapeLayer` anymore.

You now have a nice looking text field that is flexible enough to be used throughout your app. It's time to put it to action in the login screen!

## Creating a SwiftUI login screen

Now, finally, you can get to work on the login screen! Head back to **LoginView.swift** and change `body` to the following:

```swift
var body: some View {
  VStack(alignment: .leading, spacing: 26) {
    Text("Log In")
      .modifier(TitleText())
  }
  .padding()
}
```

First, you'll make sure stack view items are aligned to the left edge, just like in the last part of this SwiftUI course. Add a spacing of 26 points between each item and make sure the "Log In" text is styled as a title. You'll also add the system padding to the stack.

![Creating a login screen in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/jkdbcn0a6r901wwqu63f.png)

Remember that the text field you built receives a validation function? Let's create one that will validate an email. Add the following method to the struct:

```swift
private func isValid(email: String) -> Bool {
  let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
  let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
  return predicate.evaluate(with: email)
}
```

This method checks the email string against a regular expression. If the email doesn't contain an @ sign, a dot, and text around both of those, this function will return `false`.

Also, add a state variable for the entered text:

```swift
@State private var email = ""
```

With those two pieces in place, you can now add a text field for the email to your view:

```swift
var body: some View {
  VStack(alignment: .leading, spacing: 26) {
    Text("Log In")
      .modifier(TitleText())
    
    // New code:
    ErrorTextField(
      title: "Email",
      placeholder: "mail@example.com",
      iconName: "email",
      text: $email,
      keyboardType: .emailAddress,
      isValid: isValid)
  }
  .padding()
}
```

Most of the properties should be self-explanatory, except maybe this weird `$text` thing. Don't worry, you're not programming PHP! `$` is a special character that converts an `@State` variable to a binding. Remember, bindings are state variables that can be changed from a different view. In this case, `LoginView` will pass the `email` as a binding to `ErrorTextField`.

![Creating a login screen in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/cn8cfc533wval68bit7i.png)

Next, create an empty function that you'll call when the user presses the login button:

```swift
private func login() {
  // Initiate network request
}
```

Then, add a spacer and a button that calls this function. As you learned in the previous part of this SwiftUI course, the spacer will expand to make sure everything above it is on top, while the button is on the very bottom of the stack. Add the following to `body`:

```swift
var body: some View {
  VStack(alignment: .leading, spacing: 26) {
    ...
    
    Spacer()
    
    Button(action: login) {
      PrimaryButton(title: "Log In")
    }
  }
  .padding()
}
```

Whenever the button is pressed, SwiftUI will call your `login` function which, currently, does absolutely nothing.

![Creating a login screen in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/11183jdsil8je1e8l6j3.png)

Don't be disappointed, you'll fix this soon.

### Presenting a SwiftUI view asynchronously

Later in this course, `login` will perform a network request to log the user in and then present a contacts screen. For now, though, you'll navigate to an empty screen when the button is pressed.

Earlier, you learned how to use `NavigationLink` to present a new view in the navigation stack when a button is pressed. Often, though, you don't want to _immidiately_ go to a new screen. You'll usually want to perform a check, a network request or some other bit of logic, and then present a new screen programmatically when you're done. SwiftUI has a way to do that, but it is a bit clumsy.

First, create a new state variable:

```swift
@State private var showContacts = false
```

You'll navigate to the contacts screen when this variable gets set to `true`. To do this, you can use a `NavigationLink`, but differently from before. Instead of wrapping a button in the navigation link, you'll show a hidden link that the user won't see. 

You'll also use a different `NavigationLink` initializer: `NavigationLink(destination:isActive:label)`. The key here is `isActive`: This is a binding to a bool variable. When it gets set from `false` to `true` `NavigationLink` will know to trigger the presentation.

Add the navigation link to the bottom of `body`:

```swift
var body: some View {
  VStack(alignment: .leading, spacing: 26) {
    ...
    
    Button(action: login) {
      PrimaryButton(title: "Log In")
    }
        
    NavigationLink(destination: EmptyView(), isActive: $showContacts) {
      EmptyView()
    }
  }
  .padding()
}
```

By making navigation link's body an `EmptyView` you make sure that the user can't see the link. You set its `isActive` binding to the state variable you created earlier.

Finally, set `showContacts` to `true` at the bottom of `login`:

```swift
showContacts = true
```

When the user presses the login button, SwiftUI calls `login`, which sets `showContacts` to true, triggering the `NavigationLink`, which then presents an empty view in the navigation stack. This Rube Goldberg machine of events is what happens when you try to do an imperative thing, like presenting a view programmatically, in a declarative UI framework.

### Presenting multiple SwiftUI views asynchronously

To expand this `NavigationLink` pattern to multiple views, you'd have to have one state variable for each view you want to present, leading to multiple flags in your view that can be false and true at the same time. This doesn't scale well.

To solve this problem, `NavigationLink` offers a _third_ initializer: `NavigationLink(_:destination:tag:selection:)`. While the one you used previously has a binding to a Boolean, this initializer is generic. `selection` is a binding to any `Hashable` type, like integers, strings and even enums. If the current value of `selection` matches the value of `tag`, the link will get triggered.

For instance, let's say you wanted to present either a login or a registration screen based on some logic. You'd start by defining an enum with cases for each of the views you'd like to present.

```swift
enum PresentedView {
  case login
  case registration
}
```

You'll also need a state variable to track which view should be shown:

```swift
@State private var viewToPresent: PresentedView?
```

Then, inside `body`, you can create hidden navigation links to each of your views.

```swift
NavigationLink(
  destination: LoginView(), 
  tag: .login, 
  selection: $viewToPresent) {
  EmptyView()
}

NavigationLink(
  destination: RegistrationView(), 
  tag: .registration, 
  selection: $viewToPresent) {
  EmptyView()
}
```

When you want to present one of these two views, set `viewToPresent` to the corresponding value:

```swift
viewToPresent = .login
```

The navigation link will check the bound value, and if it matches its tag, present the view. This solution is easier to scale, so if you have more than one view you'd like to present, I suggest you use this approach.


## Conclusion

And there you have it! You've built out a login screen and by doing so you've learned a bunch of important SwiftUI concepts:

 - How to present and style SwiftUI text fields, as well as how to use basic SwiftUI shape views.
 - How to use a `NavigationView` to show and style a navigation bar.
 - How to push a view when you press a button using `NavigationLink`.
 - How to push SwiftUI views programmatically using `NavigationLink(destination:isActive)` or `NavigationLink(destination:tag:selection:)`.

I'd say that's a good day's work! No need to stop now, though.

In the next part, you'll learn all about SwiftUI lists by building out a contacts screen to show your user's friends. You'll also begin your journey into making network requests!