LastPass clone - part 5

Apr 13, 2020

Hey guys, last week’s post was about finishing off the authenticationManager. In this one, we are going to start building the home screen. We will start by creating the curved navigation bar

Preparation

You should have the source code link in your email inbox if you are subscribed, otherwise click here to subscribe and get the source code. Let’s jump right into it.

Custom Curved Navigation Bar

In the Views folder, add a swift UI file named NavBar.swift. Add the following properties to the top of the struct:

    @Binding var showMenu: Bool
    var title: String = "Vault"
    var showSearchField = false

You will understand those when you use them shortly.

Now, replace the Text() in body with the following:

  ZStack(alignment: .bottom) {
            HStack {
                
                Rectangle()
                    .foregroundColor(Color.clear)
                    .frame(width: 20, height: 20)
                    .padding(.leading)
                Spacer()
                Text(title)
                    .font(.title)
                    .bold()
                    .foregroundColor(Color.white)
                
                Spacer()
                Image(systemName: "line.horizontal.3")
                    .resizable()
                    .frame(width: 20, height: 20)
                    .imageScale(.large)
                    .foregroundColor(Color.white)
                    .padding(.trailing)
                    
            }
        }

What we’ve done here is:

  1. We create a bold title, giving it a white color
  2. We then put a button to the right giving it 20 by 20 size.
  3. Last, we surround the HStack with a ZStack, you will see why shortly.

Now, add the following modifiers directly after the HStack container:

.padding(.bottom, 20)
                .frame(maxWidth: .infinity)
                .frame(height: showSearchField ? 140 : 90,alignment: showSearchField ? .center : .bottom)
                .background(Color.accent)
                .clip(shouldCurve: showSearchField)

Here’s what those modifiers mean:

  1. We put a padding of 20 to the bottom of the children, not the HStack
  2. We set the width to be as wide as its parent which is the ZStack
  3. We only want the curved navigation bar and the search field on the home screen, and flat and without the search field on other screens, that’s why we use the showSearchField flag to set the height accordingly.
  4. The last modifier is a custom one. We will create it next.

Curved shape

In your root folder, add another folder named Shapes, and create a swift file named CurvedShape.swift inside containing the following code:

import SwiftUI

struct CurvedShape: Shape {
    func path(in rect: CGRect) -> Path {
        let cornerRadius: CGFloat = 25
        var path = Path()
        
        path.move(to:  CGPoint.zero)
        path.addLine(to: CGPoint(x: rect.width, y: 0))
        path.addLine(to: CGPoint(x: rect.width, y: rect.height - cornerRadius))
        path.addQuadCurve(to: CGPoint(x: 0, y: rect.height - cornerRadius), control: CGPoint(x: rect.midX , y: rect.height))
        
        path.closeSubpath()
        
        return path
    }
}

Swift UI makes drawing custom shapes at little bit easier, but the process itself is the same. It’s like drawing with a pencil. Here is what we’ve done in the code above:

  1. We use the path we’ve just created to move the pencil to the origin point .
  2. Then we add a line from the origin point to CGPoint(x: rect.width, y: 0) which is the top-right corner.
  3. Then we add a new line from the top-right to to CGPoint(x: rect.width, y: rect.height - cornerRadius), this point will be a little bit above the bottom-right corner by 25 which is the cornerRadius value.
  4. This time we will add a quad curve which is a curve with less curvature . We do that by using the addQuadCurve method rather than addLine. The point CGPoint(x: 0, y: rect.height - cornerRadius) is where the curve will be added. The control parameter is also a point that will curve the line from right to left.

As you can see in the pic above, the control point ,as its name implies, is the point that will control how much curvature is applied to a particular line.

  1. Last we close the path which will link this point CGPoint(x: 0, y: rect.height - cornerRadius) to the origin CGPoint.zero.

Curved Modifier

Now, in the Modifiers folder, add a file name CurvedShapeModifier.swift containing the following code:

struct CurvedShapeModifier: ViewModifier {
    var shouldClip = false
    func body(content: Content) -> some View {
        
        if shouldClip {
            return AnyView(content.clipShape(CurvedShape()))
        } else {
            return AnyView(content)
        }
    }
}

Here is what I’ve done:

  1. We create a custom modifier by conforming the struct to ViewModifier which then gives us the body(content: Content) function. The content is the view we will apply the modification to.
  2. We then modify the content, in this case we apply a clipShape modifier, passing in a CurvedShape object. The view that’s returned will be a clipped version that has the exact shape that we’ve passed in.
  3. We wrap the content in AnyView because we are returning different types inside the condition.

Now add the following extension just below that struct:


extension View {
    func clip(shouldCurve: Bool) -> some View {
        self.modifier(CurvedShape(shouldClip: shouldCurve) )
    }
}

Here we’ve just created an helper method that we can call like a built-in swift UI modifier.

Command + B to build the project, and Voilà, all errors are gone. Open the NavBar.swift file, and resume the preview. You should see something like this:

Now change the showSearchField property value to true , resume the preview, and you should see this:

Custom search field.

In the ZStack container, below the HStack, add the following code:

 if showSearchField{
                LCSearchField(value: self.$searchTerm)
                    .padding()
                    .offset(y: 15)
            }

This code will introduce errors because you don’t the LCSearchField yet. Before we create the LCSearchField, add the following to the top of the struct:

  @State private var searchTerm = ""

We will change this when we introduce code data, but for now that will do the trick.

LCSearchField

In the Views folder, add a swift UI file named LCSearchField.swift. Add the following properties to the top of the struct:

 @Binding var value: String
 var onEditingChanged: ((Bool)-> Void) = {_ in }
 var onCommit: (() -> ()) = {}

As you can see the value property has a @Binding property wrapper attached to it. This will make every change we make outside of the current view to the value be propagated both ways (two-way binding).

The remaining properties are closures.

Closures are self-contained blocks of functionality that can be passed around and used in your code.

Closures are just like functions that you can pass around, set and get values from them. You can also pass in parameters like I did in the onEditingChanged parameter, and return values. In the above closures, I return Void (nothing)

Now replace the Text with the following:

// 1
  HStack {
			// 2
            Image(systemName: "magnifyingglass")
                .imageScale(.large)
                .padding(.leading)
			// 3
            TextField("Search ...", text: self.$value, 
onEditingChanged: self.onEditingChanged , onCommit: {
				
                self.onCommit()
            }).padding(.vertical, 10)
        }

Here is what we’ve done here:

  1. We put the 2 views in the HStack container.
  2. We create a magnifying glass image.
  3. We then create a textfield and attach the value property to the text parameter which is also a @Binding that’s why we are the dollar sign before the value’s name. See how we set the onEditingChanged? We can do that because the parameter and the property have the same signature. However, instead of doing the same for the onCommit, I call it like a function just to show you how many ways you can call a closure.

Add the following modifiers directly after HStack:

.foregroundColor(Color.gray)
              .background(Color.background)
              .cornerRadius(13)
              .padding()
              .frame(height: 45)
              .shadow(color: Color.darkShadow, radius: 5, x: 0, y: 5)

Here is a break down of the above code:

  1. We add the foregroundColor modifier to the HStack to make its children’s foreground color gray.
  2. We add the background color to the HStack
  3. The remaining modifiers are pretty simple to understand.

The last thing this you can do here is to add the following in the preview to silence the error you got :

value: .constant("")

Your preview code should look like this:

struct LCSearchField_Previews: PreviewProvider {
    static var previews: some View {
        LCSearchField(value: .constant(""))
    }
}

Now resume the preview… And you should see this:

Now open the Navbar.swift and resume the preview. You should see this:

That’s it folks. Next, we will create the list of passwords and notes so stay tune.

Don’t forget to subscribe and share this article. HAPPY CODING!!!

John K

I am a software developer and code enthusiast. Do you want to work with me, have a suggestion or a request? Feel free to contact me at [email protected] or https://twitter.com/liquidcoder