{"componentChunkName":"component---src-templates-course-js","path":"/course/singlepass-the-lastpass-redesign","result":{"data":{"contentfulCourse":{"title":"SinglePass - The lastpass redesign","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1590241012/lastpass%20clone/i1k22sc6fp7xa1yu2vfn.png","slug":"singlepass-the-lastpass-redesign","plan":"FREE","freeLessons":null,"updatedDate":"3 years ago","excerpt":"Over the past month and half, I’ve been working on a pure swift ui project that I am preparing to create articles for. The project itself will not be a full blown application, but will be major. You will learn how...","createdDate":"Monday, 10 February 2020","tags":[{"title":"Free"},{"title":"Swift UI"},{"title":"Course"}],"resources":[{"name":"Swift","url":"https://www.hackingwithswift.com/"},{"name":"Apple developer documentation","url":"https://developer.apple.com/videos/play/wwdc2019/215/"},{"name":"Advanced swift","url":"https://www.swiftbysundell.com/"},{"name":"Swift UI Handbook","url":""}],"lessons":[{"createdDate":"3 years ago","createdAt":"2020-08-15T18:02:50.927Z","isNewLayout":null,"previews":null,"slug":"lastpass-clone-pt1","title":"Login & SignUp User interface","updatedDate":"2020-02-16T00:00+01:00","downloads":[{"url":"https://ln2.sync.com/dl/40431ae00/8y4niefz-68enbfra-9fh9rid3-j8sxcvjt","type":"zip","id":"ca324d61-243b-5078-b474-06d55ac485ea","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This is part one of many in which we are going to build a redesigned clone of the last pass app. If you don\\u2019t know what last pass is, allow me to tell you all about it.\"), mdx(\"p\", null, \"Last pass is a password Management service that help in safely store password and other sensitive informations. You can check it out here.\"), mdx(\"p\", null, \"So in this course, we are going to clone some functionalities of their iOS app, not the entire app. I am thinking of creating a full blown swiftUI app in a paid course, what do you think? If you agree, send me an email or contact me on social media.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 11 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Catalina or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 13 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Open Xcode\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Create a new Xcode project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Select single view app and click next\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Name your app and make sure the user interface is Swift UI\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last, click Finish.\")), mdx(\"h2\", null, \"Starter project\"), mdx(\"p\", null, \"The starter project contains the boilerplate code that comes with a brand new Xcode project with the addition of assets that we will use thoroughtout this series. The ColorExt file contains the Color extension.\"), mdx(\"h2\", null, \"Neumorphism: Soft Design\"), mdx(\"p\", null, \"Neumorphism is a modern UI design trend that's a new take on Skeuomorphism. The app will be built using this emerging design language which in my opinion looks stunning to say the least. Fight me if you don\\u2019t agree (Just kidding \\uD83D\\uDE00).\"), mdx(\"p\", null, \"In the root folder, create another folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Modifiers\"), \" containing a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NeumorphicEffect.swift\"), \" , add put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct NeumorphicEffect: ViewModifier {\\n    func body(content: Content) -> some View {\\n        content\\n            .shadow(color: Color.darkShadow , radius: 10, x: 9, y: 9)\\n            .shadow(color: Color.lightShadow, radius: 10, x: -9, y: -9)\\n            \\n    }\\n}\\n\\n@available(iOS 13, macCatalyst 13, tvOS 13, watchOS 6, *)\\nextension View {\\n    func neumorphic() -> some View {\\n        return self.modifier(NeumorphicEffect())\\n    }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"That\\u2019s the code that will apply the amazing neumorphic effect all views. Pretty simple! \"), mdx(\"p\", null, \"Now, let\\u2019s start creating swiftUI views.\"), mdx(\"h2\", null, \"AuthenticationView\"), mdx(\"p\", null, \"The authentication view will be the view that will always show when the user opens the app or when the app switches from background to foreground. The user will only be able to use the app when he authenticates successfully.\"), mdx(\"p\", null, \"Now, create a folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" containing a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AuthenticationView.swift\"), \" with the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct AuthenticationView: View {\\n    \\n    @State private var showAccountCreationView = false\\n    \\n    var body: some View {\\n        VStack {\\n            if showAccountCreationView {\\n                AccountCreationView(showLogin: self.$showAccountCreationView)\\n                    .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))\\n            } else {\\n                LoginView(showCreateAccount: self.$showAccountCreationView)\\n                    .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))\\n            }\\n        }\\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\\n        .background(Color.background)\\n         .edgesIgnoringSafeArea(.all)\\n    }\\n}\\n\\nstruct AuthenticationView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        AuthenticationView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"We are missing a couple of views hence the compiler error, but we will fix that shortly. What the above code does is it will initially show the login view, and if the user does not have an account registered with the device, he will switch to the account creation view by  changing the showAccountCreationView state. We also apply some transition for when each view appears.\"), mdx(\"p\", null, \"Next up, before creating the 2 views, we first need  custom textfields that they will use. \"), mdx(\"h2\", null, \"SharedTextfield\"), mdx(\"p\", null, \"Create a Views folder containing a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SharedTextfield.swift\"), \", inside it put the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct SharedTextfield: View {\\n    @Binding var value: String\\n     var header = \\\"Username\\\"\\n     var placeholder = \\\"Your username or email\\\"\\n     var trailingIconName = \\\"\\\"\\n     var errorMessage = \\\"\\\"\\n     var showUnderline = true\\n     var onEditingChanged: ((Bool)->()) = {_ in }\\n     var onCommit: (()->()) = {}\\n    \\n    var body: some View {\\n        VStack(alignment: .leading, spacing: 0) {\\n            Text(header.uppercased()).font(.footnote).foregroundColor(Color.gray)\\n            HStack {\\n                TextField(placeholder, text: self.$value, onEditingChanged: { flag in\\n                    self.onEditingChanged(flag)\\n                }, onCommit: {\\n                    self.onCommit()\\n                }).padding(.vertical, 15)\\n                \\n                Image(systemName: self.trailingIconName ).foregroundColor(Color.gray)\\n            }\\n            .frame(height: 45)\\n            if showUnderline{\\n                Rectangle().frame(height: 1).foregroundColor(Color.gray)\\n            }\\n            \\n            if !errorMessage.isEmpty {\\n                Text(errorMessage)\\n                    .lineLimit(nil)\\n                    .font(.footnote)\\n                    .foregroundColor(Color.red)\\n                    .transition(AnyTransition.opacity.animation(.easeIn))\\n            }\\n        }.background(Color.background)\\n    }\\n}\\n\\nstruct SharedTextfield_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SharedTextfield(value: .constant(\\\"\\\"))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code does nothing special, it just stacks 3 views vertical to decorate the textfield. We will also show the error message if it\\u2019s not empty. We will create valuations using combine in the second part.\"), mdx(\"h2\", null, \"PasswordField\"), mdx(\"p\", null, \"In the same folder, create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordField.swift\"), \", and put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct PasswordField: View {\\n        @Binding var value: String\\n        var header = \\\"Username\\\"\\n        var placeholder = \\\"Your password\\\"\\n        var errorMessage = \\\"\\\"\\n        var trailingIconName = \\\"\\\"\\n        var showUnderline = true\\n        var onEditingChanged: ((Bool)->()) = {_ in }\\n        var onCommit: (()->()) = {}\\n        @State var isSecure: Bool = true\\n        let pasteboard = UIPasteboard.general\\n       \\n       var body: some View {\\n        \\n        VStack(alignment: .leading, spacing: 0) {\\n            Text(header.uppercased()).font(.footnote).foregroundColor(Color.gray)\\n            HStack {\\n                ZStack{\\n                    SecureField(placeholder, text: self.$value, onCommit: {\\n                        self.onEditingChanged(false)\\n                    }).padding(.vertical, 15).opacity(isSecure ? 1 : 0)\\n                    \\n                    TextField(placeholder, text: self.$value, onEditingChanged: { flag in\\n                        self.onEditingChanged(flag)\\n                    }).padding(.vertical, 15).opacity(isSecure ? 0 : 1)\\n                }\\n                \\n                \\n                HStack {\\n                    if isSecure{\\n                        Image(systemName: \\\"eye.slash\\\").foregroundColor(Color.gray).onTapGesture {\\n                            withAnimation {\\n                                self.isSecure.toggle()\\n                            }\\n                        }\\n                    } else {\\n                        Image(systemName: \\\"eye\\\").foregroundColor(Color.gray).onTapGesture {\\n                           withAnimation {\\n                                self.isSecure.toggle()\\n                            }\\n                        }\\n                    }\\n                    if !isSecure{\\n                        Image(systemName: self.trailingIconName )\\n                            .foregroundColor(Color.gray)\\n                            .transition(.opacity)\\n                            .onTapGesture {\\n                                self.pasteboard.string = self.value\\n                        }\\n                    }\\n                }\\n            }.frame(height: 45)\\n            Rectangle().frame(height: 1).foregroundColor(Color.gray)\\n            if !errorMessage.isEmpty{\\n                Text(errorMessage)\\n                    .lineLimit(nil)\\n                    .font(.footnote)\\n                    .foregroundColor(Color.red)\\n                    .transition(AnyTransition.opacity.animation(.easeIn))\\n            }\\n            \\n        }.background(Color.background)\\n    }\\n}\\n\\nstruct PasswordField_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PasswordField(value: .constant(\\\"\\\"))\\n    }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"The above code does almost the same thing as the ShareTextfield with some minor addition:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We stack a SecureField and the normal TextField on top of each other so that when one presses the eye icon, the field will toggle between showing and hiding the content of the password.\")), mdx(\"h2\", null, \"LCButton\"), mdx(\"p\", null, \"In  the folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LCButton.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct LCButton: View {\\n    var text = \\\"\\\"\\n    var backgroundColor = Color.black\\n    var action = {}\\n    \\n    var body: some View {\\n        Button(action: {\\n            HapticFeedback.generate()\\n            self.action()\\n        }) {\\n            HStack {\\n                Text(text)\\n                    .font(.system(size: 20, weight: Font.Weight.semibold))\\n                    .frame(minWidth: 0, maxWidth: .infinity)\\n                    .padding(.vertical)\\n                    .accentColor(Color.white)\\n                    .background(backgroundColor.opacity(0.9))\\n                    .cornerRadius(20)\\n            }\\n        }\\n    }\\n}\\n\\nstruct LCButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LCButton()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This is just a shared view that we will reuse in several place in this project. We also generate a light haptic feedback when one clicks the button. \"), mdx(\"p\", null, \"Create a folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Shared\"), \", and add a swift file inside named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HapticFeedback.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HapticFeedback {\\n    public static func generate(){\\n        let generator = UIImpactFeedbackGenerator(style: .light)\\n        generator.prepare()\\n        generator.impactOccurred()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"We are just generating a simple light feedback, but you can change  the style to your liking. To test this, you will need to run it on a physical device\"), mdx(\"p\", null, \"Now that we have our view components in place, it\\u2019s time to create the login and account creation views.\"), mdx(\"h2\", null, \"Login View\"), mdx(\"p\", null, \"In the views folder, add a swiftUI file  named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoginView.swift\"), \".  Put the following properties at the top of the file:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @Binding var showCreateAccount: Bool\\n    @State private var email = \\\"\\\"\\n    @State private var password = \\\"\\\"\\n    @State private var formOffset: CGFloat = 0\\n\")), mdx(\"p\", null, \"The email and password will change when we implement form validation with the combine framework, but for now those will do the job.\"), mdx(\"p\", null, \"Next, add the following method to create the content of the form.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       fileprivate func createContent() -> some View {\\n        VStack {\\n            Image(\\\"singlePass-dynamic\\\").resizable().aspectRatio(contentMode: .fit) .frame(height: 30)\\n            .padding(.bottom)\\n           \\n            VStack(spacing: 30) {\\n                Text(\\\"Login\\\").font(.title).bold()\\n                VStack(spacing: 30) {\\n                    SharedTextfield(value: self.$email, header: \\\"Email\\\" , placeholder: \\\"Your email\\\",errorMessage: \\\"\\\")\\n                    PasswordField(value: self.$password, header: \\\"Master Password\\\", placeholder: \\\"Make sure the password is strong\\\", errorMessage: \\\"\\\" , isSecure: true)\\n                    \\n                    LCButton(text: \\\"Login\\\", backgroundColor: Color.accent ) { }\\n                    \\n                    Button(action: {\\n                \\n                    }) {\\n                        VStack {\\n                            Image(systemName: \\\"faceid\\\")\\n                            .resizable()\\n                                .aspectRatio(contentMode: .fit)\\n                            .frame(width: 40, height: 40)\\n                                .foregroundColor(Color.accent)\\n                            Text(\\\"Use face ID\\\").foregroundColor(.accent)\\n                        }\\n                    }\\n                }\\n                \\n                \\n            }.modifier(FormModifier()).offset(y: self.formOffset)\\n            createAccountButton()\\n        }\\n    }\\n\")), mdx(\"p\", null, \"You will get a bunch of errors caused by the missing  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FormModifier\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccountButton()\"), \" method. To create it, add  a swift file named, you guessed it,  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FormModifier.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct FormModifier: ViewModifier {\\n        \\n     func body(content: Content) -> some View {\\n        content.padding()\\n                 .background(Color.background)\\n                             .cornerRadius(10)\\n                             .padding()\\n                             .neumorphic()\\n               \\n       }\\n}\\n\\n\\n\")), mdx(\"p\", null, \"The above code just creates a typical swift UI layout, hopefully you can understand easily, if you don\\u2019t, feel free to send me an email.\"), mdx(\"p\", null, \"Next, add the following method below the one you\\u2019ve just added:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" fileprivate func createAccountButton() -> some View {\\n        return Button(action: {\\n            withAnimation(.spring()) {\\n                self.showCreateAccount.toggle()\\n            }\\n        }) {\\n            HStack {\\n                Image(systemName: \\\"arrow.left.square.fill\\\")\\n                .resizable()\\n                    .aspectRatio(contentMode: .fit)\\n                .frame(height: 20)\\n                    .foregroundColor(Color.darkerAccent)\\n                Text(\\\"Create account\\\")\\n                    .accentColor(Color.darkerAccent)\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"Right now the body still contains the hello world text. Before we put the login form content we\\u2019ve just created inside, let\\u2019s first write the code  that will handle the keyboard. \"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, add a swift file named\\n\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NotificationCenterExt.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport UIKit\\n\\nextension NotificationCenter {\\n    private static let keyboardWillShow = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification)\\n    private static let keyboardWillHide = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillHideNotification)\\n    \\n    static var keyboardPublisher = NotificationCenter.keyboardWillShow.merge(with: NotificationCenter.keyboardWillHide.map { Notification(name: $0.name, object: $0.object, userInfo: nil) })\\n        .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero)}\\n}\\n\\n\")), mdx(\"p\", null, \"The above code create 2 notifications that will listen for when the keyboard shows or hides. We then merge them together using combine\\u2019s merge operator to retrieve the keyboard\\u2019s frame. So when we subscribe will to this publisher, we will get back the frame that we will utilise to adjust the  content hidden by the keyboard.  \"), mdx(\"p\", null, \"There are  several ways one can subscribe to publisher in swift UI using combine, but we will use a special view that comes with swiftUI called \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SubscriptionView\"), \". This view was made to handle publishers, its constructor takes in the content, a publisher and spills out the output of the publisher. Here is its signature:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@inlinable public init(content: Content, publisher: PublisherType, action: @escaping (PublisherType.Output) -> Void)\\n\")), mdx(\"p\", null, \"Let\\u2019s now implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", shall we?\"), mdx(\"p\", null, \"Replace the content of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"SubscriptionView(content: createContent(), publisher: NotificationCenter.keyboardPublisher) { frame in\\n            withAnimation {\\n                self.formOffset = frame.height > 0 ? -200 : 0\\n            }\\n        }\\n\\n\")), mdx(\"p\", null, \"In the code above, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SubscriptionView\"), \" passing in the content we created earlier and the keyboard publisher. We get the keyboard\\u2019s frame that we use to change the formOffset.\"), mdx(\"h2\", null, \"Account creation view\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, create a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AccountCreationView.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport SwiftUI\\n\\nstruct AccountCreationView: View {\\n    \\n    @Binding var showLogin: Bool\\n    @State private var email = \\\"\\\"\\n    @State private var password = \\\"\\\"\\n    @State private var confirmedPassword = \\\"\\\"\\n    @State private var formOffset: CGFloat = 0\\n    \\n\\n    fileprivate func goToLoginButton() -> some View {\\n        return Button(action: {\\n            withAnimation(.spring() ) {\\n                self.showLogin.toggle()\\n            }\\n        }) {\\n            HStack {\\n                Text(\\\"Login\\\")\\n                    .accentColor(Color.darkerAccent)\\n                Image(systemName: \\\"arrow.right.square.fill\\\")\\n                    .resizable()\\n                    .aspectRatio(contentMode: .fit)\\n                    .frame(height: 20)\\n                    .foregroundColor(Color.darkerAccent)\\n                    \\n            }\\n        }\\n    }\\n    \\n    fileprivate func createContent() -> some View{\\n        VStack {\\n                Image(\\\"singlePass-dynamic\\\").resizable().aspectRatio(contentMode: .fit) .frame(height: 30)\\n                    .padding(.bottom)\\n                VStack(spacing: 10) {\\n                    Text(\\\"Create Account\\\").font(.title).bold()\\n                    VStack(spacing: 30) {\\n                        SharedTextfield(value: self.$email,header: \\\"Email\\\", placeholder: \\\"Your primary email\\\",errorMessage: \\\"\\\")\\n                        PasswordField(value: self.$password,header: \\\"Password\\\",  placeholder: \\\"Make sure it's string\\\",errorMessage: \\\"\\\", isSecure: true)\\n                        PasswordField(value: self.$confirmedPassword,header: \\\"Confirm Password\\\",  placeholder: \\\"Must match the password\\\", errorMessage: \\\"\\\", isSecure: true)\\n                        \\n                    }\\n                    LCButton(text: \\\"Sign up\\\", backgroundColor: Color.accent ) {}\\n                    \\n                }.modifier(FormModifier()).offset(y: self.formOffset)\\n            \\n            goToLoginButton()\\n        }\\n    }\\n\\n    var body: some View {\\n        \\n        SubscriptionView(content: createContent(), publisher: NotificationCenter.keyboardPublisher) { frame in\\n            withAnimation {\\n                self.formOffset = frame.height > 0 ? -200 : 0\\n            }\\n        }\\n    }\\n}\\n\\nstruct SignupView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        AccountCreationView(showLogin: .constant(false))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code is the same as what I\\u2019ve just explained. We\\u2019ve  only added an extra textfield for the password confirmation and removed the Touch ID or Face ID  button.\"), mdx(\"p\", null, \"The last thing to do is to call the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationView()\"), \"  in the ContentView\\u2019s body. Your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \"\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" should look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var body: some View {\\n    AuthenticationView()\\n }\\n\")), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"As already, feel free to share this tutorial and happy coding!!! Get the source code here\"), mdx(\"p\", null, \"Creating this type of project is tiring and time-consuming. I spent days planning, designing, struggling and coding, that\\u2019s why I need your support to keep making this type of project for free. So if you want to support me, you can do so by subscribing to the premium plan.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#starter-project","title":"Starter project"},{"url":"#neumorphism-soft-design","title":"Neumorphism: Soft Design"},{"url":"#authenticationview","title":"AuthenticationView"},{"url":"#sharedtextfield","title":"SharedTextfield"},{"url":"#passwordfield","title":"PasswordField"},{"url":"#lcbutton","title":"LCButton"},{"url":"#login-view","title":"Login View"},{"url":"#account-creation-view","title":"Account creation view"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:43:17.613Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-11","title":"CoreData Configuration","updatedDate":"2020-05-25T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZxdtqkZvQzSYeQtOaVfTF2htH3vzH1H3rNy","type":"zip","id":"622f4f6a-43b1-5242-a1fa-604ecac7ce8e","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys! In the previous part (part 10), we finished the most important part of the home screen, the only thing left is implementing the menu, but that will be implemented after after integrating CoreData. So in this part we will configure CoreData and in the next  one we will finish implementing everything CoreData related. To follow along, get the previous lesson source code. Let\\u2019s jump right in.\"), mdx(\"h2\", null, \"Data Model File\"), mdx(\"p\", null, \"Inside your project, look for a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SinglePass.xcdatamodeld\"), \". This is the file that we will use to create schemas which are \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManagedObject\"), \" subclasses. We will create 2 entities, passwords and notes. Let\\u2019s do just that\"), mdx(\"h2\", null, \"Entity creation\"), mdx(\"p\", null, \"Like I\\u2019ve just said, we will have 2 entities. I will show you how to create one, and you can replicate the process for the second one. select the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SinglePass.xcdatamodeld\"), \" data model file , then click the add button located at the bottom-left corner and rename the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Entity\"), \" to \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordItem\"), \".\"), mdx(\"p\", null, \"Then, while that entity is selected, click the add button in the attributes section to add an attribute.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1588494373/lastpass%20clone/esvufyiwiwvrjobmozd8.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"In the above screenshot, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordItem\"), \" entity. Add those 8 attributes. When you\\u2019re done, select the inspector on the right and set the module to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"current project module\")), \" and set the codegen to  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Manual/None\\xA0\")), \" , also make sure the module is   \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Current Product Module\"), \" because we want to control the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"managedObject\"), \" ourselves like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1588494772/lastpass%20clone/qwhmcdrshlt8lmdujput.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now, do the same for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NoteItem\"), \", here is a screenshot for it.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1588494373/lastpass%20clone/ewofvrrzqw9bxj1vxqji.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"We only have  6 attributes for this one. Make sure to not forget to set the codegen to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Manual/None\\xA0\")), \" for both the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordItem\"), \" and the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NoteItem\"), \" entities.\"), mdx(\"h2\", null, \"NSManaged objects creation.\"), mdx(\"p\", null, \"After, we\\u2019ve done everything aforementioned successfully, it\\u2019s now time to create the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NSManaged\"), \" objects for the entities we\\u2019ve created. What is a managed object you may ask. Apple defines it as follow:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"A managed object is a model object (in the model-view-controller sense) that represents a record from a persistent store. A managed object is an instance of NSManagedObject or a subclass of NSManagedObject. A managed object is registered with a managed object context. In any given context, there is at most one instance of a managed object that corresponds to a given record in a persistent store.\\nIn the above statement, you can understand that the managed objects we\\u2019ve just created will be the ones that we will hold the data that we want save in the database \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"(CoreData)\"), \", and that they are model objects like in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MVC\"), \" \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"(model-view-controller)\"), \", but we are not going to use \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MVC\"), \" in this project, we will use use \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MVVM\"), \", so we will need to map those 2 models, to their respective \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ViewModel\"), \" Objects.\")), mdx(\"h2\", null, \"Password and note items\"), mdx(\"p\", null, \"Now, create the following files in the Models folder: \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordItem.swift\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NoteItem.swift\"), \", and then add the following code inside them respectively:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport CoreData\\n\\npublic class PasswordItem: NSManagedObject, Identifiable {\\n    @NSManaged public var id: UUID\\n    @NSManaged public var site: String\\n    @NSManaged public var username: String\\n    @NSManaged public var password: String\\n    @NSManaged public var createdAt: Date\\n    @NSManaged public var note: String\\n    @NSManaged public var lastUsed: Date\\n    @NSManaged public var isFavorite: Bool\\n}\\n\\n\")), mdx(\"p\", null, \"And\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport Foundation\\nimport CoreData\\n\\n public class NoteItem: NSManagedObject, Identifiable {\\n    @NSManaged public var id: UUID\\n    @NSManaged public var name: String\\n    @NSManaged public var content: String\\n    @NSManaged public var isFavorite: Bool\\n    @NSManaged public var createdAt: Date\\n    @NSManaged public var lastUsed: Date    \\n}\\n\\n\")), mdx(\"p\", null, \"Those are our two model objects. Next up, let\\u2019s create their respective ViewModel objects. Create a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ViewModels\"), \" folder in the root directory, then add 2 files named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordViewModel.swift\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NoteViewModel.swift\"), \". Next, put the following code inside each of them respectively:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport Foundation\\n\\n\\nstruct PasswordViewModel: Identifiable {\\n    var id: UUID\\n    var site: String\\n    var username: String\\n    var password: String\\n    var createdAt: Date\\n    var note: String\\n    var lastUsed: Date\\n    var isFavorite: Bool\\n    \\n    init(passwordItem: PasswordItem) {\\n        self.id = passwordItem.id\\n        self.site = passwordItem.site\\n        self.username = passwordItem.username\\n        self.password = passwordItem.password\\n        self.createdAt = passwordItem.createdAt\\n        self.isFavorite = passwordItem.isFavorite\\n        self.note = passwordItem.note\\n        self.lastUsed = passwordItem.lastUsed\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"And\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nimport Foundation\\n\\nstruct NoteViewModel: Identifiable {\\n    var id: UUID\\n    var name: String\\n    var content: String\\n    var isFavorite: Bool\\n    var createdAt: Date\\n    var lastUsed: Date\\n    \\n    init(noteItem: NoteItem) {\\n        self.id = noteItem.id\\n        self.name = noteItem.name\\n        self.content = noteItem.content\\n        self.isFavorite = noteItem.isFavorite\\n        self.createdAt = noteItem.createdAt\\n        self.lastUsed = noteItem.lastUsed\\n    }\\n}\\n\")), mdx(\"p\", null, \"As you can see above, we map the items to their respective view models which may leave you wondering why on earth haven\\u2019t we just used the items directly? Is there anything that prevent us from doing so? The answer is nothing, we could\\u2019ve used the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManaged\"), \" object subclasses directly, but using \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"viewmodels\"), \" gives us more ways to control and customise properties in a cleaner manner.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"This is it for this part folks, In this part we\\u2019ve only prepared the playground for what we will be doing in the next one.  Stay tuned for more. Please feel free to share this article, subscribe if you haven\\u2019t done so already.  If you have any question, send me an email.  Stay tuned and happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#data-model-file","title":"Data Model File"},{"url":"#entity-creation","title":"Entity creation"},{"url":"#nsmanaged-objects-creation","title":"NSManaged objects creation."},{"url":"#password-and-note-items","title":"Password and note items"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:47:23.433Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-13","title":"CoreData Integration in Views","updatedDate":"2020-06-08T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZbvJEkZzGGx2UQQvI0gvqB0KpTeGjm5ccYX","type":"zip","id":"057c342e-7ee8-5a45-bcde-aa2239f79a39","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys! Previously, we finished creating the CoreData Manager which is the class that we used to make CoreData operations. In this one, we will finally integrate core data in our views. We will also do a bit of refactoring. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"List\"), mdx(\"p\", null, \"The list is the main piece of our app, so it does make sense to start with it. In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \", most of the work will be done in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordsSection\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNotesSection\"), \", we will wrap the sections inside a component that will fetch data from the database and feed them to the section. Let\\u2019s create that component then:\"), mdx(\"h2\", null, \"Fetch result wrapper\"), mdx(\"p\", null, \"Inside the Views folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FetchResultWrapper.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport CoreData\\n\\nstruct FetchResultWrapper<Object, Content>: View where Object: NSManagedObject, Content: View {\\n    \\n    let content: ([Object]) -> Content\\n    var request: FetchRequest<Object>\\n    \\n    init(\\n         predicate: NSPredicate = NSPredicate(value: true),\\n         sortDescriptors: [NSSortDescriptor] = [],\\n         @ViewBuilder content: @escaping ([Object]) -> Content\\n     ) {\\n         self.content = content\\n         self.request = FetchRequest(\\n             entity: Object.entity(),\\n             sortDescriptors: sortDescriptors,\\n             predicate: predicate\\n         )\\n     }\\n    var body: some View {\\n        self.content(request.wrappedValue.map({$0}))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"That\\u2019s one complicated code block, let\\u2019s break it down:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"This wrapper is a generic view container where the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Object\"), \" is a\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NSManagedObject\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Content\"), \" is a View.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"content\"), \" is a closure that will take in an array of managed object, build a view with them, (in this case it will be a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Section\"), \" view) and spills that View. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"request\"), \"  will be used to collects the criteria needed to select and optionally to sort a group of managed objects held in the database.  \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create an initialiser which we will use to pass in a sort descriptor and a predicate. As I mentioned earlier, the fetch request need a predicate and a sort descriptor.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the body block, we map over the request\\u2019s results, which is of type \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"FetchedResults<Result>\"), \", to retrieve the object and convert the fetch results to an array of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Object\"), \" (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NSManagedObject\"), \") that we then pass in the content view builder.\\nThe above component will allow us to just wrap a list or a section and feed whatever comes from the request to the its children.\")), mdx(\"h2\", null, \"Update the home view list\"), mdx(\"p\", null, \"We need to get an instance of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoreDataManager\"), \" class  in the home. Likely for us, we\\u2019ve created a shared instance that we can just pass around\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@ObservedObject var coredataManager = CoreDataManager.shared\\n\")), mdx(\"p\", null, \"Next, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordsSection()\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNotesSection()\"), \" methods, replace their existing content with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    private func createPasswordsSection() -> some View {\\n        \\n        FetchResultWrapper(predicate: self.coredataManager.passwordPredicate, sortDescriptors: [self.coredataManager.sortDescriptor]) { (passwords: [PasswordItem]) in\\n            Section(header:\\n                SectionTitle(title: \\\"Passwords\\\")\\n            ) {\\n                ForEach(1..<5) { i in\\n                    RowItem().listRowBackground(Color.background)\\n                }\\n            }\\n        }        \\n    }\\n\")), mdx(\"p\", null, \"And this is in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNotesSection()\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"private func createNotesSection() -> some View {\\n        \\n        FetchResultWrapper(predicate: self.coredataManager.notePredicate, sortDescriptors: [self.coredataManager.sortDescriptor]) { (notes: [NoteItem]) in\\n            Section(header:\\n                SectionTitle(title: \\\"Notes\\\")\\n            ) {\\n                ForEach(1..<5) { i in\\n                            RowItem().listRowBackground(Color.background)\\n                }\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"What we\\u2019ve done here is just wrapping the existing section with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FetchResultWrapper\"), \", passing in the predicate and sort descriptor created in the CoreData manager.   Right now, If you build and run the project, you will still see the old hard-coded values displayed on the list, that\\u2019s normal as we haven\\u2019t added anything in the database nor have we linked the NSManaged Objects from the fetch request to our views. \"), mdx(\"p\", null, \"Now, replace the range (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1..<5\"), \") inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach()\"), \"  with the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordsSection\"), \" method:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"passwords.map { PasswordViewModel(passwordItem: $0) } \\n\")), mdx(\"p\", null, \"And this in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNotesSection\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"notes.map { NoteViewModel(noteItem: $0) } \\n\")), mdx(\"p\", null, \"Rename the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"i\"), \" in the closure block to password and note respectively.\"), mdx(\"p\", null, \"What we\\u2019ve done above is converting the passwords and notes to an array of their view model counterparts. We still have two pass the note or password to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \" which we will do later, but let\\u2019s first take care of the home view.\"), mdx(\"p\", null, \"Next, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createList\"), \" function replace the content of the list, meaning the 2 function calls with the following condition:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nif self.coredataManager.showPasswords{\\n   createPasswordsSection()\\n}\\n            \\nif self .coredataManager.showNotes {\\n   createNotesSection()\\n}\\n\")), mdx(\"p\", null, \"Here we use the published boolean values from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoreDataManager\"), \" to check whether we need to show both passwords and notes or one of them. \"), mdx(\"p\", null, \"If you take a look at our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Filter\"), \" enum, you will see that we have 5 filters, and we\\u2019ve just covered the remaining 2. With that done, let\\u2019s now implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \". Open its file, and add the following to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var passwordModel: PasswordViewModel?\\n    var noteModel: NoteViewModel?\\n\")), mdx(\"p\", null, \"In order to avoid moving back and forth, let\\u2019s just replace the whole body content with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" let image = passwordModel?.site.getImageName()\\n        return HStack{\\n            Image(image ?? \\\"note\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 50, height: 50)\\n            \\n            VStack(alignment: .leading) {\\n                Text(passwordModel?.site ?? noteModel?.name ?? \\\"No name\\\")\\n                    .font(.system(size: 20))\\n                    .padding(.bottom, 5)\\n                Text(passwordModel?.username ?? noteModel?.createdAt.format() ?? \\\"N/A\\\")\\n                    .foregroundColor(Color.gray)\\n                    .font(.callout)\\n            }\\n        }.padding(.horizontal)\\n        .frame(maxWidth: .infinity, minHeight: 80, alignment: .leading)\\n            .background(Color.background)\\n             .cornerRadius(20)\\n             .neumorphic()\\n             .padding(.vertical)\\n\")), mdx(\"p\", null, \"You\\u2019ll get an error caused by the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".format()\"), \" extension method on the Date type. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \"  folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DateExt.swift\"), \" containing the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nextension Date{\\n    func format(_ format: String = \\\"E, d MMM yyyy HH:mm\\\") -> String {\\n           let formatter = DateFormatter()\\n           formatter.dateFormat = format\\n            formatter.locale = Locale.autoupdatingCurrent\\n           let component = formatter.string(from: self)\\n           return component\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"This code just extends the Date with an helper method that you saw us use in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \" view. Now build the project and the error should go away. \"), mdx(\"p\", null, \"With that in place, go back in the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView.swift\"), \", and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \" call in both methods with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"RowItem(passwordModel: password).listRowBackground(Color.background)\\n\")), mdx(\"p\", null, \"For the password, and this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" RowItem(noteModel: note).listRowBackground(Color.background)\\n\")), mdx(\"p\", null, \"If you run the project now, you will find that all of your hard-coded data have disappeared, but you are still seeing the password and note section header texts, let\\u2019s get rid of those if passwords or notes are empty. \"), mdx(\"p\", null, \"Inside the functions that creates those sections, only replace  the sections with the following for the passwords:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if !passwords.isEmpty {\\n                Section(header:\\n                    SectionTitle(title: \\\"Passwords\\\")\\n                ) {\\n                    ForEach(passwords.map { PasswordViewModel(passwordItem: $0) } ) { password in\\n                        RowItem(passwordModel: password).listRowBackground(Color.background)\\n                    }\\n                }\\n            }\\n\")), mdx(\"p\", null, \"And this for the notes:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"            if !notes.isEmpty {\\n                Section(header:\\n                    SectionTitle(title: \\\"Notes\\\")\\n                ) {\\n                    ForEach(notes.map { NoteViewModel(noteItem: $0) } ) { note in\\n                        RowItem(noteModel: note).listRowBackground(Color.background)\\n                    }\\n                }\\n            }\\n\\n\")), mdx(\"p\", null, \"Now run the app again, and you should see a clean screen. However, it\\u2019s would be a much better user experience to show the user that he should click the button to add new passwords or notes when there\\u2019s nothing right? I\\u2019ll leave that to you, \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"be creative and liquid.\")), mdx(\"p\", null, \"Only thing that\\u2019s left now is saving new passwords and notes to the database.\"), mdx(\"h2\", null, \"Persisting data to core data\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \", add the following at the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var passwordModel: PasswordViewModel?\\n    var noteModel: NoteViewModel?\\n    @EnvironmentObject var coredataManager: CoreDataManager\\n\")), mdx(\"p\", null, \"Next, add the following function below everything:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  \\n    fileprivate func saveNewPassword() {\\n        if self.passwordModel == nil{\\n            if !username.isEmpty\\n               && !password.isEmpty\\n            && !website.isEmpty {\\n                let password = PasswordItem(context: coredataManager.context)\\n                password.createdAt = Date()\\n                password.id = UUID()\\n                password.isFavorite = false\\n                password.lastUsed = Date()\\n                password.note = self.passwordNote\\n                password.site = self.website.lowercased()\\n                password.username = self.username\\n                password.password = self.password\\n                _ = coredataManager.save()\\n                \\n                withAnimation {\\n                    self.showDetails = false\\n                }\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"Above we first check if inputs are valid, then proceed to creating a new password item using the context, we then set each property and save those changes using the context to persist them to the database.\"), mdx(\"p\", null, \"Do the same thing for the note by adding this below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func saveNewNote() {\\n        if self.noteModel == nil {\\n            \\n            if !self.noteName.isEmpty && !self.noteContent.isEmpty {\\n                let note = NoteItem(context: coredataManager.context)\\n                note.id = UUID()\\n                note.isFavorite = false\\n                note.createdAt = Date()\\n                note.lastUsed = Date()\\n                note.name = noteName\\n                note.content = noteContent\\n                _ = coredataManager.save()\\n                withAnimation {\\n                    self.showDetails = false\\n                }\\n            }\\n        }\\n    }\\n\\n\")), mdx(\"p\", null, \"Now find the function that creates the save button and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"break\"), \" in each case with corresponding \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveNew__\"), \" functions so that the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createSaveButton\"), \" becomes like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createSaveButton() -> LCButton {\\n           return LCButton(text: \\\"Save\\\", backgroundColor: Color.accent) {\\n               switch self.formType{\\n               case .Password:\\n                    self.saveNewPassword()\\n               case .Note:\\n                    self.saveNewNote()\\n               }\\n           }\\n       }\\n\")), mdx(\"p\", null, \"Now run the app, add a new note or password and click \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"save\"), \", but you will need to dismiss the sheet explicitly. The sheet should be dismissed automatically upon clicking the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"save\"), \" button, but it doesn\\u2019t, this is my fault as I forgot to bind the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showDetails\"), \" binding property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \" to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showEditFormView\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \", let\\u2019s do that now. \"), mdx(\"p\", null, \"Find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createEditFormView\"), \" function, and add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \" call:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"showDetails: self.$showEditFormView\\n\")), mdx(\"p\", null, \"Test the app again, and now everything should work as expected.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That\\u2019s it folks! This might be the last post for this series. I might add one for the navigation drawer if people are interested. The remaining bit are just simple screens that anyone can creates by himself.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#list","title":"List"},{"url":"#fetch-result-wrapper","title":"Fetch result wrapper"},{"url":"#update-the-home-view-list","title":"Update the home view list"},{"url":"#persisting-data-to-core-data","title":"Persisting data to core data"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T18:29:34.915Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-5","title":"Custom navigation bar","updatedDate":"2020-04-13T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZdxWPkZbdRpJFtA7vkVNqhLWijclyQrwzFV","type":"zip","id":"e2899fb9-a3b7-59b3-8a83-c4c5789a7ae5","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, last week\\u2019s 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. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"Custom Curved Navigation Bar\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add a swift UI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NavBar.swift\"), \".  Add the following properties to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @Binding var showMenu: Bool\\n    var title: String = \\\"Vault\\\"\\n    var showSearchField = false\\n\")), mdx(\"p\", null, \"You will understand those when you use them shortly.\"), mdx(\"p\", null, \"Now, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text()\"), \" in body with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  ZStack(alignment: .bottom) {\\n            HStack {\\n                \\n                Rectangle()\\n                    .foregroundColor(Color.clear)\\n                    .frame(width: 20, height: 20)\\n                    .padding(.leading)\\n                Spacer()\\n                Text(title)\\n                    .font(.title)\\n                    .bold()\\n                    .foregroundColor(Color.white)\\n                \\n                Spacer()\\n                Image(systemName: \\\"line.horizontal.3\\\")\\n                    .resizable()\\n                    .frame(width: 20, height: 20)\\n                    .imageScale(.large)\\n                    .foregroundColor(Color.white)\\n                    .padding(.trailing)\\n                    \\n            }\\n        }\\n\")), mdx(\"p\", null, \"What we\\u2019ve done here is:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a bold title, giving it a white color\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then put a button to the right giving it 20 by 20 size.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we surround  the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \", you will see why shortly.\")), mdx(\"p\", null, \"Now, add the following modifiers directly after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".padding(.bottom, 20)\\n                .frame(maxWidth: .infinity)\\n                .frame(height: showSearchField ? 140 : 90,alignment: showSearchField ? .center : .bottom)\\n                .background(Color.accent)\\n                .clip(shouldCurve: showSearchField)\\n\")), mdx(\"p\", null, \"Here\\u2019s what those modifiers mean:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We put a padding of 20 to the bottom of the children, not the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We set the width to be as wide as its parent which is the ZStack\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"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\\u2019s why we use the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"showSearchField\"), \" flag to set the height accordingly.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The last modifier is a custom one. We will create it next.\")), mdx(\"h2\", null, \"Curved shape\"), mdx(\"p\", null, \"In your root folder, add another folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Shapes\"), \",  and  create a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CurvedShape.swift\"), \"  inside containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CurvedShape: Shape {\\n    func path(in rect: CGRect) -> Path {\\n        let cornerRadius: CGFloat = 25\\n        var path = Path()\\n        \\n        path.move(to:  CGPoint.zero)\\n        path.addLine(to: CGPoint(x: rect.width, y: 0))\\n        path.addLine(to: CGPoint(x: rect.width, y: rect.height - cornerRadius))\\n        path.addQuadCurve(to: CGPoint(x: 0, y: rect.height - cornerRadius), control: CGPoint(x: rect.midX , y: rect.height))\\n        \\n        path.closeSubpath()\\n        \\n        return path\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Swift UI makes drawing custom shapes at little bit easier, but the process itself is the same. It\\u2019s like drawing with a pencil. Here is what we\\u2019ve done in the code above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"We use the path we\\u2019ve just created to move the pencil to the origin point .\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Then we add a line from the origin point to  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CGPoint(x: rect.width, y: 0)\"), \" which is the top-right corner.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Then we add a new line from the top-right to to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"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 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" value.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"This time we will add a quad curve which is a curve with less curvature . We do that by using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"addQuadCurve\"), \" method rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"addLine\"), \". The point \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"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. \"), mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585376226/lastpass%20clone/mvyfdasbhbph3wccydiq.gif\",\n    \"alt\": null\n  }))), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"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.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Last we close the path which will link this point   \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CGPoint(x: 0, y: rect.height - cornerRadius)\"), \" to  the origin \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CGPoint.zero\"), \".\"))), mdx(\"h2\", null, \"Curved Modifier\"), mdx(\"p\", null, \"Now, in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Modifiers\"), \" folder, add a file name  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CurvedShapeModifier.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct CurvedShapeModifier: ViewModifier {\\n    var shouldClip = false\\n    func body(content: Content) -> some View {\\n        \\n        if shouldClip {\\n            return AnyView(content.clipShape(CurvedShape()))\\n        } else {\\n            return AnyView(content)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Here is what I\\u2019ve done:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a custom modifier by conforming the struct to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ViewModifier\"), \" which then gives us the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"body(content: Content)\"), \" function. The content is the view we will apply the modification to.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then modify the content, in this case we apply a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"clipShape\"), \" modifier, passing in a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CurvedShape\"), \" object.  The view that\\u2019s returned will be a clipped version that has the exact shape that we\\u2019ve passed in.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We wrap the content in \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyView\"), \" because we are  returning different types inside the condition.\")), mdx(\"p\", null, \"Now add the following extension just below that struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nextension View {\\n    func clip(shouldCurve: Bool) -> some View {\\n        self.modifier(CurvedShape(shouldClip: shouldCurve) )\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Here we\\u2019ve just created an helper method that we can call like a built-in swift UI modifier.\"), mdx(\"p\", null, \"Command + B to build the project, and Voil\\xE0, all errors\\tare gone.\\tOpen the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NavBar.swift\"), \"  file, and  resume the preview. You should see something like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585387103/lastpass%20clone/qmti8rfhn2zmv8dv2hnr.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now change the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showSearchField\"), \" property value to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" , resume the preview, and you should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585387103/lastpass%20clone/o579s54svt7noymquqj1.png\",\n    \"alt\": null\n  }))), mdx(\"h2\", null, \"Custom search field.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container, below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \", add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if showSearchField{\\n                LCSearchField(value: self.$searchTerm)\\n                    .padding()\\n                    .offset(y: 15)\\n            }\\n\")), mdx(\"p\", null, \"This code will introduce errors because you don\\u2019t the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LCSearchField\"), \" yet. Before we create the LCSearchField, add the following to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  @State private var searchTerm = \\\"\\\"\\n\")), mdx(\"p\", null, \"We will change this when we introduce code data, but for now that will do the trick.\"), mdx(\"h2\", null, \"LCSearchField\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add a swift UI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LCSearchField.swift\"), \". Add the following properties to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" @Binding var value: String\\n var onEditingChanged: ((Bool)-> Void) = {_ in }\\n var onCommit: (() -> ()) = {}\\n\")), mdx(\"p\", null, \"As you can see the value property has a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@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).\"), mdx(\"p\", null, \"The remaining properties are closures.  \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Closures are self-contained blocks of functionality that can be passed around and used in your code.\")), mdx(\"p\", null, \"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 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onEditingChanged\"), \" parameter, and return values.  In the above closures, I return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Void\"), \" (nothing)\"), mdx(\"p\", null, \"Now replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// 1\\n  HStack {\\n            // 2\\n            Image(systemName: \\\"magnifyingglass\\\")\\n                .imageScale(.large)\\n                .padding(.leading)\\n            // 3\\n            TextField(\\\"Search ...\\\", text: self.$value, \\nonEditingChanged: self.onEditingChanged , onCommit: {\\n                \\n                self.onCommit()\\n            }).padding(.vertical, 10)\\n        }\\n\")), mdx(\"p\", null, \"Here is what we\\u2019ve done here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We put the 2 views in the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" container.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a  magnifying glass image.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create a textfield and attach the value property to the text parameter which is also a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"@Binding\"), \" that\\u2019s why we are the dollar sign before the value\\u2019s name. See how we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onEditingChanged\"), \"? We can do that because the parameter and the property have the same signature. However, instead of doing the same for the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onCommit\"), \", I call it like a function just to show you how many ways you can call a closure.\")), mdx(\"p\", null, \"Add the following modifiers directly after \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".foregroundColor(Color.gray)\\n              .background(Color.background)\\n              .cornerRadius(13)\\n              .padding()\\n              .frame(height: 45)\\n              .shadow(color: Color.darkShadow, radius: 5, x: 0, y: 5)\\n\")), mdx(\"p\", null, \"Here is a break down of the above code:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"foregroundColor\"), \" modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" to make its children\\u2019s foreground color gray.  \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We add the background color to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The remaining modifiers are pretty simple to understand.\\nThe last thing this you can do here is to add the following in the preview to silence the error you got :\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"value: .constant(\\\"\\\")\\n\")), mdx(\"p\", null, \"Your preview code should look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct LCSearchField_Previews: PreviewProvider {\\n    static var previews: some View {\\n        LCSearchField(value: .constant(\\\"\\\"))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Now resume the preview\\u2026 And  you should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585397338/lastpass%20clone/chq5el0clhr3ll4hzx69.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Navbar.swift\"), \" and resume the preview. You should see this:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585397338/lastpass%20clone/gksswxpj5l5migx9nymz.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"That\\u2019s it folks. Next, we will create the list of passwords and notes so stay tune.\\nDon\\u2019t forget to subscribe and share this article. HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#custom-curved-navigation-bar","title":"Custom Curved Navigation Bar"},{"url":"#curved-shape","title":"Curved shape"},{"url":"#curved-modifier","title":"Curved Modifier"},{"url":"#custom-search-field","title":"Custom search field."},{"url":"#lcsearchfield","title":"LCSearchField"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:44:56.767Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-12","title":"Dive into CoreData","updatedDate":"2020-06-01T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZob9tkZLtXG7nDrNdycB2PPrJV58b3kbW3X","type":"zip","id":"f425de18-1e2f-5d48-92b5-8b77564d9795","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, in the previous part we created entities for password and note as well as their respective models and view models. I had to prepare those in a separate post as it would\\u2019ve made this article way too long. In this one we  will start working with CoreData and do a little bit of refactoring. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"CoreData Manager\"), mdx(\"p\", null, \"We will put all core data related stuff in a separate file almost . In the service folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CoreDataManager.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Combine\\nimport CoreData\\nimport UIKit\\n\\nclass CoreDataManager: ObservableObject {\\n    \\n}\\n\\n\")), mdx(\"p\", null, \"This will be our CoreData playground. Then add the following property to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" private init(context: NSManagedObjectContext) {\\n        self.context = context\\n }\\n\")), mdx(\"p\", null, \"We make the initialiser private, because we want to make this class a singleton meaning we will create a shared static function that will return an instance of this class containing a valid context.\"), mdx(\"p\", null, \"Add the following code below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"context\"), \" to create that shared instance:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    static let shared = CoreDataManager(context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)\\n\")), mdx(\"p\", null, \"What we are doing here is initialising the shared instance with the context retrieved from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppDelegate\"), \". If you open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AppDelegate.swift\"), \", and look for \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"persistentContainer\"), \", you\\u2019ll find it declared as a lazy variable inside the class.\"), mdx(\"p\", null, \"You will get an error saying the following \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Value of type 'CoreDataManager' has no member 'context\\u2019\")), \", it makes sense because we haven\\u2019t created the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"context\"), \" property yet. Add the following to the top, above the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var context: NSManagedObjectContext\\n\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"A context consists of a group of related model objects that represent an internally consistent view of one or more persistent stores. Changes to managed objects are held in memory, in the associated context, until that context is saved to one or more persistent stores. A single managed object instance exists in one and only one context, but multiple copies of an object can exist in different contexts. Thus an object is unique to a particular context.\")), mdx(\"p\", null, \"The above statement means that the context is an object that keep our local and persisted state of our data in sync. Every time we set one or more of our entities, the changes will only be persisted until we save the current state using the context.\"), mdx(\"p\", null, \"Next, add the following method below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func save() -> Bool {\\n        if context.hasChanges {\\n            do {\\n                try context.save()\\n                return true\\n            } catch let error {\\n                print(\\\"Error saving changes to the context\\u2019s parent store.: \\\\(error.localizedDescription)\\\")\\n                return false\\n            }\\n        }\\n        return true\\n    }\\n    \\n\")), mdx(\"p\", null, \"In the above function, we first check if there are changes in the context, if it\\u2019s the case we call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"save()\"), \" on the context to store those changes. \"), mdx(\"h2\", null, \"Entity Updates\"), mdx(\"p\", null, \"Next, add the following function below the one above:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func updateLastUsedPassword(with id: UUID) -> Bool  {\\n        let request: NSFetchRequest<PasswordItem> = PasswordItem.fetchRequest() as! NSFetchRequest<PasswordItem>\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", id.uuidString)\\n        \\n        do {\\n            let results = try context.fetch(request)\\n            results[0].setValue(Date(), forKey: \\\"lastUsed\\\")\\n            return save()\\n            \\n        } catch let error {\\n            print(error)\\n        }\\n        \\n        return false\\n    }\\n\")), mdx(\"p\", null, \"This one will be called every time a user views a particular password\\u2019s details. What happening is that we use the password\\u2019s id to fetch the whole object from the database, update its lastUsed date to current date and save it again. \"), mdx(\"p\", null, \"Next, add the following below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" func setFavoritePassword(_ password: PasswordViewModel) -> Bool  {\\n        let request: NSFetchRequest<PasswordItem> = PasswordItem.fetchRequest() as! NSFetchRequest<PasswordItem>\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", password.id.uuidString)\\n        let isFavorite = password.isFavorite ? 0 : 1\\n        do {\\n            let results = try context.fetch(request)\\n            results[0].setValue(isFavorite, forKey: \\\"isFavorite\\\")\\n            return save()\\n            \\n        } catch let error {\\n            print(error)\\n        }\\n        \\n        return false\\n    }\\n\")), mdx(\"p\", null, \"This one will be called every time a user toggles the favourite button in the details view. What happening is that we use the password\\u2019s id to fetch the whole object from the database again, update its \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isFavorite\"), \" attribute to 1 or 0 \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"(true or false)\"), \" and save it again. \"), mdx(\"p\", null, \"Next, add the following 2 methods below the ones above:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func updateLastUsedNote(with id: UUID) -> Bool  {\\n        let request: NSFetchRequest<NoteItem> = NoteItem.fetchRequest() as! NSFetchRequest<NoteItem>\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", id.uuidString)\\n        \\n        do {\\n            let results = try context.fetch(request)\\n            results[0].setValue(Date(), forKey: \\\"lastUsed\\\")\\n            return save()\\n            \\n        } catch let error {\\n            print(error)\\n        }\\n        \\n        return false\\n    }\\n    \\n    func setFavoriteNote(_ note: NoteViewModel) -> Bool  {\\n        let request: NSFetchRequest<PasswordItem> = PasswordItem.fetchRequest() as! NSFetchRequest<PasswordItem>\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", note.id.uuidString)\\n        \\n        let isFavorite = note.isFavorite ? 0 : 1\\n        \\n        do {\\n            let results = try context.fetch(request)\\n            results[0].setValue(isFavorite , forKey: \\\"isFavorite\\\")\\n            return save()\\n            \\n        } catch let error {\\n            print(error)\\n        }\\n        \\n        return false\\n    }\\n    \\n\")), mdx(\"p\", null, \"The above methods do the same as the ones we\\u2019ve created for the password, but these will be used to update the note.\"), mdx(\"h2\", null, \"Search\"), mdx(\"p\", null, \"Before creating the search function, let\\u2019s first add the following properties to the top of thee struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @Published var notePredicate = NSPredicate(value: true)\\n    @Published var passwordPredicate = NSPredicate(value: true)\\n    @Published var sortDescriptor = NSSortDescriptor(key: \\\"createdAt\\\", ascending: false)\\n    \\n    @Published var searchTerm = \\\"\\\"\\n    \\n    @Published var showNotes = true\\n    @Published var showPasswords = true\\n    \\n    private var cancellableSet: Set<AnyCancellable> = []\\n\")), mdx(\"p\", null, \"Here is an explanation of what each of those properties will do:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first 2 predicates are the same, just for different entity. We will use the predicate to filter out the data based on a condition. We used predicates to retrieve item matching a particular \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"id\"), \" attribute.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The sortDescriptor will be used to sort notes and password based on \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"lastUsed\"), \" when we want to see the most used passwords and notes. The default key is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createdAt\"), \" atrribute which we will use to sort the data based on the creation date.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"searchTerm\"), \" is  self-explanatory.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The 2 boolean properties will be used to hide notes or passwords section depending on which filter is selected.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"cancellableSet\"), \" will hold the cancellable subscriptions  to avoid premature completion of the search subscription\\nNow let\\u2019s implement the search process. Below everything, add the following block of code:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func performSearch()  {\\n        let passwordSearchPublisher = self.$searchTerm.debounce(for: 0.5, scheduler: RunLoop.main)\\n            .removeDuplicates()\\n            .map { term in\\n                \\n                return term.isEmpty ? NSPredicate.init(value: true) :  NSPredicate(format: \\\"site CONTAINS[c] %@ || username CONTAINS[c] %@\\\", term, term)\\n        }.eraseToAnyPublisher()\\n        \\n        let noteSearchPublisher = self.$searchTerm.debounce(for: 0.5, scheduler: RunLoop.main)\\n            .removeDuplicates()\\n            .map { term in\\n                return term.isEmpty ? NSPredicate.init(value: true) :   NSPredicate(format: \\\"name CONTAINS[c] %@\\\", term)\\n        }.eraseToAnyPublisher()\\n        \\n        Publishers.CombineLatest(passwordSearchPublisher, noteSearchPublisher)\\n            .receive(on: DispatchQueue.main)\\n            .sink {[unowned self] (passwordPred, notepred) in\\n                self.passwordPredicate = passwordPred\\n                self.notePredicate = notepred\\n        }.store(in: &self.cancellableSet)\\n    }\\n\")), mdx(\"p\", null, \"Here is what we are doing above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"passwordPublisher\"), \" using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"debounce\"), \" on the search term. The publisher will be of type \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyPublisher<NSPredicate, Never>\"), \". As you can see the return statement in the map closure returns a predicate, for the password we filter based on the site name or username.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"notePublisher\"), \" which will be filtered on the name only. One could also include the content, but let\\u2019s just keep it simple.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CombineLatest\"), \" publisher, we combine the 2 publishers and subscribe to the resulting publisher, and set those predicates we created earlier.\\nLast but not least in this class, we need created a function that will be used to apply filters. Below the function you\\u2019ve created, add the following:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func applyFilter(_ filter: Filter){\\n        self.sortDescriptor = NSSortDescriptor(keyPath: \\\\PasswordItem.createdAt, ascending: false)\\n        self.passwordPredicate = NSPredicate(value: true)\\n        self.notePredicate =  NSPredicate(value: true)\\n       \\n        switch filter {\\n        case .MostUsed:\\n            self.sortDescriptor = NSSortDescriptor(keyPath: \\\\PasswordItem.lastUsed, ascending: false)\\n        case .Favorites:\\n            self.passwordPredicate = NSPredicate(format: \\\"isFavorite = %@\\\", \\\"1\\\")\\n            self.notePredicate = NSPredicate(format: \\\"isFavorite = %@\\\", \\\"1\\\")\\n        case .Notes:\\n            self.showPasswords = false\\n            self.showNotes = true\\n        case .Passwords:\\n            self.showNotes = false\\n            self.showPasswords = true\\n        case .AllItems:\\n            self.showNotes = true\\n            self.showPasswords = true\\n        }\\n    }\\n\\n\")), mdx(\"p\", null, \"The above is pretty self-explanatory as well. We first reset the predicates and sortDescriptor to default values, then set corresponding values in the switch statements.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"With that, we are done for this part. We are still missing the deletion functionality which is almost the same as the update. I\\u2019ll leave that to you, play with the project be creative, be liquid. In the next one we will integrate \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoreData\"), \" in views, stay tuned. Please feel free to share this article, subscribe if you haven\\u2019t done so already.  If you have any question, send me an email. Happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#coredata-manager","title":"CoreData Manager"},{"url":"#entity-updates","title":"Entity Updates"},{"url":"#search","title":"Search"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:38:16.170Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-8","title":"Edit Detail View","updatedDate":"2020-05-04T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZqU8lkZ0QFGESa0Wkhov3X8vqLUikjBDBok","type":"zip","id":"c2673845-1e0d-57ca-b0c0-82718580cf1e","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, in the previous part, we created the user interface for the home view. In this one, we  will make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \". As its name implies, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \" will be used to edit a password or a note as well as view their respective details.  To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"DetailHeader\"), mdx(\"p\", null, \"For simplicity, we will have 2 types of information to keep safe, but the real LastPass service has more than 2 therefore a user will only be able to save passwords and notes. You could also extend the app by yourself to include credit cards information, Identification number and so on\\u2026 \"), mdx(\"p\", null, \"To track the type of info, we need some kind of model, so let\\u2019s use an enum for that. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Utils\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FormType.swift\"), \" with the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum FormType: String{\\n    case Password\\n    case Note\\n}\\n\\n\")), mdx(\"p\", null, \"With that in place, add another file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FormHeader.swift\"), \" in the  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder. \"), mdx(\"p\", null, \"Now add the following to the top of  struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Binding var showDetails: Bool\\n@Binding var formType: FormType\\n    \\n\")), mdx(\"p\", null, \"Then replace the content of the body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let imageName = \\\"https://www.amazon.com\\\".getImageName()\\n\\nreturn HStack {\\n                Button(action: {\\n                    HapticFeedback.generate()\\n                    withAnimation(.easeInOut) {\\n                        self.showDetails.toggle()\\n                    }\\n                }) {\\n                    Image(systemName: \\\"chevron.left\\\")\\n                        .imageScale(.large)\\n                        .foregroundColor(Color.text)\\n                }\\n                \\n                Picker(\\\"\\\", selection:  self.$formType) {\\n                    Text(FormType.Password.rawValue).tag(FormType.Password)\\n                    Text(FormType.Note.rawValue).tag(FormType.Note)\\n                }.pickerStyle(SegmentedPickerStyle())\\n                    .background(Color.background)\\n                    .padding()\\n                \\n                Image(imageName)\\n                    .resizable()\\n                    .aspectRatio(contentMode: .fit)\\n                    .frame(width: 40, height: 40)\\n                    .foregroundColor(Color.gray)\\n                \\n            }.padding()\\n                .padding(.top)\\n                .frame(maxWidth: .infinity)\\n                .frame(height: 100)\\n                .background(Color.background)\\n                .shadow(color: Color.darkShadow, radius: 5, x: 0, y: 5)\\n\")), mdx(\"p\", null, \"Here is what we are doing above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Assets.xcassets\"), \" I put are a bunch of icons for most of the major websites.  I have created a helper extension method to retrieve the name of the asset from a website\\u2019s URL that I will show you shortly. For now, we are hard-coding the URL, but later on,  this will be dynamic.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a button that will be used to dismiss the details view. We also generate some haptic feedback  when one clicks on the button. If you are not aware, we created the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"HapticFeedback\"), \" helper struct in one of the previous parts.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create a Picker that, in our case, has only 2 options I talked about earlier.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create an image that will display which website we are viewing or editing.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And last we add a bunch of modifiers to style \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" container itself.\")), mdx(\"p\", null, \"Let\\u2019s now create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getImageName\"), \" extension method. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"StringExt.swift\"), \", and put the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getImageName() -> String {\\n         let urlComponents = self.lowercased().split(separator: \\\".\\\")\\n         let count = urlComponents.count\\n\\n         if urlComponents.isEmpty{\\n             return \\\"placeholder\\\"\\n         }\\n         return count > 2 ? String(urlComponents[count - 2]) : String(urlComponents.first ?? \\\"\\\")\\n     }\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We need to convert the string to lowercase since all asset names are in lowercase, and then split the string based on dots.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The split method will return an array of strings, so we need to check whether the array is empty and return a placeholder if it\\u2019s the case, otherwise we return the name of the website. This method will only work for urls with the following form :\\n\", mdx(\"em\", {\n    parentName: \"li\"\n  }, mdx(\"a\", _extends({\n    parentName: \"em\"\n  }, {\n    \"href\": \"https://hostname.domain.com\"\n  }), \"https://hostname.domain.com\")), \" or \", mdx(\"em\", {\n    parentName: \"li\"\n  }, mdx(\"a\", _extends({\n    parentName: \"em\"\n  }, {\n    \"href\": \"https://domain.com\"\n  }), \"https://domain.com\")), \"\\nAny other case might fail, and return the placeholder icon name.\")), mdx(\"p\", null, \"Here is a preview of what we\\u2019ve just built:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587192047/lastpass%20clone/bj2enia19uxjn8gt6g1y.png\",\n    \"alt\": null\n  }))), mdx(\"h2\", null, \"Edit Form View\"), mdx(\"p\", null, \"Now is the time to create the edit form. Contrary to its name, this form will not only be used to edit, but also to view and add new information. So, create a new swiftUI file in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"\\xA0EditFormView.swift\"), \".\"), mdx(\"p\", null, \"Now, add the following methods below body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   fileprivate func createBodyContent() -> some View {\\n        EmptyView()\\n    }\\n    \\n    fileprivate func createNoteForm() -> some View{\\n         EmptyView()\\n    }\\n      \\n      fileprivate func createPasswordForm()-> some View{\\n        EmptyView()\\n      }\\n\")), mdx(\"p\", null, \"We will work on those 3 methods shortly, but first replace the text in body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" SubscriptionView(content:  createBodyContent(), publisher: NotificationCenter.keyboardPublisher) { rect in\\n                   withAnimation {\\n                       self.formOffsetY = rect.height > 0 ? -150 : 0\\n                   }\\n      }\\n\")), mdx(\"p\", null, \"Here is a quick explanation of what we\\u2019ve just done above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We use the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"SubscriptionView\"), \"  to subscribe to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"keyboardPublisher\"), \"  events relayed by the keyboard\\u2019s notification.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then set the offset accordingly. When the keyboard pops up, the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"rect.height\"), \" will be greater than 0, hence the need to move the content a little bit higher.\")), mdx(\"p\", null, \"We have an compiler error,  add the following property to the top to get rid of it:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   @State private var formOffsetY: CGFloat = 0\\n\")), mdx(\"h2\", null, \"Password & Note Form\"), mdx(\"p\", null, \"Before creating the password, let's first create some properties to the top that will hold our states.\"), mdx(\"p\", null, \"Add this above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"formOffsetY\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Binding var showDetails: Bool\\n@State private var username = \\\"\\\"\\n    @State private var password = \\\"\\\"\\n    @State private var website = \\\"\\\"\\n    @State private var passwordNote = \\\"\\\"\\n    @State private var noteName = \\\"\\\"\\n    @State private var noteContent = \\\"\\\"\\n    @State private var favoriteImage = \\\"heart\\\"\\n\\n    \\n    @State private var formType = FormType.Password\\n\")), mdx(\"p\", null, \"Next, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordForm\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" VStack(spacing: 40) {\\n                   SharedTextfield(value: self.$username)\\n                   VStack(alignment: .leading) {\\n                       PasswordField(value: self.$password, header: \\\"Password\\\",placeholder: \\\"Make sure the password is secure\\\")\\n                       Button(action: {\\n                       }) {\\n                           Text(\\\"Generate password\\\")\\n                               .foregroundColor(.accent)\\n                       }\\n                   }\\n                   SharedTextfield(value: self.$website, header: \\\"Website\\\", placeholder: \\\"https://\\\")\\n                   SharedTextfield(value: self.$passwordNote, header: \\\"Note\\\",placeholder: \\\"You can write anything here...\\\", showUnderline: false)\\n               }.padding()\\n      }\\n\")), mdx(\"p\", null, \"This will need to be refactored later on in a separate view. The above code does anything special, it just stacks views vertically. We created SharedTextfield in one of the previous parts, in case you are wondering where it\\u2019s coming from.\"), mdx(\"p\", null, \"Next, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createNoteForm\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack(spacing: 40) {\\n            SharedTextfield(value: self.$noteName, header: \\\"Title\\\",placeholder: \\\"Your note title goes here\\\")\\n            SharedTextfield(value: self.$noteContent, header: \\\"Note\\\",placeholder: \\\"You can write anything here...\\\", showUnderline: false)\\n            \\n        }.padding()\\n\")), mdx(\"p\", null, \"Here we only need 2 fields for the note, the note\\u2019s name or title and content. So we simply  stack them vertically.\"), mdx(\"p\", null, \"Let\\u2019s now put the 2 together in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createBodyContent\"), \" method. Now replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" in the aforementioned method with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" VStack {\\n            FormHeader(showDetails: self.$showDetails, formType: self.$formType)\\n            ScrollView {\\n                VStack {\\n                    if formType == FormType.Password{\\n                        createPasswordForm()\\n                    }\\n                    else {\\n                        createNoteForm()\\n                    }\\n                    \\n                }\\n            }\\n        }\\n\")), mdx(\"p\", null, \"Here is what we are doing here: \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We stack the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"FormHeader\"), \" and the password or note form vertically\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We defined a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"formType\"), \" state above that we use here to check which type of form we are working with, and display it appropriately.\\nWe are still missing a couple of details before we finish this part. \")), mdx(\"p\", null, \"Now, add the following inside the inner \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containing the forms, and below those forms:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" HStack(spacing: 20) {\\n                    createSaveButton()\\n                    createHeartButton()\\n                }.padding(.horizontal)\\n\")), mdx(\"p\", null, \"We still don\\u2019t have those 2 methods, hence the errors. Add them to silence those errors:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  fileprivate func createSaveButton() -> LCButton {\\n           return LCButton(text: \\\"Save\\\", backgroundColor: Color.accent) {\\n               switch self.formType{\\n               case .Password:\\n                   break\\n               case .Note:\\n                   break\\n               }\\n           }\\n       }\\n       \\n    fileprivate func createHeartButton() -> some View {\\n        \\n        Button(action: {\\n            \\n        }) {\\n            Image(systemName: self.favoriteImage)\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 30)\\n                .foregroundColor( favoriteImage == \\\"heart\\\" ? Color.gray : Color.orange)\\n        }\\n    }\\n\")), mdx(\"p\", null, \"Those two methods create nothing else but two buttons that don\\u2019t do anything yet, we will implement their actions in a later article.\"), mdx(\"p\", null, \"Last, but not least we need to add a bunch of modifiers to style whole body to make it look presentable. So, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createBodyContent\"), \" function, add the following modifiers to the outer \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container : \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".background(Color.background)\\n            .cornerRadius(20)\\n            .padding()\\n            .padding(.top, 40)\\n            .neumorphic()\\n\")), mdx(\"p\", null, \"Here is what we have now:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587192494/lastpass%20clone/dlv3fxbmdpye1kqxt56d.png\",\n    \"alt\": null\n  }))), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"It\\u2019s not pretty yet, but things start to come together. With this, we finish this part, stay tuned for the next one. If you have any question, send me an email. Don\\u2019t forget to subscribe if you haven\\u2019t done so already, and share this article. HAPPY CODING!!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#detailheader","title":"DetailHeader"},{"url":"#edit-form-view","title":"Edit Form View"},{"url":"#password--note-form","title":"Password & Note Form"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T18:31:30.140Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-6","title":"Filter View","updatedDate":"2020-04-20T00:00+01:00","downloads":[{"url":" https://my.pcloud.com/publink/show?code=XZou5xkZR9bi4eXHTY4DvgF4f199uzvYeyuy","type":"zip","id":"2118af57-2409-55fc-ae7c-14306d6cb4b8","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"In the previous article, we built a custom navigation bar. If you haven\\u2019t read it , check it out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/lastpass-redesigned-clone-part-5/\"\n  }), \"here\"), \". In this one, we will create the filter header which will be an horizontal scrollable list. Let\\u2019s. To follow along, get the previous lesson source code. Get started.\"), mdx(\"h2\", null, \"Filter View\"), mdx(\"p\", null, \"Like I\\u2019ve just said in the intro, the header will contain a Scrollview where the filterViews will be the items scrolled horizontally, so let\\u2019s first  create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FilterView\"), \" that will be used as a cell.\"), mdx(\"p\", null, \"Create a swift UI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FilterView.swift\"), \" in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder. Put the following properties to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var filter: Filter\\n    @Binding var isSelected: Bool\\n    var onSelect: ((Filter)->()) = {_ in }\\n\")), mdx(\"p\", null, \"As you may have noticed, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedFilter\"), \" has a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Binding\"), \" property wrapper because we want the view\\u2019s body to be re-computed when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSelected\"), \" state changes. \"), mdx(\"p\", null, \"And the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSelect\"), \" is a closure that will be called whenever we select one of the the filter item.  You will see how we\\u2019ll call this later on. Before we implement the body, we still need to create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Filter\"), \" enum to silence the error you are getting. Create a folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Utils\"), \", add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Filter.swift\"), \" inside containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum Filter: String, CaseIterable{\\n    case AllItems = \\\"All Items\\\"\\n    case Passwords\\n    case Notes\\n    case MostUsed = \\\"Most Used\\\"\\n    case Favorites\\n}\\n\")), mdx(\"p\", null, \"Command + B to build the project, and the error should leave you alone except the one produced by the missing binding parameter in the preview. So replace the content of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"previews\"), \" static property with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        FilterView(filter: .AllItems, isSelected: .constant(false))\\n\\n\")), mdx(\"p\", null, \"With that in place, you  are totally clear.\"), mdx(\"p\", null, \"Now, replace the content of body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" ZStack(alignment: .bottom) {\\n                   Text(filter.rawValue)\\n                   .layoutPriority(1)\\n                       .foregroundColor(isSelected ? Color.text : Color.gray)\\n                   .padding(5)\\n        }\\n\")), mdx(\"p\", null, \"The only modifier to note here is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"layoutPriority\"), \". We pass in 1  to make the text value fit perfectly and not being truncated. The default value is 0. You will see why we had to add this modifier later on. \"), mdx(\"p\", null, \"Next add the following code inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if isSelected{\\n                Rectangle()\\n                    .frame(width: 50, height: 3)\\n                    .foregroundColor(Color.accent)\\n                    .cornerRadius(2)\\n                    .transition(.scale)\\n            }\\n\")), mdx(\"p\", null, \"We want to the horizontal line only when the filter is selected. Here  is a quick explanation of what the above code does:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We use a rectangle which we flattened to make it a line by giving it a height of 3 and a width of 50. One could also use a rotated Capsule by 90 degrees and the result would be the same with more lines of code.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We give the rectangle a foreground color rather than background because it\\u2019s a shape.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The corner radius is understandable. We then add a scale transition that will make the line scale from the middle when the filter is selected.\")), mdx(\"p\", null, \"Here is a preview of what we\\u2019ve just created:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585550839/lastpass%20clone/ka5pgt5ooju8ncibt6vx.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Last for the filter view, add the following directly on the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".padding().onTapGesture {\\n            self.onSelect(self.filter)\\n        }\\n\")), mdx(\"p\", null, \"Here, we\\u2019ve added some padding around the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FilterView\"), \" as well as a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"tapGesture\"), \". Inside the tapGesture\\u2019s block we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSelect\"), \" closure to send the selected filter to the parent view container.\"), mdx(\"h2\", null, \"Header View\"), mdx(\"p\", null, \"Create a swift UI file in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HeaderView.swift\"), \".  Add the following properties to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var selectedFilter = Filter.AllItems\\n    var onSelect: ((Filter)->()) = {_ in }\\n\")), mdx(\"p\", null, \"We make  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedFilter\"), \"  mutable by preceding it  with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@State\"), \" property wrapper that will be used to update the User interface when it\\u2019s set.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"@State:\"), \" SwiftUI manages the storage of any property you declare as a state. When the state value changes, the view invalidates its appearance and recomputes the body. Use the state as the single source of truth for a given view.\\nNow replace the content of the body with the following:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ScrollView(.horizontal, showsIndicators: false) {\\n            HStack{\\n                ForEach(Filter.allCases, id: \\\\.self){ filter in\\n                    FilterView(filter: filter, isSelected: .constant(self.selectedFilter == filter), onSelect: { selected in\\n                            // Replace this\\n                    })\\n                }\\n            }\\n        }\\n\")), mdx(\"p\", null, \"As you can see the filters will be horizontally scrollable. Because the Filter enum conform to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CaseIterable\"), \",  we can get all the cases as an array which in turn allows us to iterate through it using swiftUI\\u2019s \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \". We then return an instance of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FilterView\"), \" for each filter.\"), mdx(\"p\", null, \"Being a binding property wrapper,   the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSelected\"), \" parameter  is set  by giving it a  constant value using the static function  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"contant(value:)\"), \" which returns a binding.\"), mdx(\"p\", null, \"Now replace the comment inside the onSelect block with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"withAnimation(.spring()) {\\n                            self.selectedFilter = selected\\n                        }\\n                        \\n                        self.onSelect(selected)\\n\")), mdx(\"p\", null, \"Here, we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedFilter\"), \" property inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"withAnimation\"), \" block to animate the selection. At the same time, we want to call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onSelect\"), \" closure to pass the value to the parent view container.\"), mdx(\"p\", null, \"Now is the time to explain the benefit of the \\u2018\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"layoutPriority\"), \"\\u2019 modifier. Go back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FilterView\"), \" and comment out the   \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".layoutPriority(1)\"), \", then inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HeaderView\"), \", resume the preview and click play to make it interactive.  Now, select on one of the filter item\\u2026 You should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585601972/lastpass%20clone/ncmapjofk9aqndzagivm.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"As you can see the password is being truncated. This happens because the  parent does not change its width to accommodate the widest child. So to make the parent view to stretch or shrink sooner to fit a child view, we need to change the layout priority of the child that we know will be wider. Now go back and un-comment the layoutPriority in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FilterView\"), \" . Try again selecting the filters, and everything should work fine. \"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"And with this, we conclude this part. In the next part we will put this component and the Navigation bar together after creating the list using neumorphic  design. Feel free to share this article and subscribe if you haven\\u2019t done so already. Thank you for reading HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#filter-view","title":"Filter View"},{"url":"#header-view","title":"Header View"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T18:25:19.788Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-4","title":"Further  authentication  improvement","updatedDate":"2020-04-06T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZsimgkZvKifGIDiONFwheEa4rmwY8kR19ky","type":"zip","id":"cdd9b93a-cdca-5da2-a6f6-6f862201725f","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys! Last week\\u2019s post,  we implemented local authentication using Cryptokit , Touch ID and Face ID. In this one, we will improve the authentication manager, and finalise all authentication related views. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"Authentication manager improvement.\"), mdx(\"p\", null, \"There are a few things we need to do in order to allow the said user to login or sign up.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"On the sign up view, if there\\u2019s already an account associated with the device, we need to show a message telling the user that there\\u2019s an existing account.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"On the login view, we need to tell the user that the provided details are not correct upon clicking the login button.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We need to clear the inputs when switching from login view to sign up and vice-versa.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We will also allow the user to reset the password.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Right now if you delete the app from your phone, all existing credentials will not be deleted from keychain. This behaviour will give the user headaches when he re-installs the app in the future having used the app in the app. So, we will need to find a way of getting around that.\\nThat\\u2019s our agenda for this article. You should try figuring out how to do this by yourself first, and then comeback to see how I tackled the aforementioned problems. Let\\u2019s get started.\")), mdx(\"h2\", null, \"Sign up\"), mdx(\"p\", null, \"Let\\u2019s start with the sign up view. In the Authentication Manager class, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccount\"), \" method with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     func createAccount() -> Bool {\\n        guard !hasAccount() else { return false }\\n        let hashedPassword = hashPassword(password)\\n        let emailResult = keychain.set(email.lowercased(), forKey: AuthKeys.email, withAccess: .accessibleWhenPasscodeSetThisDeviceOnly)\\n        let passwordResult = keychain.set(hashedPassword, forKey: AuthKeys.password, withAccess: .accessibleWhenPasscodeSetThisDeviceOnly)\\n        if emailResult && passwordResult {\\n            login()\\n            return true\\n        }\\n        return false\\n    }\\n\")), mdx(\"p\", null, \"What we\\u2019ve just done here is returning a boolean value indicating whether the account creation process has been successful or a failure. We will modify the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccountCreationView\"), \" next.\"), mdx(\"p\", null, \"Open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccountCreationView\"), \", and add the following property to the top of the struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var showAlert = false\\n\")), mdx(\"p\", null, \"Then replace the line inside the sign up button\\u2019s action like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.showAlert = !self.authManager.createAccount()\\n\")), mdx(\"p\", null, \"Now, directly after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".disabled\"), \" modifier, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" .alert(isPresented: self.$showAlert) {\\n                        Alert(title: Text(\\\"Error\\\") , message: Text(\\\"Oops! Seems like there's already an account associated with this device. You need to login instead.\\\") , dismissButton: .default(Text(\\\"Ok\\\")) )\\n\")), mdx(\"p\", null, \"We will show an alert telling the user that there is already an account associated with the device. And with that, we say bye bye to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccountCreationView\"), \" .\"), mdx(\"h2\", null, \"Login View\"), mdx(\"p\", null, \"Before we start modifying the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LoginView\"), \" , let\\u2019s first take care of small things in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \". So, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"authenticate\"), \" method, add the following condition before everything:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       if !hasAccount(){\\n            return false\\n        }\\n\")), mdx(\"p\", null, \"Here, we first check if the user is already registered, otherwise we return false because there\\u2019s no need to proceed any further if there is no account.\"), mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoginView.swift\"), \", and add the following property to the top of the struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var showAlert = false\\n\")), mdx(\"p\", null, \"Then replace the line inside the login button\\u2019s action like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.showAlert = !self.authManager.authenticate()\\n\")), mdx(\"p\", null, \"Now, directly after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".disabled\"), \"modifier, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" .alert(isPresented: self.$showAlert) {\\n                        if self.authManager.hasAccount(){\\n                            return Alert(title: Text(\\\"Error\\\") , message: Text(\\\"Oops!The credentials you've provided are not correct, try again.\\\") , dismissButton: Alert.Button.default(Text(\\\"Ok\\\")) )\\n                        }\\n                            return Alert(title: Text(\\\"Error\\\") , message: Text(\\\"Oops! You don't have an account yet, sign uo instead.\\\") , dismissButton: Alert.Button.default(Text(\\\"Ok\\\")) )\\n  }\\n\")), mdx(\"p\", null, \"Run the project and test both the signup and login views. You should be testing the app on a real device.\"), mdx(\"h2\", null, \"Clear inputs when switching sign up and login views.\"), mdx(\"p\", null, \"Right now, when you write something in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LoginView\"), \", and then switch to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CreateAccountView\"), \" without clearing input fields, you will still see the same values.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccountCreationView\"), \", add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"goToLoginButton\"), \"\\u2019s action block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.authManager.email = \\\"\\\"\\nself.authManager.password = \\\"\\\"\\nself.authManager.confirmedPassword = \\\"\\\"\\n\")), mdx(\"p\", null, \"Then in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LoginView\"), \", add this in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccountButton\"), \"\\u2019s action block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.authManager.email = \\\"\\\"\\nself.authManager.password = \\\"\\\"\\n\")), mdx(\"p\", null, \"That will clear all input fields. \"), mdx(\"h2\", null, \"AuthenticationManager\"), mdx(\"p\", null, \"First of all, let\\u2019s make some changes in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \"  class.\"), mdx(\"p\", null, \"Open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" class and rename the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"authenticateWithBiometric\"), \"  function to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loginWithBiometric\"), \". \"), mdx(\"p\", null, \"We need 3 states when authenticating with biometric, add this \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"enum\"), \"  inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" class that will hold those states: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    enum BiometricResult {\\n        case success\\n        case failure\\n        case none\\n    }\\n\")), mdx(\"p\", null, \"Before the authentication process begins, the initial state will be \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"none\"), \", then on success and failure, we will return the remaining  2.\"), mdx(\"p\", null, \"Now find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"biometryPublisher\"), \", and replace it with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    private lazy var biometryPublisher: Future<BiometricResult, Never> = {\\n          Future<BiometricResult, Never> {[unowned self] promise in\\n               let myLocalizedReasonString = \\\"Replace with your description explaining why you want to use biometrics\\\"\\n               var authError: NSError?\\n               self.laContext.localizedFallbackTitle = \\\"Please use your Passcode\\\"\\n               if self.canAuthenticate(error: &authError) {\\n                   self.laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in\\n                    return  promise(.success(BiometricResult.success))\\n                   }\\n               } else {\\n                   print(authError ?? \\\"\\\")\\n                return promise(.success(BiometricResult.failure))\\n               }\\n           }\\n       }()\\n\")), mdx(\"p\", null, \"What we\\u2019ve just done here is replacing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Bool\"), \" return type with  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BiometricResult\"), \" return type, and change all places affected with that change inside the block. \"), mdx(\"p\", null, \"We will need to change the content of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loginWithBiometric\"), \" method  , so find it, and replace the content with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"guard hasAccount() else { return }\\n        biometryPublisher\\n            .receive(on: DispatchQueue.main)\\n            .sink(receiveValue: { result in\\n                switch result{\\n                case .success:\\n                    self.isLoggedIn = true\\n                case .failure:\\n                    self.isLoggedIn = false\\n                    print(\\\"Error authenticating with biometrics\\\")\\n                case .none:\\n                    break\\n                }\\n            })\\n            .store(in: &self.cancellableSet)\\n\")), mdx(\"p\", null, \"We replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assign\"), \" operator with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink\"), \", which are both used to subscribe to publishers, because we want to set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isLoggedIn\"), \" manually using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BiometricResult\"), \" states passed down the publisher pipeline.  The rest of the code is pretty self-explanatory .\"), mdx(\"p\", null, \"Here are apple\\u2019s definitions of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assign\"), \": \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink(receiveCompletion:receiveValue:)\"), \" takes two closures. The first closure executes when it receives Subscribers.Completion, which is an enumeration that indicates whether the publisher finished normally or failed with an error. The second closure executes when it receives an element from the publisher.\"), mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assign(to:on:)\"), \" immediately assigns every element it receives to a property of a given object, using a key path to indicate the property.\")), mdx(\"p\", null, \"Now let\\u2019s implement the method that will be used in the popup. It does the same thing as the one we\\u2019ve just worked on, except that we are still using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assign\"), \" operator. So add the following  below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loginWithBiometric\"), \" function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func authenticateWithBiometric()  {\\n        guard hasAccount() else { return }\\n        biometryPublisher\\n            .receive(on: DispatchQueue.main)\\n            .assign(to: \\\\.biometricResult, on: self)\\n            .store(in: &self.cancellableSet)\\n        \\n    }\\n    \\n\")), mdx(\"p\", null, \"We don\\u2019t have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"biometricResult\"), \" property yet, add it to the top like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published var biometricResult = BiometricResult.none\\n\")), mdx(\"p\", null, \"We set the initial value to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".`none\"), \" like I told you earlier.  We assign a new value coming from the publisher\\u2019s pipeline whether it\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \". We will use it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Loginview\"), \" shortly.\"), mdx(\"p\", null, \"We need to create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"resetPasswordPublisher\"), \" that will listen and validate the reset password entered by the user. Let\\u2019s just do that:\"), mdx(\"p\", null, \"Add the following to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published var resetPassword = \\\"\\\"\\n@Published var resetPasswordValidation = FormValidation()\\n\")), mdx(\"p\", null, \"Next add this below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"passwordPublisher\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" private var resetPasswordPublisher: AnyPublisher<FormValidation, Never> {\\n         self.$resetPassword.debounce(for: 0.2, scheduler: RunLoop.main)\\n             .removeDuplicates()\\n             .map { password in\\n                 \\n                 if password.isEmpty{\\n                     return FormValidation(success: false, message: \\\"\\\")\\n                 }\\n                 if password.count < Config.recommendedLength{\\n                     return FormValidation(success: false, message: \\\"The password length must be greater than \\\\(Config.recommendedLength) \\\")\\n                 }\\n                 \\n                 \\n                 if !Config.passwordPredicate.evaluate(with: password){\\n                     return FormValidation(success: false, message: \\\"The password is must contain numbers, uppercase and special characters\\\")\\n                 }\\n                 \\n                 return FormValidation(success: true, message: \\\"\\\")\\n         }.eraseToAnyPublisher()\\n     }\\n    \\n\")), mdx(\"p\", null, \"This property is the exact copy of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"passwordPublisher\"), \" Check out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/lastpass-clone-pt2-combine/\"\n  }), \"this post\"), \" to know more about I have done above.\\nThen add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function to subscribe to the above publisher:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"      resetPasswordPublisher\\n               .assign(to: \\\\.resetPasswordValidation, on: self)\\n               .store(in: &self.cancellableSet)\\n\")), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"hashPassword\"), \" with this one:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    private func hashPassword(_ password: String, reset: Bool = false) -> String {\\n        var salt = \\\"\\\"\\n        \\n        if let savedSalt = keychain.get(AuthKeys.salt), !reset{\\n            salt = savedSalt\\n        } else {\\n            let key = SymmetricKey(size: .bits256)\\n            salt = key.withUnsafeBytes({ Data(Array($0)).base64EncodedString() })\\n            keychain.set(salt, forKey: AuthKeys.salt)\\n        }\\n\\n        guard let data = \\\"\\\\(password)\\\\(salt)\\\".data(using: .utf8) else { return \\\"\\\" }\\n        let digest = SHA256.hash(data: data)\\n        return digest.map{String(format: \\\"%02hhx\\\", $0)}.joined()\\n    }\\n\")), mdx(\"p\", null, \"The function is still the same, we\\u2019ve just added a new boolean parameter to check whether the function is being used for password reset or not. We don\\u2019t want to re-use the same salt for  password reset. It is safer to generate a new salt for each password reset.\"), mdx(\"p\", null, \"Next add the following function below :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func saveResetPassword() -> Bool {\\n        let hashedPassword = hashPassword(resetPassword, reset: true)\\n        let passwordResult = keychain.set(hashedPassword, forKey: AuthKeys.password, withAccess: .accessibleWhenPasscodeSetThisDeviceOnly)\\n        return passwordResult\\n    }\\n\\n\")), mdx(\"p\", null, \"This one will be used to save the new hashed password in Keychain.\"), mdx(\"h2\", null, \"Views\"), mdx(\"p\", null, \"Now is the time to create a reset password mechanism. In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LoginView\"), \"  struct, below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccountButton\"), \" function, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createResetPasswordButton() -> some View {\\n           return Button(action: {\\n               withAnimation(.spring()) {\\n                   self.showCreateAccount.toggle()\\n               }\\n           }) {\\n               HStack {\\n                   Image(systemName: \\\"arrow.counterclockwise\\\")\\n                   .resizable()\\n                       .aspectRatio(contentMode: .fit)\\n                   .frame(height: 20)\\n                       .foregroundColor(Color.darkerAccent)\\n                   Text(\\\"Reset Password\\\")\\n                       .accentColor(Color.darkerAccent)\\n               }\\n           }\\n       }\\n\")), mdx(\"p\", null, \"This function just creates a button similar to the one above.\"), mdx(\"p\", null, \"Then add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createContent\"), \" function, directly below create the call to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccountButton()\"), \": \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if self.authManager.hasAccount(){\\n                Text(\\\"OR\\\").bold()\\n                createResetPasswordButton()\\n    }\\n\")), mdx(\"p\", null, \"We will only show the reset button if there is already an account \"), mdx(\"p\", null, \"Let\\u2019s now create the popupView that we will show the user.  First,  add the following function below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createResetPasswordButton()\"), \" function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"      private func createBiometricButton(action: @escaping () -> Void) -> some View {\\n        Button(action: action) {\\n            \\n            if self.authManager.biometryType == LABiometryType.faceID{\\n                BiometricButtonLabel(icon: \\\"faceid\\\", text: \\\"Use face ID\\\")\\n            }\\n            \\n            if self.authManager.biometryType == LABiometryType.touchID{\\n                BiometricButtonLabel()\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"This function just recreates the biometric button in order to use it in multiple  places.  You still don\\u2019t have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BiometricButtonLabel\"), \" view. Add a swift ui file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"BiometricButtonLabel.swift\"), \"  in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, and put\\nthe following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct BiometricButtonLabel: View {\\n    var icon = \\\"touchID\\\"\\n    var text = \\\"Use touch ID\\\"\\n    \\n    var body: some View {\\n        VStack {\\n            Image(\\\"touchID\\\" )\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 40, height: 40)\\n            Text(\\\"Use touch ID\\\")\\n        }.foregroundColor(Color.accent)\\n    }\\n}\\n\\n\\nstruct BiometricButtonLabel_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BiometricButtonLabel()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Now replace the entire button inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"hasAccount\"), \" condition with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"createBiometricButton {\\n                             self.authManager.loginWithBiometric()\\n                        }\\n\")), mdx(\"p\", null, \"The condition should look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"if self.authManager.hasAccount(){\\n                        createBiometricButton {\\n                             self.authManager.loginWithBiometric()\\n                        }\\n                    }\\n\")), mdx(\"p\", null, \"Before creating the popup views, add this to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var showResetPasswordPopup = false\\n\")), mdx(\"p\", null, \"This is a flag that will be used to show and hide the popup.\"), mdx(\"p\", null, \"Next, add the following function below the one you\\u2019ve just added:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       private func biometricPopupView() -> some View {\\n        ZStack(alignment: .topTrailing) {\\n            VStack(spacing: 20) {\\n                Text(self.authManager.biometricResult == .failure ? \\\"Failed\\\" : \\\"Authentication required\\\")\\n                    .bold()\\n                    .foregroundColor(self.authManager.biometricResult == .failure ? .red : .accent)\\n                createBiometricButton {\\n                     self.authManager.authenticateWithBiometric()\\n                }\\n            }.frame(width: 250, height: 200)\\n                .background(Color.background)\\n                .cornerRadius(20)\\n            \\n            Button(action: {\\n                self.showResetPasswordPopup.toggle()\\n            }) {\\n                Image(systemName: \\\"xmark\\\")\\n                    .imageScale(.large)\\n                    .padding()\\n                    .foregroundColor(.gray)\\n                    .opacity(0.6)\\n            }\\n        }\\n    }\\n\")), mdx(\"p\", null, \"This one create a popup view that presents the user with a biometric button with which he can authenticate. If the authentication is successful, he will then be presented with yet another popup view containing a password reset textfield. \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1584607125/lastpass%20clone/vbuh4eldjhqesufl6qr5.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Add the following below to create the password reset textfield:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       private func resetPassordPopupView() -> some View {\\n        ZStack(alignment: .topTrailing) {\\n            VStack(spacing: 40) {\\n                Text(\\\"Set your new password\\\")\\n                    .bold()\\n                    .foregroundColor(.accent)\\n                PasswordField(value: self.$authManager.resetPassword, header: \\\"New Password\\\", placeholder: \\\"New password goes here...\\\", errorMessage: authManager.resetPasswordValidation.message , isSecure: true)\\n                \\n                LCButton(text: \\\"Save\\\", backgroundColor: self.authManager.resetPasswordValidation.success ? Color.accent : Color.gray) {\\n                    self.showResetPasswordPopup = !self.authManager.saveResetPassword()\\n                }.disabled(!self.authManager.resetPasswordValidation.success)\\n            }.padding(.horizontal).frame(maxWidth: .infinity, maxHeight: 280)\\n                .background(Color.background)\\n                .cornerRadius(20)\\n            \\n            Button(action: {\\n                self.showResetPasswordPopup.toggle()\\n            }) {\\n                Image(systemName: \\\"xmark\\\")\\n                .imageScale(.large)\\n                .padding()\\n                .foregroundColor(.gray)\\n            }\\n            \\n        }\\n    }\\n\")), mdx(\"p\", null, \"This creates the pop view with a reset password textfield.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1584607125/lastpass%20clone/a4pnylwe430b6nkdjdyp.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Next, add the following function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func showPopups() -> some View {\\n        switch self.authManager.biometricResult {\\n        case .success:\\n            return AnyView(resetPassordPopupView().padding())\\n        default:\\n            return AnyView(biometricPopupView())\\n        }\\n    }\\n\")), mdx(\"p\", null, \"This is a simple function that shows an appropriate popup based on the biometric result. \"), mdx(\"p\", null, \"The last change we will make is to replace the body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   ZStack {\\n            SubscriptionView(content: createContent(), publisher: NotificationCenter.keyboardPublisher) { frame in\\n                withAnimation {\\n                    self.formOffset = frame.height > 0 ? -150 : 0\\n                }\\n            }\\n            \\n            VStack{\\n                if showResetPasswordPopup{\\n                    showPopups()\\n                     .offset(y: self.formOffset)\\n                        .transition(.scale)\\n                }\\n            }.frame(maxWidth: .infinity, maxHeight: .infinity)\\n                .background(Color.black.opacity(0.6))\\n                .opacity(showResetPasswordPopup ? 1 : 0)\\n                .animation(.spring())\\n\\n           \\n        }\\n\")), mdx(\"p\", null, \"This code will animate in the popup view.\"), mdx(\"h2\", null, \"Delete keychain items when the app is deleted from device\"), mdx(\"p\", null, \"Items saved in keychain will remain in it unless you explicitly remove them even if the app is completely deleted from the device. Depending on the functionality of your app, this behaviour may cause big problems. To delete the items from the keychain, we will need to check whether  the app has just been installed or re-installed so that we can delete the items from the device\\u2019s keychain before initial use. \"), mdx(\"p\", null, \"Now, open \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AuthKeys.swift\"), \" file , and the following line inside the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" static let initialLaunchKey = \\\"com.liquidcoder.initialLaunch\\\"\\n\")), mdx(\"p\", null, \"Then open the  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SceneDelegate.swift\"), \"  file.  Inside the first method, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"scene(_, willConnectTo, options)\"), \" one, add the following inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     if !UserDefaults.standard.bool(forKey: AuthKeys.initialLaunchKey) {\\n            authManager.deleteAccount()\\n            UserDefaults.standard.set(true, forKey: AuthKeys.initialLaunchKey)\\n      }\\n            \\n\")), mdx(\"p\", null, \"What we are doing here is checking whether the app has just been launched for the first, then we delete the items from the keychain, and last we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"initialLaunchKey\"), \" to true in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UserDefaults\"), \". Contrary to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"keychain\\u2019s\"), \" items, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UserDefaults\"), \" items will be deleted automatically whenever the app is deleted from the device.\"), mdx(\"h2\", null, \"Log out when the app enters the background.\"), mdx(\"p\", null, \"In order to protect the data in the app, we will need  log out as soon as the app enters the background. Entering background means the app got interrupted by an incoming call or you\\u2019ve have opened another app while the current is still opened in the background.\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SceneDelegate.swift\"), \" ,  add the following line in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sceneWillResignActive\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sceneDidEnterBackground\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.authManager.logout()\\n\")), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That\\u2019s it folks. In the next article, we will start working on the home screen.  Feel free to share this article, subscribe if you haven\\u2019t done so already, and HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#authentication-manager-improvement","title":"Authentication manager improvement."},{"url":"#sign-up","title":"Sign up"},{"url":"#login-view","title":"Login View"},{"url":"#clear-inputs-when-switching-sign-up-and-login-views","title":"Clear inputs when switching sign up and login views."},{"url":"#authenticationmanager","title":"AuthenticationManager"},{"url":"#views","title":"Views"},{"url":"#delete-keychain-items-when-the-app-is-deleted-from-device","title":"Delete keychain items when the app is deleted from device"},{"url":"#log-out-when-the-app-enters-the-background","title":"Log out when the app enters the background."},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:35:10.395Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-7","title":"Home View","updatedDate":"2020-08-27T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZBxrwkZL9POfCS0FPHgF5aE01FUYVimiUsX","type":"zip","id":"27e66c49-4ff0-5f79-bcb4-a90a4c348e86","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey ladies and gents, in the previous article (part 6) we made the header view which contained the filter view, but we haven\\u2019t used it yet. In this article we will create the home view containing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HeaderView\"), \" and a list of password and notes using the neumorphism design. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"HomeView\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \", add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HomeView.swift\"), \".  Before we create the list, we first need to create \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItems\"), \" for it, we will do that by creating a new swift UI file in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, and name it \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \" .\"), mdx(\"p\", null, \"Now, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text()\"), \" inside body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" HStack{\\n            Image(\\\"note\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 50, height: 50)\\n            \\n            VStack(alignment: .leading) {\\n                Text(\\\"No name\\\")\\n                    .font(.system(size: 20))\\n                    .padding(.bottom, 5)\\n                Text( \\\"N/A\\\")\\n                    .foregroundColor(Color.gray)\\n                    .font(.callout)\\n            }\\n\")), mdx(\"p\", null, \"Here is what I\\u2019ve done:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create an image view giving it a frame of 50 by 50.  We need to make the image resizable in order for the frame to work, try commenting out the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"resizable\"), \" modifier and you will see what I am talking about. We also set its aspect ratio to fit to prevent it from being cut-out.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" containing two \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \" views. When you create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \", the default alignment is center,  but we want the alignment to be left or leading that\\u2019s why we gave the leading alignment to the parent \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" container.\\nHere is a preview of what we\\u2019ve just done above:\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585726588/lastpass%20clone/knuk6rcugstk921qj6zn.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Values you see above are not final, we will change those when we introduce \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CoreData\"), \", but for now those will do the job.\"), mdx(\"p\", null, \"Next up, let\\u2019s give that component some neumorphic design.  First,  In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"NeumorphicEffect.swift\"), \" file,  add the  following  below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NeumorphicEffect\\xA0\"), \" struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nextension View {\\n    func neumorphic() -> some View {\\n        self.modifier(NeumorphicEffect())\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Next, add the following modifiers to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".padding(.horizontal)\\n        .frame(maxWidth: .infinity, minHeight: 80, alignment: .leading)\\n            .background(Color.background)\\n             .cornerRadius(20)\\n             .neumorphic()\\n             .padding(.vertical)\\n\")), mdx(\"p\", null, \"Here is what those are doing:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We add some padding to the left and right \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We make the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" fill the entire width with \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"See how we call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"neumorphic()\"), \" modifier? That\\u2019s because we have extended the view Protocol, and made our own modifier.\")), mdx(\"p\", null, \"Now, let\\u2019s go back to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HomeView.swift\"), \" file, and create the list view.\\nBefore we create the list, we first need to do some housekeeping.\"), mdx(\"p\", null, \"In the Views folder, add a swiftUI file named  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SectionTitle.swift\"), \" , and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" Text(title)\\n            .font(.title)\\n            .frame(maxHeight: .infinity)\\n            .frame(width: UIScreen.main.bounds.width, alignment: .leading)\\n            .padding(.leading, 40)\\n            .background(Color.background)\\n\")), mdx(\"p\", null, \"And add the following property to the top:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var title: String\\n\")), mdx(\"p\", null, \"Here is what new are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We want the title to fill the height of the parent hence we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"maxHeight\"), \"  to infinity which will make the height to match its parent\\u2019s height.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"As of the time of writing this article, there\\u2019s no way to customise the section header or footer\\u2019s colors, so in order to do so, we will need to cover the entire section header or footer with its child that\\u2019s why we set the width to be the device\\u2019s screen width.\")), mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HomeView.swift\"), \" file and put the following functions below body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    private func createList() -> some View {\\n        \\n    }\\n    \\n    private func createPasswordsSection() -> some View {\\n        \\n    }\\n    \\n    private func createNotesSection() -> some View {\\n        \\n    } \\n\")), mdx(\"p\", null, \"Don\\u2019t worry about the errors you are getting now, we will implement those\\nfunctions one by one shortly. In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createList\"), \" function, add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" List{\\n            createPasswordsSection()\\n            createNotesSection()\\n        }\\n\")), mdx(\"p\", null, \"That\\u2019s the list for  now, we will comeback later to give it some styles.\"), mdx(\"p\", null, \"Now add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordsSection\"), \" function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Section(header:\\n                SectionTitle(title: \\\"Passwords\\\")\\n            ) {\\n                ForEach(1..<5) { i in\\n                    RowItem()\\n                }\\n            }\\n\")), mdx(\"p\", null, \"And the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NotesSection\"), \" function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Section(header:\\n                SectionTitle(title: \\\"Notes\\\")\\n            ) {\\n                ForEach(1..<5) { i in\\n                    RowItem()\\n                }\\n            }\\n\")), mdx(\"p\", null, \"As you can see, we are re-using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SectionTitle\"), \" for the section header and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem\"), \" for the list item. Later on, we will replace the hard-coded data with \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CoreData\"), \" content, but for now that will do the trick.\"), mdx(\"p\", null, \"Now let\\u2019s style the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \". Add the following modifier directly to the it:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".onAppear {\\n            UITableView.appearance().backgroundColor = UIColor(named: \\\"bg\\\")\\n            UITableView.appearance().separatorColor = .clear\\n            UITableView.appearance().showsVerticalScrollIndicator = false\\n        }\\n\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"As of time of writing this tutorial, SwiftUI \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \" has limited customisation,  there\\u2019s no way of:\"), mdx(\"ol\", {\n    parentName: \"blockquote\"\n  }, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"changing the List\\u2019s background color, \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"removing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ScrollIndicator\"), \", \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"hiding separators and so on. \"))), mdx(\"p\", null, \"However, having been built on top of UIKit\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UITableView\"), \", we can use the latter to hack it, and that\\u2019s exactly what we are doing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" block.\"), mdx(\"p\", null, \"UITableView has a static function that return the current instance of itself (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UITableView\"), \"), so we can get that instance, and customize it.  Because the instance is shared, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \" will pickup the change and apply them to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \". \"), mdx(\"p\", null, \"Here is the preview of what we have done so far:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585986724/lastpass%20clone/fdzuxey38rfa7dnaeqlo.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Yeah looks weird right? We are still missing some stuff. Let\\u2019s work on that next\\u2026 we are missing just one line of code. Add the following modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RowItem()\"), \" inside the 2 functions:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".listRowBackground(Color.background)\\n\")), mdx(\"p\", null, \"And look what we\\u2019ve got now:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1585989138/lastpass%20clone/ojdfldslvuavvpwqtnrf.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Isn\\u2019t that beautiful? \"), mdx(\"p\", null, \"Let\\u2019s put the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Navbar\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Header\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \" together . In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \", replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createList()\"), \" function call with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack {\\n            HeaderView { filter in }\\n            createList()\\n        }\\n\")), mdx(\"p\", null, \"Here we\\u2019ve just added the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HeaderView\"), \" that we created earlier. Don\\u2019t worry about the closure, we will use it later on when we we start working with \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CoreData\"), \".\"), mdx(\"p\", null, \"You should see this on the preview:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1586242609/lastpass%20clone/zdsapwbwy2knhopil1qs.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Now that you have put together the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HeaderView\"), \" with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \". We can now add the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationView\"), \".  Go back in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \", and you\\u2019ll find that the preview is not working no matter how many times you hit the  resume button. Remember that we are passing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EnvironmentObject\"), \", but the preview does not have access to it, so we need to give it one. Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView()\"), \" in the preview: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let auth = AuthenticationManager()\\nauth.isLoggedIn = true\\nreturn ContentView().environmentObject(auth)\\n\")), mdx(\"p\", null, \"Here we are creating a new instance of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \", and then we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isLoggedIn\"), \" to true explicitly to bypass the authentication screen.\"), mdx(\"p\", null, \"Now if you resume the preview, everything should work as expected. However, you are still seeing the coming soon text. Let\\u2019s now replace it with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \". \"), mdx(\"p\", null, \"While you\\u2019re still in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ContentView.swift\"), \" file, add the following property to the top of the file:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" @State private var showMenu = false\\n\")), mdx(\"p\", null, \"This property will be used in a later stage, we are adding now because it will be required shortly.\"), mdx(\"p\", null, \"Now replace the coming soon text with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack {\\n      NavBar(showMenu: self.$showMenu)\\n }\\n\")), mdx(\"p\", null, \"This add the navigation bar we created earlier to the centre of the screen which is not what we want of course, we will be fixed shortly.\"), mdx(\"p\", null, \"Below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavBar\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \", add the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" HomeView()\\n\")), mdx(\"p\", null, \"The user interface is starting to come together, but there\\u2019re still some minor things we need to take care of. \"), mdx(\"p\", null, \"Here is what we have now:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1586329353/lastpass%20clone/tbrjamdh6gfnvyljykr1.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"As you can see in the image above, we still need to fix those issues. Likely for us, it will only take exactly two lines to tidy up the  UI, so add the following modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \"  container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".edgesIgnoringSafeArea(.top)\\n            .background(Color.background)\\n\")), mdx(\"p\", null, \"Here is what those two do:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first modifier will push the User interface to the top and ignore the space at the top of the screen.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second just adds a background to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ZStack\"), \" container\")), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That\\u2019s it for this part guys, as I said I want to make the parts smaller so that I can explain some concepts a little bit better.  If you have any question, send me an email. Don\\u2019t forget to subscribe if you haven\\u2019t done so already, and share this article. Happy coding!!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#homeview","title":"HomeView"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T18:18:46.666Z","isNewLayout":null,"previews":null,"slug":"lastpass-clone-part-3","title":"Local authentication using CryptoKit, Keychain and UserDefaults or Biometrics (Face ID and Touch ID)","updatedDate":"2020-03-15T00:00+01:00","downloads":[{"url":" https://ln2.sync.com/dl/3ae15fbf0/6taq3kzn-fkpx2g3f-drz74h3k-pfb86rhc","type":"zip","id":"eb3f1a48-d88e-53ab-a0a9-a7a812ef5ba3","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Last week (actually 3 weeks ago), I published part 2 of this ongoing series in which we created validations for the 2 authentication forms using the combine framework (login and sign up), if you haven\\u2019t read it, you should read it first, check it out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/lastpass-clone-pt2-combine/\"\n  }), \"here\"), \".  In this one, we will perform local authentication using \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CryptoKit\"), \" (the newly released native swift cryptographic library available from iOS 13), \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Keychain\"), \" and the good old \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"UserDefaults\"), \". We will also provide an alternative authentication method using Touch ID or Face ID depending on the device\\u2019s support. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"Import the KeychainSwift Package using Swift package manager\"), mdx(\"p\", null, \"The native keychain library is a little bit overwhelming to work with that\\u2019s why we will need to use a third party wrapper to simplify the interaction with its API.  \"), mdx(\"p\", null, \"So in your Xcode project, select File -\", \">\", \" Swift Package Manager -\", \">\", \" Add Package Dependency like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1583565841/lastpass%20clone/y0nqzvlwkusox3oyf7yg.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Paste in this url (\", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/evgenyneu/keychain-swift.git\"\n  }), \"https://github.com/evgenyneu/keychain-swift.git\"), \"), and click next, and click again next, once the package has been fetched, click the finished button. After that, you  are ready to start working with the library. \"), mdx(\"p\", null, \"Most of the work will be done in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManaager\"), \" class. To the top and below all the existing properties, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published var isLoggedIn = false\\n@Published var userAccount = User()\\nprivate var keychain = KeychainSwift()\\n\\n\")), mdx(\"p\", null, \"All of those are pretty much self-explanatory.\\nThere was an LAContext object already declared, here is apple\\u2019s definition of it.\\n\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"laContext\"), \": A mechanism for evaluating authentication policies and access controls. You use an authentication context to evaluate the user\\u2019s identity, either with biometrics like Touch ID or Face ID, or by supplying the device passcode. We will basically use that to check whether the device support any biometric and whether we can use it to authenticate.\"), mdx(\"h2\", null, \"Create account and login\"), mdx(\"p\", null, \"Before diving into creating the account, let\\u2019s take care of some stuff first. In the same folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AuthKeys.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct AuthKeys {\\n    static let isLoggedIn = \\\"com.liquidcoder.isLoggedIn\\\"\\n    static let hasAccount = \\\"com.liquidcoder.hasAccount\\\"\\n    static let email = \\\"com.liquidcoder.email\\\"\\n    static let password = \\\"com.liquidcoder.password\\\"\\n    static let salt = \\\"com.liquidcoder.salt\\\"\\n}\\n\\n\")), mdx(\"p\", null, \"Those are just keys that we will use to store credentials in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UserDefaults\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Keychain\"), \".\"), mdx(\"p\", null, \"Then create a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"User.swift\"), \" in the Models folder containing the following struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct User {\\n    var email = \\\"\\\"\\n    var password = \\\"\\\"\\n}\\n\\n\")), mdx(\"p\", null, \"Now, add the following methods in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     func hasAccount() -> Bool {\\n        keychain.get(AuthKeys.email) != nil\\n    }\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func createAccount()  {\\n        guard !hasAccount() else { return }\\n        let hashedPassword = hashPassword(password)\\n        keychain.set(email.lowercased(), forKey: AuthKeys.email,withAccess: .accessibleWhenPasscodeSetThisDeviceOnly)\\n        keychain.set(hashedPassword, forKey: AuthKeys.password,withAccess: .accessibleWhenPasscodeSetThisDeviceOnly)\\n        login()\\n    }\\n\")), mdx(\"p\", null, \"The first method checks whether this device already has a SinglePass account associated with it. We then use that method in the second method to check before creating a new account. So we will only create an account if there\\u2019s no existing account. So, step by step:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We get the hashed password from the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"hashPassword\"), \"  method which I\\u2019ll explain shortly.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We then save the email and password in the device\\u2019s keychain.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last, we \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"login\"), \"  which will also be created shortly.\\nNext, add the following function below the one above:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func login() {\\n    userDefaults.set(true, forKey: AuthKeys.isLoggedIn)\\n    self.isLoggedIn = true\\n}\\n\")), mdx(\"p\", null, \"Here we save a true boolean value in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"UserDefaults\"), \" to indicate that the user is logged in, and set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isLoggedIn\"), \" published property to true to trigger the user interface to refresh and dismiss the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationView\"), \".\"), mdx(\"p\", null, \"Next, add this below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" private func hashPassword(\\\\_ password: String) -> String {\\n      var salt = \\\"\\\"\\n        \\n        if let savedSalt = keychain.get(AuthKeys.salt) {\\n            salt = savedSalt\\n        } else {\\n            let key = SymmetricKey(size: .bits256)\\n            salt = key.withUnsafeBytes({ Data(Array($0)).base64EncodedString() })\\n            keychain.set(salt, forKey: AuthKeys.salt)\\n        }\\n  \\nguard let data = \\\"\\\\(password)\\\\(salt)\\\".data(using: .utf8) else { return \\\"\\\" }\\n    let digest = SHA256.hash(data: data)\\n    return digest.map{String(format: \\\"%02hhx\\\", $0)}.joined()\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We generate the salt for each user that will be (prepended) or appended to whatever the user types as a password before generating the hash. we will save both the salt and the hashed password  in the keychain. The salt is used to make the password stronger in case the user types in a weak or common password.  \"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We append the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"salt\"), \" to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"password\"), \" to make the password much stronger.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We then use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CryptoKit\"), \" to generate a hash value from the salted password which we will then save in the device\\u2019s keychain. You need to import \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CryptoKit\"), \" before using it. Saving a hash instead of the real password will make the account more secure in the sense that no one will ever know the user\\u2019s password even if the keychain is compromised thanks to the salt. \")), mdx(\"h2\", null, \"Authenticate and logout\"), mdx(\"p\", null, \"In order to login, some conditions must be met first, naming:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"There should be an account associated with the device\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The password and email must be the same as the ones saved in the device\\u2019s keychain.\\nSo let\\u2019s do that now. Below the methods you\\u2019ve created, add the following:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func authenticate(username: String, password: String) -> Bool{\\n    if let savedEmail = keychain.get(AuthKeys.email), let savedPassword = keychain.get(AuthKeys.password),\\n    let salt = keychain.get(AuthKeys.salt) {\\n        let hashedPassword = hashPassword(\\\"\\\\(password)\\\\(salt)\\\")\\n        if savedEmail == email.lowercased() && hashedPassword == savedPassword{\\n            login()\\n            return true\\n        }\\n    }\\n    return false\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing above: \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Retrieve the saved email and password.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Check whether the typed email is the same as the saved one and the typed password is equal to the hashed password because the hash function will always return the same hash for a given value. Hashes also have the property that if the input changes by even a tiny bit, the resulting hash is completely different.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"If everything is the same, we login.\\nSigning  out is the same as logging in. Just reset the user default value for \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isLoggedIn\"), \"  key to false and set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isLoggedIn\"), \" to false to update the user interface. Here is the code for that:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" func logout() {\\n    userDefaults.set(false, forKey: AuthKeys.isLoggedIn)\\n    self.isLoggedIn = false\\n}\\n\")), mdx(\"p\", null, \"We will also give the user the ability to delete his account. Here is the method that will do just that:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     func deleteAccount()  {\\n        keychain.delete(AuthKeys.email)\\n        keychain.delete(AuthKeys.password)\\n        keychain.delete(AuthKeys.salt)\\n        logout()\\n    }\\n\")), mdx(\"p\", null, \"We will later delete all data associated with this device.\"), mdx(\"p\", null, \"That\\u2019s done for now. Let\\u2019s move on to Biometrics authentication.\"), mdx(\"h2\", null, \"Touch ID, Face ID or Password\"), mdx(\"p\", null, \"Before we go any further, we need to add a usage description key value in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" file to allow the use of either Face ID or Touch ID, and also to prevent the app from crashing. To do that, open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" file as source code like this:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1583565741/lastpass%20clone/nbct41tqhqdjnlqep5yy.png\",\n    \"alt\": null\n  })), \"\\nAnd put the key and value (string) inside the dict tag like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-xml\"\n  }), \"...\\n    <key>NSFaceIDUsageDescription</key>\\n    <string>Replace with your description explaining why you want to use biometrics</string>\\n...\\u2028\\n\")), mdx(\"p\", null, \"Replace the string value with the reason why you want to use either Touch ID or  Face ID.\"), mdx(\"p\", null, \"Now add the following method to the bottom of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AutheticationManager\"), \" class:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" func canAuthenticate(error: NSErrorPointer) -> Bool{\\n       self.laContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: error)\\n    }\\n\")), mdx(\"p\", null, \"This checks whether  we  can authenticate using the given policy. There 4 policies that you can choose from. The one the we are using will allow the user to authenticate with either biometry, Apple Watch, or the device passcode.You can read more about those policies \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/localauthentication/lapolicy\"\n  }), \"here\"), \".  \"), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"Note :\"), \" Delete the existing \", mdx(\"inlineCode\", {\n    parentName: \"em\"\n  }, \"canAuthenticate\"), \" function if there\\u2019s one.\")), mdx(\"p\", null, \"After that condition has succeeded, we will then be able to evaluate the actual policy using this code  :\\nAdd the following to the top but below all other publishers.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  private lazy var biometryPublisher: Future<Bool, Never> = {\\n       Future<Bool, Never> {[unowned self] promise in\\n            let localizedReasonString = \\\"Replace with your description explaining why you want to use biometrics\\\"\\n            var authError: NSError?\\n            self.laContext.localizedFallbackTitle = \\\"Please use your Passcode\\\"\\n            if self.canAuthenticate(error: &authError) {\\n                self.laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in\\n                    return  promise(.success(success))\\n                }\\n            } else {\\n                print(authError ?? \\\"\\\")\\n                return promise(.success(false))\\n            }\\n        }\\n    }()\\n    \\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We are using combine\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Future\"), \" to wrap the evaluation\\u2019s asynchronous call into a publisher to use the result in a combine pipeline.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the future block, we start by capturing \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"self\"), \" to avoid memory leaks.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first check whether we can evaluate, if yes, we proceed with the same evaluation policy we checked with. The localised string is the one that will be displayed  in the authentication dialog presented to the user.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The evaluation will give us a boolean flag indicating whether the evaluation succeeded  or failed, and an error which we will just print to the console for simplicity (In production, you will need to handle errors properly).\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"promise\"), \" that was provided by \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Future\"), \" containing the boolean flag which will be passed to the downstream publisher.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"If the device can not evaluate the policy, we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"promise\"), \" with false.\")), mdx(\"p\", null, \"Now add the following function below everything in the class:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   func authenticateWithBiometric()  {\\n        biometryPublisher\\n            .receive(on: DispatchQueue.main)\\n            .assign(to: \\\\.isLoggedIn, on: self)\\n            .store(in: &self.cancellableSet)\\n    }\\n\")), mdx(\"p\", null, \"Here is what this code is doing:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Using the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"biometryPublisher\"), \", we move the remaining operations to the main thread using the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".receive\"), \" operator.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Now every operation will done on the main thread because you can not update the user interface on a background thread. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then use the assign operator to set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isLoggedIn\"), \" property with the value that came from the pipeline to update the user interface accordingly.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we store yet another cancellable object in the set. \", mdx(\"blockquote\", {\n    parentName: \"li\"\n  }, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"You can read more about the combine framework \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://heckj.github.io/swiftui-notes/#patterns-future\"\n  }), \"here\"), \". If you are following along with the part 2 project, there is an existing authenticateWithBiometric function, delete it please!\")))), mdx(\"p\", null, \"We will reuse the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"biometryPublisher\"), \" later on to reset the user\\u2019s password in case that user has forgotten it. Before we continue, let\\u2019s first add the code that will check whether the device support Touch ID or Face ID. \"), mdx(\"p\", null, \"At the top of the class, add the following property:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published var biometryType = LABiometryType.none\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LABiometryType\"), \" enum is from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LocalAuthentication\"), \" package by apple. So we create this property as published because we will use it to update the user interface.\"), mdx(\"p\", null, \"Then below all of the functions, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getBiometryType() {\\n        var authError: NSError?\\n        if canAuthenticate(error: &authError){\\n            self.biometryType = laContext.biometryType\\n        }\\n    }\\n\")), mdx(\"p\", null, \"You first need to call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"canEvaluatePolicy\"), \" in order to get the biometry type.  That is, if you're just doing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LAContext().biometryType\"), \" then you'll always get '\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"none\"), \"' back.  You would first need to call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"canEvaluatePolicy\"), \" on that instance, and then \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"biometryType\"), \" should have a non-none value (assuming the device has biometry support, and the user has enabled it). Call that function in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method to set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"biometryType\"), \".\"), mdx(\"p\", null, \" Now is the time to update the user interface with what we\\u2019ve just done above.\"), mdx(\"h2\", null, \"Updating the User interface\"), mdx(\"p\", null, \"We will start with the account creation view.  Now open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AccountCreationView.swift\"), \", and add the following code in the sign up action closure.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" self.authManager.createAccount()                  \\n\")), mdx(\"p\", null, \"The above code does not need much explanation, we are just calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccount()\"), \"  method which will take care of saving user\\u2019s detail in the device\\u2019s keychain. And that\\u2019s all we need to do here. Let\\u2019s implement the remaining bit of the login view.\"), mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LoginView.swift\"), \" file, and replace the following button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  Button(action: {\\n                \\n                    }) {\\n                        VStack {\\n                            Image(systemName: \\\"faceid\\\")\\n                            .resizable()\\n                                .aspectRatio(contentMode: .fit)\\n                            .frame(width: 40, height: 40)\\n                                .foregroundColor(Color.accent)\\n                            Text(\\\"Use face ID\\\").foregroundColor(.accent)\\n                        }\\n                    }\\n\")), mdx(\"p\", null, \"With this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if self.authManager.hasAccount(){\\n                        Button(action: {\\n                            self.authManager.authenticateWithBiometric()\\n                        }) {\\n                            \\n                            if self.authManager.biometryType == LABiometryType.faceID{\\n                                VStack {\\n                                    Image(systemName: \\\"faceid\\\" )\\n                                    .resizable()\\n                                        .aspectRatio(contentMode: .fit)\\n                                    .frame(width: 40, height: 40)\\n                                    Text(\\\"Use face ID\\\")\\n                                }.foregroundColor(Color.accent)\\n                            }\\n                            \\n                            if self.authManager.biometryType == LABiometryType.touchID{\\n                                VStack {\\n                                    Image(\\\"touchID\\\" )\\n                                        .resizable()\\n                                        .aspectRatio(contentMode: .fit)\\n                                        .frame(width: 40, height: 40)\\n                                    Text(\\\"Use touch ID\\\")\\n                                }.foregroundColor(Color.accent)\\n                            }\\n                        }\\n                    }\\n\")), mdx(\"p\", null, \"The above code will show either a face id button or touch id button or no button at all depending on the device\\u2019s support of biometric authentication. When a user clicks the button, he will be able to login using just touch id or face id without needing to type in the email and password. The biometric button will only show up if there\\u2019s an account associated with the device.\"), mdx(\"p\", null, \"Let\\u2019s now implement the login function using the email and password. So add the following code inside the login button\\u2019s action closure:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" self.authManager.authenticate()\\n\")), mdx(\"p\", null, \"This code will only run when the button is enabled, meaning the inputs are valid and we can authenticate successfully. That\\u2019s it for the login and sign up functions. We are missing the password reset functionality and some other minor things that we will implement in the next post.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"That\\u2019s it for this part folks. In the next one, we will improve the authentication manager before moving on to creating the main screen. Don\\u2019t forget to subscribe if you haven\\u2019t done so already, share and HAPPY CODING!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#import-the-keychainswift-package-using-swift-package-manager","title":"Import the KeychainSwift Package using Swift package manager"},{"url":"#create-account-and-login","title":"Create account and login"},{"url":"#authenticate-and-logout","title":"Authenticate and logout"},{"url":"#touch-id-face-id-or-password","title":"Touch ID, Face ID or Password"},{"url":"#updating-the-user-interface","title":"Updating the User interface"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:49:14.864Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-14","title":"Navigation drawer and clean up","updatedDate":"2020-08-29T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZyy1IkZEIRTXaWvA1y9oyYFaJhTLynqDjk7","type":"zip","id":"3098e538-5aab-552d-86b1-cb1969143f51","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, in the previous post we finished implementing all the CoreData stuff, if you haven\\u2019t read it, I suggest you read it first before this. In this one, we will finish off this series by implementing the navigation drawer and other miscellaneous stuff. To follow along, get the previous lesson source code.\"), mdx(\"h2\", null, \"Navigation Drawer\"), mdx(\"p\", null, \"There are multiple ways to implement the navigation drawer, I will use one method, but you are totally free to modify it or implement your own.\"), mdx(\"h2\", null, \"Menu Item\"), mdx(\"p\", null, \"We will have 4 screens and we need to create a navigation menu item for each of them. In the utils folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screens\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum Screens: String, CaseIterable {\\n    case Vault\\n    case Premium = \\\"Go premium\\\"\\n    case Security\\n    case Settings\\n}\\n\\n\")), mdx(\"p\", null, \"Then in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MenuItem\"), \". Replace the content of the entire file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct MenuItem: View {\\n    var icon: String = \\\"briefcase.fill\\\"\\n    var screen: Screens = .Vault\\n    @Binding var isSelected: Bool\\n    var onSelect: ((Screens)->()) = {_ in}\\n    \\n    var body: some View {\\n        \\n        Button(action: {\\n            HapticFeedback.generate()\\n            self.onSelect(self.screen)\\n        }) {\\n            HStack {\\n                       Image(systemName: icon).imageScale(.large).foregroundColor(isSelected ? .accent : .gray)\\n                       Spacer()\\n                       Text(screen.rawValue)\\n                           .frame(maxWidth: .infinity, alignment: .trailing)\\n                           .foregroundColor(isSelected ? .accent : .gray)\\n                   }.padding()\\n                       .background(Color.background)\\n                       .cornerRadius(20).modifier(NeumorphicEffect())\\n        }\\n    }\\n}\\n\\nstruct MenuItem_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MenuItem(isSelected: .constant(false))\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code creates the following view:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1591865787/lastpass%20clone/vet3bgxjyudafualf8wt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1591865787/lastpass%20clone/vet3bgxjyudafualf8wt.png\"\n  }))), mdx(\"h2\", null, \"Menu Content\"), mdx(\"p\", null, \"In the Views folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MenuContent\"), \". Add the following properties to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    private let icons = [\\\"star.fill\\\",\\\"shield.lefthalf.fill\\\",\\\"briefcase.fill\\\"]\\n    @Binding var showMenu: Bool\\n    @Binding var selectedScreen: Screens\\n\")), mdx(\"p\", null, \"Next, add the following function below body:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createMenuItem(icon: String, screen: Screens) -> MenuItem {\\n        return MenuItem(icon: icon , screen: screen, isSelected: .constant(self.selectedScreen == screen),onSelect: { selectedScreen in\\n            self.selectedScreen = selectedScreen\\n            withAnimation {\\n                self.showMenu = false\\n            }\\n        })\\n    }\\n\\n\")), mdx(\"p\", null, \"Next, replace the content of the body with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        VStack(spacing: 30) {\\n            Image(\\\"singlePass-dynamic\\\").resizable().aspectRatio(contentMode: .fit) .frame(height: 30)\\n                .padding()\\n            \\n            ForEach(0..<icons.count, id: \\\\\\\\.self){ i in\\n                self.createMenuItem(icon: self.icons[i], screen: Screens.allCases[i])\\n            }\\n            \\n            Spacer()\\n            createMenuItem(icon: \\\"gear\\\", screen: .Settings)\\n            \\n        }.padding(.top, 44)\\n            .padding(.bottom, 30)\\n            .padding(.horizontal)\\n            .padding(.leading, UIScreen.main.bounds.width - UIScreen.main.bounds.width / 1.5 )\\n            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)\\n            .background(Color.background).edgesIgnoringSafeArea(.all) \\n\\n\")), mdx(\"p\", null, \"To get rid of the errors you are getting, replace the content of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"previews\"), \" static property with the following :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" MenuContent(showMenu: .constant(false), selectedScreen: .constant(Screens.Vault))\\n\")), mdx(\"p\", null, \"With all of that in place, resume the preview and you should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1591868568/lastpass%20clone/if9w3jtncvqh7gepx5mo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1591868568/lastpass%20clone/if9w3jtncvqh7gepx5mo.png\"\n  }))), mdx(\"h2\", null, \"Navigation drag\"), mdx(\"p\", null, \"It\\u2019s time to bring the navigation drawer content to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \". So open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ContentView.swift\"), \" file, and add the following properties at the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State var selectedScreen: Screens = .Vault\\n    @GestureState private var  translation: CGFloat = 0\\n    private let triggerLocation = UIScreen.main.bounds.width - 50\\n    var offset: CGFloat {\\n        return showMenu ? UIScreen.main.bounds.width / 1.5 : 0\\n    }\\n\")), mdx(\"p\", null, \"And this function below the body block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    fileprivate func createMenuContent() -> some View {\\n        return MenuContent(showMenu: self.$showMenu, selectedScreen: self.$selectedScreen)\\n            .padding(.top, 40).padding(.bottom, 65)\\n            \\n            .rotation3DEffect(Angle(degrees: 30 - Double(30 * dragPercent())) , axis: (x: 0, y: 1, z: 0))\\n            .offset(x: 100 - (100 * dragPercent()), y: 0)\\n            .animation(.spring(), value: showMenu)\\n            .onTapGesture {\\n                if self.showMenu { HapticFeedback.generate() }\\n                self.showMenu = false\\n                \\n        }\\n    }\\n  \\n\\n\")), mdx(\"p\", null, \"The above function creates and customises the menu content,  we\\u2019ve also added a tapGesture recogniser that will be used to close the navigation drawer.\"), mdx(\"p\", null, \"Next, add the following function below:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// 1    \\nfileprivate func dragPercent() -> CGFloat {\\n         min(1,(self.offset +  self.translation) / (UIScreen.main.bounds.width / 1.5))\\n    }\\n       \\n// 2    \\nfileprivate func handleGesture() -> _EndedGesture<GestureStateGesture<DragGesture, CGFloat>> {\\n        return DragGesture().updating(self.$translation, body: { (value, state, transaction) in\\n            if value.startLocation.x > self.triggerLocation {\\n                state = -value.translation.width\\n            }\\n        }\\n        ).onEnded({ value in\\n            guard value.startLocation.x > self.triggerLocation else { return }\\n            HapticFeedback.generate()\\n            self.showMenu = value.translation.width < 0\\n        })\\n    }\\n\\n\")), mdx(\"p\", null, \"Here is what those 2 are doing:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first one just calculates the drag progress in percentage based on the drag translation.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second one, as its name implies, just handles the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"DragGesture\"), \"\\u2019s methods. We don\\u2019t want the gesture to work on the entire width of the screen, we want it to work only on the right edge of the screen that\\u2019s why we don\\u2019t change the state unless the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"startLocation\"), \" is greater than the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"triggerLocation\"), \" that we have declared above. We\\u2019ve also added some haptic feedback for better user experience.\\nNext, call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createMenuContent\"), \" in the body block, above the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \", so that it is like this:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" if authManager.isLoggedIn {\\n                createMenuContent()\\n                \\n                VStack {\\n                    ...\\n\\n\")), mdx(\"p\", null, \"Then add the following modifiers to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".background(Color.background)\\n                    .clipped()\\n                    .shadow(color:  showMenu ? .darkShadow : .clear, radius: showMenu ? 6 : 0, x: 3, y: 0)\\n                    .offset(x:  -max(self.offset + self.translation, 0), y: 0)\\n                    .animation(.spring(), value: showMenu)\\n                    .gesture( handleGesture() )\\n\\n\")), mdx(\"p\", null, \"And replace the 2 modifiers on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".background(Color.background)\\n        .edgesIgnoringSafeArea(.all)\\n\\n\")), mdx(\"h2\", null, \"Other screens\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Utils\"), \", add a file named Screens containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum Screens: String, CaseIterable {\\n    case Vault\\n    case Premium = \\\"Go premium\\\"\\n    case Security\\n    case Settings\\n}\\n\")), mdx(\"p\", null, \"Next, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Navbar\"), \" call with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"NavBar(showMenu: self.$showMenu, title: selectedScreen.rawValue, showSearchField: selectedScreen == .Vault)\\n\\n\")), mdx(\"p\", null, \"And replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView()\"), \" call with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"if selectedScreen == .Vault{\\n      HomeView().transition(.opacity)\\n  }\\n  \\n  if selectedScreen == .Premium{\\n      GoPremiumView()\\n  }\\n  \\n  if selectedScreen == .Security{\\n      SecurityView()\\n  }\\n  \\n  if selectedScreen == .Settings{\\n      SettingsView()\\n  }\\n\\n\")), mdx(\"p\", null, \"We still don\\u2019t have those 3 Views. Let\\u2019s create them now\\u2026  In the Screens folder, create 3 files named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GoPremiumView\"), \", \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GoPremiumView\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SettingsView.\"), \" These screens do not matter as they will only contain static placeholder to better illustrate the navigation drawer's functionality. With that in mind, you can put whatever you like in these files as long as what you put in them are swiftUI Views.\"), mdx(\"h2\", null, \"Go Premium View\"), mdx(\"p\", null, \"Replace everything in that file with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GoPremiumView: View {\\n    var body: some View {\\n        ScrollView{\\n            VStack(spacing: 20) {\\n                HStack {\\n                    Text(\\\"SinglePass\\\").font(.title)\\n                    Text(\\\"Premium\\\").font(.title).bold()\\n                }\\n                \\n                Text(\\\"Take your productivity to the next lavel\\\")\\n                \\n                LCButton(text: \\\"Go Premium\\\", backgroundColor: .accent) { }\\n                \\n                Text(\\\"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.\\\")\\n            }.padding()\\n                .padding(.vertical)\\n        }\\n    }\\n}\\n\\nstruct GoPremiumView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GoPremiumView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Like I've just said the content is just a placeholder, don't mind it.\"), mdx(\"h2\", null, \"Security View\"), mdx(\"p\", null, \"Replace the content of this file with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct SecurityView: View {\\n    var body: some View {\\n        ScrollView{\\n            VStack(spacing: 0){\\n                           ForEach(0..<4, id: \\\\.self) { i in\\n                               VStack {\\n                                   HStack {\\n                                       Text(SettingItems.allCases[i].rawValue).padding()\\n                                       Spacer()\\n                                       Image(systemName: \\\"chevron.right\\\")\\n                                   }\\n                                   if i < 3{\\n                                       Rectangle().frame(height: 0.5).foregroundColor(Color.gray)\\n                                   }\\n                               }.padding(.horizontal)\\n                               \\n                           }\\n                       }.frame(maxWidth: .infinity)\\n                       .background(Color.background).cornerRadius(20)\\n                           .padding(10)\\n                  .padding(.vertical)\\n                           .modifier(NeumorphicEffect())\\n        }\\n    }\\n}\\n\\nstruct SecurityView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SecurityView()\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Settings View\"), mdx(\"p\", null, \"Last, replace the content of that file with the following code :\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct SettingsView: View {\\n    \\n    var body: some View {\\n        ScrollView{\\n            VStack(spacing: 0){\\n                ForEach(0..<6, id: \\\\.self) { i in\\n                    VStack {\\n                        HStack {\\n                            Text(SettingItems.allCases[i].rawValue).padding()\\n                            Spacer()\\n                            Image(systemName: \\\"chevron.right\\\")\\n                        }\\n                        if i < 5{\\n                            Rectangle().frame(height: 0.5).foregroundColor(Color.gray)\\n                        }\\n                    }.padding(.horizontal)\\n                    \\n                }\\n            }.frame(maxWidth: .infinity)\\n            .background(Color.background).cornerRadius(20)\\n                .padding(10)\\n                .modifier(NeumorphicEffect())\\n            \\n            VStack(spacing: 0){\\n                ForEach(6..<8, id: \\\\.self) { i in\\n                    VStack {\\n                        HStack {\\n                            Text(SettingItems.allCases[i].rawValue).padding()\\n                            Spacer()\\n                            Image(systemName: \\\"chevron.right\\\")\\n                        }\\n                        if i < 7{\\n                            Rectangle().frame(height: 0.5).foregroundColor(Color.gray)\\n                        }\\n                    }.padding(.horizontal)\\n                    \\n                }\\n            }.frame(maxWidth: .infinity)\\n            .background(Color.background).cornerRadius(20)\\n                .padding(10)\\n                .modifier(NeumorphicEffect())\\n            \\n            VStack {\\n                HStack {\\n                    Text(SettingItems.PrivacyPolicy.rawValue).foregroundColor(Color.accent).padding()\\n                    Spacer()\\n                    Image(systemName: \\\"doc.on.doc.fill\\\").foregroundColor(Color.accent)\\n                }\\n            }.padding(.horizontal)\\n            .frame(maxWidth: .infinity)\\n            .background(Color.background).cornerRadius(20)\\n                .padding(10)\\n                .modifier(NeumorphicEffect())\\n            \\n            VStack {\\n                HStack {\\n                    Text(SettingItems.LogOut.rawValue).foregroundColor(Color.red).padding()\\n                    Spacer()\\n                    Image(systemName: \\\"exclamationmark.triangle.fill\\\").foregroundColor(Color.orange)\\n                }\\n            }.padding(.horizontal)\\n                 \\n            .frame(maxWidth: .infinity)\\n            .background(Color.background).cornerRadius(20)\\n                .padding(10)\\n                \\n                .modifier(NeumorphicEffect())\\n        }\\n        .onAppear {\\n            UITableView.appearance().backgroundColor = UIColor(named: \\\"bg\\\")\\n        }\\n    }\\n}\\n\\nstruct SettingsView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SettingsView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"I totally agree that this is over-kill for some placeholder content, but I didn't want to ruin the design by making some part ugly and others pretty.\"), mdx(\"h2\", null, \"Few more things\"), mdx(\"p\", null, \"Open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavBar\"), \" file, find the code that renders the navigation drawer icon  and replace it with  the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Button(action: {\\n      self.showMenu.toggle()\\n  }) {\\n\\n  Image(systemName: \\\"line.horizontal.3\\\")\\n      .resizable()\\n      .frame(width: 20, height: 20)\\n      .imageScale(.large)\\n      .foregroundColor(Color.white)\\n      .padding(.trailing)\\n}\\n\")), mdx(\"p\", null, \"What we've done here was re-used the existing code to create that button. The button will be used to open or close the navigation drawer.\"), mdx(\"p\", null, \"Next, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \", find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createBodyContent\"), \" method and add the following line to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".offset(x: 0, y: self.formOffsetY)\\n\")), mdx(\"p\", null, \"The above code will be used handle moving the form up when in edit mode.\"), mdx(\"p\", null, \"The last thing we need  to do is to go in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" and  change the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isLoggedIn\"), \" property to false like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published var isLoggedIn = false\\n\")), mdx(\"h2\", null, \"Final Conclusion\"), mdx(\"p\", null, \"This is it folks, the last post for this series...Hope you enjoyed reading this as much as I did preparing, coding and writing it.  With the release of Swift UI 2.0 , i will go through  this app, update what need to be updated and write an article about the process,  so make sure to stay tuned,  subscribe and please share this whole series to whoever needs to start creating apps with swift UI.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#navigation-drawer","title":"Navigation Drawer"},{"url":"#menu-item","title":"Menu Item"},{"url":"#menu-content","title":"Menu Content"},{"url":"#navigation-drag","title":"Navigation drag"},{"url":"#other-screens","title":"Other screens"},{"url":"#go-premium-view","title":"Go Premium View"},{"url":"#security-view","title":"Security View"},{"url":"#settings-view","title":"Settings View"},{"url":"#few-more-things","title":"Few more things"},{"url":"#final-conclusion","title":"Final Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:42:00.495Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-10","title":"Password Generation Service","updatedDate":"2020-05-18T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZRLodkZrUFKbvuofUj6qMTfQqRPFLPc25GV","type":"zip","id":"2928d96e-867d-585b-bcf3-4411fe101ca2","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, last week\\u2019s part was all about creating the UI for our password generator view. We created a beautiful neumorphic design for the entire view. We also made a reusable custom modifier that creates an inner shadow to all  circles that uses it, of course this can be any type of shape if we tweak it a little bit. Let\\u2019s not make this intro long. To follow along, get the previous lesson source code.  let\\u2019s jump right in.\"), mdx(\"h2\", null, \"Password Generator Service\"), mdx(\"p\", null, \"Create a new folder inside the root folder of your project named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Services\"), \", then add a new swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorService.swift\"), \". This is the file in which we will put all of our logic for the password generation process. \"), mdx(\"p\", null, \"Next, add the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport Combine\\n\\nclass PasswordGeneratorService: ObservableObject {\\n    \\n    \\n}\\n\\n\")), mdx(\"p\", null, \" This class needs to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ObservableObject\"), \" protocol because some of its properties will be reactive.\"), mdx(\"p\", null, \"Next, add the following struct inside the class you\\u2019ve just created:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct Options {\\n        var lowercase = true\\n        var uppercase = false\\n        var specialCharacters = true\\n        var digits = false\\n        var length: CGFloat = 6\\n    }\\n\")), mdx(\"p\", null, \"As you can see, the above struct will hold the options that one can use to customise the password when generating a new one.\"), mdx(\"p\", null, \"Add the following below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Options\"), \" struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"     @Published var passowrd: String = \\\"\\\"\\n       private let uppercaseCharacters = \\\"abcdefghijklmnopqrstuvwxyz\\\".uppercased()\\n       private let lowercaseCharacters = \\\"abcdefghijklmnopqrstuvwxyz\\\"\\n       private let digits = \\\"0123456789\\\"\\n       private let specialCharacters = \\\"!@#$%^&*(){}[]:?/\\\\\\\\|\\\"\\n       @Published var options = Options()\\n\")), mdx(\"p\", null, \"Most of those properties are  private, we are only interested in the 2  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" ones. The password will be used to update the password field and the options to  configure the generated password.\"), mdx(\"p\", null, \"Now add the following function below everything: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func generatePassword(with options: Options) -> String {\\n           var charOptions = \\\"\\\"\\n           \\n           if options.lowercase {\\n               charOptions += lowercaseCharacters\\n           }\\n           \\n           if options.uppercase {\\n               charOptions += uppercaseCharacters\\n           }\\n           \\n           if options.digits {\\n               charOptions += digits\\n           }\\n           \\n       \\n           if options.specialCharacters {\\n               charOptions += specialCharacters\\n           }\\n           \\n           if charOptions.count <= 0 {\\n               return \\\"\\\"\\n           }\\n           \\n           var newPassword = \\\"\\\"\\n           for _ in 0...Int(options.length){\\n              let char = Array(charOptions)[Int.random(in: 0..<charOptions.count)]\\n               newPassword.append(char)\\n           }\\n\\n            return newPassword\\n\\n       }\\n\")), mdx(\"p\", null, \"This is our basic logic for generating password. Although there\\u2019s room for improvement, this one will do the trick.  Take a moment to go through the code, it\\u2019s simpler than it seems. \"), mdx(\"p\", null, \"Next, add the following below the above function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var generatePassowordPublisher: AnyPublisher<String, Never> {\\n        self.$options.debounce(for: 0.3, scheduler: RunLoop.main)\\n            .map {[unowned self] options in\\n            return self.generatePassword(with: options)\\n        }.eraseToAnyPublisher()\\n    }\\n\")), mdx(\"p\", null, \"The sole purpose of this publisher is to wait for 1/3 of a second before spitting out a new password after changing one or more of the options especially for the slider as we don\\u2019t want to generate a new password for each value while the user is sliding. \"), mdx(\"p\", null, \"Last, add the following below or above everything:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"init() {\\n        self.cancellable = generatePassowordPublisher\\n                   .assign(to: \\\\.passowrd, on: self)\\n        \\n  }\\n\")), mdx(\"p\", null, \"Here we use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assign\"), \" operator to subscribe to our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"publisher\"), \", and set the password with the value coming from the pipeline. With this, we are ready to modify the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \" in order to use our newly created service.\"), mdx(\"p\", null, \"Go back in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView.swift\"), \". Replace the following properties:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State private var lowercase = true\\n@State private var uppercase = false\\n@State private var specialCharacters = true\\n@State private var digits = false\\n@State private var length: CGFloat = 6\\n\")), mdx(\"p\", null, \"With the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@ObservedObject private var passwordService = PasswordGeneratorService()\\n\")), mdx(\"p\", null, \"Isn\\u2019t this fantastic? We\\u2019ve just replaced 5 line with 1 line, cool!\\u2026 Next, you will certainly get errors after that genocide. So, everywhere you have an error, you will need to replace the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"$\"), \" with the following except for the password.: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"$passwordService.options.\\n\")), mdx(\"p\", null, \"For eg. The following: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self.$lowercase\"), \" should be \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self.$passwordService.options.lowercase\"), \". \"), mdx(\"p\", null, \"Do this for the password:\\nChange this \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self.$password\"), \" with this \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self.$passwordService.passowrd\")), mdx(\"p\", null, \"You should now be able to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CMD+B\"), \" to build the project, resume the preview and click play to run it. You will now be able to switch on and off the options. We need a way to see the length of the password when the slider\\u2019s values changes. So, replace the slider with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"HStack {\\n                    Slider(value: self.$passwordService.options.length, in: 1...30, step: 1)\\n                        .accentColor(Color.accent)\\n                    \\n                    Text(\\\"\\\\(Int(self.passwordService.options.length))\\\")\\n                        .font(.system(size: 20, weight: .bold))\\n                        .foregroundColor(.gray)\\n                    .frame(width: 30, height: 30)\\n                    \\n                }\\n\")), mdx(\"p\", null, \"So the above code just put the slider inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" beside it that will display the current value of the slider. And with this done, we can say that  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \" is complete. \"), mdx(\"h2\", null, \"Putting the HomeView, EditFormView and PasswordGeneratorView together.\"), mdx(\"p\", null, \"Now that we have all these 3 component complete, we can proceed to putting the all of them together, but for that to work we still need one more thing, a button on the home screen that will open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \". This button will use a custom style like the one on the toggles. In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeView\"), \", add the following function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createFloatingButton() -> some View {\\n    EmptyView()\\n}\\n\")), mdx(\"p\", null, \"We will implement that function shortly. Next, replace the content of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ZStack(alignment: .bottomTrailing) {\\n            VStack {\\n                HeaderView { filter in }\\n                createList()\\n            }\\n            \\n            createFloatingButton()\\n        }\\n\")), mdx(\"p\", null, \"It\\u2019s still the same content, we\\u2019ve only added to it. We\\u2019ve surrounded the existing content with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container, then called the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createFloatingButton()\"), \" to display the button. So, in order for the button to be aligned to the bottom right of the screen, we set the alignment on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" container to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".bottomTrailing\"), \".\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createFloatingButton()\"), \" function, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" Button(action: {\\n             HapticFeedback.generate()\\n         }) {\\n             \\n             Image(systemName: \\\"plus\\\")\\n                 .resizable()\\n                 .frame(width: 20, height: 20)\\n                 .foregroundColor(Color.text)\\n                 .padding()\\n                 .background(Color.background)\\n                 .cornerRadius(35)\\n                .neumorphic()\\n         }.padding(30)\\n     \\n\")), mdx(\"p\", null, \"Resume and run the preview, you should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1588316610/lastpass%20clone/ebuyyvqt1uobqbvrox7w.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"The button looks good, right? However, if  you click on it, it does not do anything. Let\\u2019s change that. Add the following property to the of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@State var showEditFormView = false\\n\")), mdx(\"p\", null, \"Next, add this below the haptic feedback generation:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.showEditFormView.toggle()\\n\")), mdx(\"p\", null, \"Now we are able to toggle the state of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showEditFormView\"), \". Next, add the following exactly on the button, after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"padding(30)\"), \" modifier: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".sheet(isPresented: self.$showEditFormView) {\\n     self.createEditFormView()\\n }\\n\")), mdx(\"p\", null, \"To silence that error, add the following function below the function the one that creates the button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createEditFormView() -> some View {\\n    EmptyView()\\n}\\n\")), mdx(\"p\", null, \"Now if you resume the preview and run it, you should be able to present the sheet when you click the button. \"), mdx(\"p\", null, \"Next, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" inside the above function with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"EditFormView(showDetails: .constant(false))\\n\")), mdx(\"p\", null, \"Now, resume the preview and run it again. You should now see the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EdittFormView\"), \" on the presented sheet.\"), mdx(\"h2\", null, \"Present the password generator view\"), mdx(\"p\", null, \"Next, let\\u2019s present the password generator view. In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EditFormView.swift\"), \" file,  add the following property to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" @State private var showPasswordGeneratorView = false\\n\")), mdx(\"p\", null, \"Then find the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPasswordForm\"), \" function, and add the following inside the generate password action closure:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.showPasswordGeneratorView.toggle()\\n\")), mdx(\"p\", null, \"Next, add the following modifier to the same button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".sheet(isPresented: self.$showPasswordGeneratorView) {\\n                            PasswordGeneratorView(generatedPassword: self.$password)\\n                       }\\n\")), mdx(\"p\", null, \"Here is a brief explanation of what we\\u2019ve just done:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first declare a state variable that we will use to show and hide the sheet.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the generate password button\\u2019s action, we toggle the state \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"showPasswordGeneratorView\"), \" to show or hide the sheet.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then attach the sheet modifier to the button. We add set the  content of the sheet to be a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PasswordGeneratorView\"), \" instance. Note that we bind the password to the generated password from the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PasswordGeneratorView\"), \".\\nResume the preview and run it. You should be able to present the sheet containing \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PasswordGeneratorView\"), \". However, when you dismiss the sheet, the password is still empty,  the reason being that we haven\\u2019t wired the generated password binding property to the actual generated password from the password generation service. Let\\u2019s do that: \")), mdx(\"p\", null, \"Now, inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \", add the following modifier to the outer \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  .onDisappear {\\n                self.generatedPassword = self.passwordService.passowrd\\n        }\\n\")), mdx(\"p\", null, \"This will set the current generated password to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"generatedPassword\"), \" property which in turn will set the password field in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EdittFormView\"), \". \"), mdx(\"p\", null, \"Resume and run the preview again, and everything will now work fine. You will now be able to present the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \", generate a new password, dismiss the view and still see the new generated password  auto-populated in the password field in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EdittFormView\"), \".\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"This is it for this part folks, hope you\\u2019ve enjoyed it. Stay tuned for more. Please feel free to share this article, subscribe if you haven\\u2019t done so already.  If you have any question, send me an email.  Stay tuned and happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#password-generator-service","title":"Password Generator Service"},{"url":"#putting-the-homeview-editformview-and-passwordgeneratorview-together","title":"Putting the HomeView, EditFormView and PasswordGeneratorView together."},{"url":"#present-the-password-generator-view","title":"Present the password generator view"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"2 years ago","createdAt":"2020-08-15T18:40:22.645Z","isNewLayout":null,"previews":null,"slug":"lastpass-redesigned-clone-part-9","title":"Password Generation UI","updatedDate":"2020-05-10T00:00+01:00","downloads":[{"url":"https://my.pcloud.com/publink/show?code=XZHO5dkZ8Sf6rskYvMzYn72YpjbrJBP2kH7y","type":"zip","id":"95b7f539-0ae5-549e-94ed-d3198dc215de","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, last week we made the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EditFormView\"), \", and in this one and the next, we will create the password generation functionality. We will start by creating the user interface as it will be a little bit involved. To follow along, get the previous lesson source code. Let\\u2019s get started.\"), mdx(\"h2\", null, \"Password generator view\"), mdx(\"p\", null, \"Start by creating a swift UI file in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView.swift\"), \". Now add the following properties to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    @State private var password = \\\"\\\"\\n     @Binding var generatedPassword: String\\n     @State private var lowercase = true\\n     @State private var uppercase = false\\n     @State private var specialCharacters = true\\n     @State private var digits = false\\n     @State private var length: CGFloat = 6\\n\")), mdx(\"p\", null, \"The above properties will be changed when we implement the password generation service which will implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ObservableObject\"), \" protocol.\"), mdx(\"p\", null, \"Then replace everything in body with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack(spacing: 30) {\\n            Text(\\\"Generate password\\\").font(.title).bold()\\n            \\n            Group {\\n                SharedTextfield(value: self.$password, header: \\\"Generated Password\\\", placeholder: \\\"Your new password\\\", errorMessage: \\\"\\\", showUnderline: false, onEditingChanged: { flag in\\n                    }).padding()\\n            }.background(Color.background)\\n                .cornerRadius(10).neumorphic()\\n                \\n            VStack(spacing: 10){\\n                Toggle(isOn: $lowercase) {\\n                    Text(\\\"Lowercase\\\")\\n                }\\n                \\n                Toggle(isOn: $uppercase) {\\n                    Text(\\\"Uppercase\\\")\\n                }\\n                \\n                Toggle(isOn: $specialCharacters) {\\n                    Text(\\\"Special Characters\\\")\\n                }\\n                \\n                Toggle(isOn: $digits) {\\n                    Text(\\\"Digits\\\")\\n                }\\n                \\n                    \\n                Slider(value: $length, in: 1...30, step: 1)\\n                    .accentColor(Color.accent)\\n                \\n            }.padding()\\n                .background(Color.background)\\n                .cornerRadius(10)\\n                .neumorphic()\\n\\n        }.padding(.horizontal)\\n            .frame(maxHeight: .infinity)\\n            .background(Color.background)\\n            .edgesIgnoringSafeArea(.all)\\n\")), mdx(\"p\", null, \"The above code might seem enormous, but it\\u2019s way simpler than you think. Here is what it does: \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create a text that will act as a header title for this view.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then use our \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"SharedTextfield\"), \" that we created in one of previous parts to create a field that will display the generated password. Here we use a textfield rather than a simple text label because we want to give the user an option to change if he wishes.  We then add some corner radius and made it neumorphic using our own neumorphic modifier. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we put four Toggles and a slider inside a vertical Stack container. Right now, those toggles have the built-in boring styles, but we will create an amazing and beautiful neumorphic toggle design later on.  Next, we add a bunch of modifiers to style the second \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we also add modifiers to the outer \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" to make it look beautiful. Remember the order of modifiers matters.\")), mdx(\"p\", null, \"Currently the preview is broken because of the error that we have, so replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView()\"), \" in the preview with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  PasswordGenerationView(generatedPassword: .constant(\\\"\\\"))\\n\")), mdx(\"p\", null, \"Resume the preview, and you should see something like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587456811/lastpass%20clone/xgbgclrgrgoxmqpjwxer.png\",\n    \"alt\": null\n  })), \"\\nAs you can see the screen looks good, but the toggles are boring, let\\u2019s create a custom toggle neumophic style next.\"), mdx(\"h2\", null, \"Custom toggle style\"), mdx(\"p\", null, \"Let\\u2019s start by creating a new folder that we will name \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Styles\"), \", inside it add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CustomToggleStyle.swift\"), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct CustomToggleStyle: ToggleStyle {\\n    \\n    \\n    func makeBody(configuration: Self.Configuration) -> some View {\\n     \\n        EmptyView()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"In order for us to use the custom toggle style, it needs to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToggleStyle\"), \" protocol which in turn gives us the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \" function which is required to customise the toggle that uses this style. Let\\u2019s start with something simpler. \"), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView()\"), \" with the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Button(action: {\\n            configuration.isOn.toggle()\\n        }) {\\n            if configuration.isOn {\\n                Circle().frame(width: 50, height: 50).foregroundColor(Color.green)\\n            } else {\\n                Circle().frame(width: 50, height: 50).foregroundColor(Color.red)\\n            }\\n        }\\n        \\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The configuration has a \", mdx(\"em\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"em\"\n  }, \"isOn\")), \" which indicates  whether the toggle is on or off. This field is a boolean binding property which means that we can also mutate it using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"toggle()\"), \"  function. We toggle the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isOn\"), \" property in the button\\u2019s action to switch states.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next,  we return a simple green \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Circle()\"), \" when \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isOn\"), \" is set to true or a red \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Cirlce()\"), \"  otherwise. This the logic we will eventually use to toggle on and off the neumorphic design.\")), mdx(\"p\", null, \"To test this, open the password generation view, and add the following modifier to each toggle: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".toggleStyle(CustomToggleStyle())\\n\")), mdx(\"p\", null, \"After making that change, you will need to resume the preview and click the play button to run the it, and try clicking the toggles\\u2026 You will notice that there are switching from red to green and vice-versa.\"), mdx(\"p\", null, \"Here what we got now:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587625562/lastpass%20clone/ej5wqrkxlkox2hdk8fal.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Before we start making the neumorphism design, let\\u2019s first add the label that was there before. Now replace  the entire block of code in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \" function with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"        HStack {\\n            configuration.label\\n            Spacer()\\n            Button(action: {\\n                configuration.isOn.toggle()\\n            }) {\\n                if configuration.isOn {\\n                    Circle().frame(width: 50, height: 50).foregroundColor(Color.green)\\n                } else {\\n                    Circle().frame(width: 50, height: 50).foregroundColor(Color.red)\\n                }\\n            }\\n        }\\n\\n\")), mdx(\"p\", null, \"What we\\u2019ve done here is we put the label and the existing button inside and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container. You might be wondering where\\u2019s the label coming from, well it\\u2019s coming from the configuration. Go back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \" , and resume the preview again. \"), mdx(\"p\", null, \"Check out the preview now:\\n\", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587625563/lastpass%20clone/j3oflz1vplqkjwswf994.png\",\n    \"alt\": null\n  })), \"\\nAs you can see in the above preview, the label is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" you passed in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \". Now let\\u2019s make that neumorphic design now, shall we?\"), mdx(\"p\", null, \"We will start with the simple state which is when the toggle is off. So go back in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomToggleStyle\"), \", and replace the else content which is the red circle with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Circle() .frame(width: size, height: size)\\n          .foregroundColor(Color.background)\\n\\n           Image(systemName: \\\"power\\\")\\n                 .resizable()\\n                 .frame(width: 20, height: 20)\\n                 .foregroundColor(Color.gray)\\n}.cornerRadius(size / 2).neumorphic()\\n\")), mdx(\"p\", null, \"We are missing the size property hence the errors, so add the following to the top: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var onColor: Color = .background\\n var offColor: Color = .darkerAccent\\n var size: CGFloat = 40\\n\")), mdx(\"p\", null, \"Now go back to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \", resume the preview, and you should see this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587627310/lastpass%20clone/ia2cyjmev8fls3ocln0j.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"As you can see in the above picture, the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"off\"), \" state will be convex which means we want to make the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"on\"), \" state concave. We can  create concave neumophic designs using inner shadow, but there\\u2019s a problem, swift UI does not have inner shadows, so we will try to create our own. \"), mdx(\"p\", null, \"There are a bunch of ways one can create this concave neumophic  design, but I prefer the one made by Paul Hudson from \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.hackingwithswift.com/articles/213/how-to-build-neumorphic-designs-with-swiftui\"\n  }), \"HackingWithSwift\"), \". So let\\u2019s start making it.\"), mdx(\"p\", null, \"In the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, create a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"LinearGradientExt\"), \" containing the following block of code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"extension LinearGradient {\\n    init(_ colors: Color...) {\\n        self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing)\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Here we are just extending the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LinearGradient\"), \" with yet another initialiser, this one will just take in a gradient object, the start and end point will be the same for what we are trying to do, that\\u2019s why there\\u2019s no need to pass them in as parameters.\"), mdx(\"p\", null, \"Next, let\\u2019s extend the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\"), \" protocol  as well. In the same \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Extensions\"), \" folder, add another swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ViewExt\"), \"  containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" func innerShadow(radius: CGFloat, colors: (dark: Color, light:Color)) -> some View {\\n        self.overlay(\\n            Circle()\\n                .stroke(colors.dark, lineWidth: 4)\\n                .blur(radius: radius)\\n                .offset(x: radius, y: radius)\\n                .mask(Circle().fill(LinearGradient(colors.dark, Color.clear)))\\n        )\\n    }\\n\")), mdx(\"p\", null, \"Here is what that code he is doing:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We add an overlay to the view that will use this modifier.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the overlay, we add a circle \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then stroke it with the dark color used for the darker side of our neumorphic design.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we add a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"blur\"), \" modifier with the same radius as the offset value we will add next.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we move the blurred view a little to the bottom right.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then mask the view with yet another circle filled with a linear gradient.\\nThat was one side which is the top left side, we are missing the bottom right. \")), mdx(\"p\", null, \"Add another overlay directly after first one with the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".overlay(\\n                Circle()\\n                    .stroke(colors.light, lineWidth: 8)\\n                    .blur(radius: radius)\\n                    .offset(x: -radius, y: -radius)\\n                    .mask(Circle().fill(LinearGradient(Color.clear, colors.dark)))\\n        )\\n\\n\")), mdx(\"p\", null, \"The above code will create the same overlay as the one above but in the opposite side. This one goes from bottom right to top left whereas the first one went from top left to bottom right.\"), mdx(\"p\", null, \"Next, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomToggleStyle\"), \", replace the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"Circle().frame(width: 50, height: 50).foregroundColor(Color.green)\\n\")), mdx(\"p\", null, \"With the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"ZStack {\\n                        Circle()\\n                        .fill(Color.background)\\n                            .frame(width: size, height: size)\\n                            \\n                        \\n                        Image(systemName: \\\"power\\\")\\n                        .resizable()\\n                        .frame(width: 20, height: 20)\\n                            .foregroundColor(Color.accent)\\n                    }\\n\")), mdx(\"p\", null, \"Now, open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordGeneratorView\"), \"  and resume the preview, then run it. You should see something like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1587888947/lastpass%20clone/c8slq05xewjsfwnllx6r.png\",\n    \"alt\": \"Neumorphic button in swift ui\"\n  }))), mdx(\"p\", null, \"As you can see, there\\u2019s still no concave neumorphic design on the toggle when it\\u2019s selected. Well that\\u2019s because we still haven\\u2019t added the the design we\\u2019ve created. So, add the following after the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".frame(width: size, height: size)\"), \" modifier in the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CustomToggleStyle\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".innerShadow(radius: 1, colors: (Color.darkShadow, Color.lightShadow))\\n\\n\")), mdx(\"p\", null, \"Now if you resume and run the preview, you should be able to click the toggle and see your concave neumorphic design.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"And with this, we are done with this part . In the next one, we will finish the password generation process by creating its brain. Please feel free to share this article, subscribe if you haven\\u2019t done so already. Stay tuned for more and happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#password-generator-view","title":"Password generator view"},{"url":"#custom-toggle-style","title":"Custom toggle style"},{"url":"#conclusion","title":"Conclusion"}]}}}},{"createdDate":"3 years ago","createdAt":"2020-08-15T18:13:06.125Z","isNewLayout":null,"previews":null,"slug":"lastpass-clone-pt2-combine","title":"Validation with Combine & Persistence using UserDefaults","updatedDate":"2020-02-23T00:00+01:00","downloads":[{"url":"https://ln2.sync.com/dl/734d52830/arhvq42j-43fxst54-hc9gb6vj-sf9kn7xw","type":"zip","id":"624ebe22-2f1f-527f-ab32-cea495495052","name":"Source code"}],"allChapters":null,"code":null,"explanation":null,"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This is part 2 of the LastPass clone series, if you haven\\u2019t read the first part, \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/lastpass-clone-pt1/\"\n  }), \"here it is\"), \".  \"), mdx(\"h2\", null, \"Validation with combine\"), mdx(\"p\", null, \"We will use the combine framework to perform input validations. So in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" folder, add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager.swift\"), \", then create a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder, and add a file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FormValidation.swift\"), \" containing  the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct FormValidation {\\n    var success: Bool = false\\n    var message: String = \\\"\\\"\\n}\\n\")), mdx(\"p\", null, \"This is just a simple model that we will use in the validation publishers.\"), mdx(\"p\", null, \"Now go back in  the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager.swift\"), \" file , and  add the following properties to the top of the struct:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"private var cancellableSet: Set<AnyCancellable> = []\\n    \\n    @Published var email = \\\"\\\"\\n    @Published var password = \\\"\\\"\\n    @Published var confirmedPassword = \\\"\\\"\\n    \\n    @Published var canLogin = false\\n    @Published var canSignup = false\\n    \\n    @Published var emailValidation = FormValidation()\\n    @Published var passwordValidation = FormValidation()\\n    @Published var confirmedPasswordValidation = FormValidation()\\n    @Published var similarityValidation = FormValidation()\\n\")), mdx(\"p\", null, \"Notice  all properties are published except the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancellableSet\"), \", this is because we will use them in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LoginView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccountCreationView\"), \" whereas the set will be used to store cancellable objects returned by subscribers  that you will learn more of later on.\"), mdx(\"p\", null, \"Now add the following below :\"), mdx(\"h2\", null, \"Email publisher\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n    private var emailPublisher: AnyPublisher<FormValidation, Never> {\\n        self.$email.debounce(for: 0.2, scheduler: RunLoop.main)\\n        .removeDuplicates()\\n            .map { email in\\n                // To be created outside\\n                let regEx = \\\"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\\\\\\\.[A-Za-z]{2,64}\\\"\\n                let predicate = NSPredicate(format:\\\"SELF MATCHES %@\\\", regEx)\\n                \\n                if email.isEmpty{\\n                    return FormValidation(success: false, message: \\\"\\\")\\n                }\\n                \\n\\n                if !predicate.evaluate(with: email){\\n                    return FormValidation(success: false, message: \\\"Invalid email address\\\")\\n                }\\n                \\n                return FormValidation(success: true, message: \\\"\\\")\\n        }.eraseToAnyPublisher()\\n    }\\n\")), mdx(\"p\", null, \"Let\\u2019s break that down:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"debounce\"), \" on the published email property to wait for the specified amount of second before validating so that we don\\u2019t validate when each character is typed. The right amount is around 0.2 to 0.5 second, but you are totally free to try other values.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"removeDuplicates\"), \" does what its name says, remembers what was previously sent in the pipeline, and only passes forward values that don\\u2019t match the current value.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"map\"), \" operator gives us the current value that\\u2019s in the textfield to perform whatever computation we want.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"map\"), \" closure, we first check whether the textfield is empty then return false with en empty message, otherwise, we move to then next condition where we check if the email format is correct using the predicate we created earlier.. If all conditions fail meaning the input is valid, we return a successful \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"FormValidation\"), \"  object.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last we erase to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"anypublisher\"), \" to simplify the return type returned by the map operator.\")), mdx(\"p\", null, \"That\\u2019s the email publisher created. Let\\u2019s move to the password. Below that code , add the following:\"), mdx(\"h2\", null, \"Password publisher\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"private var passwordPublisher: AnyPublisher<FormValidation, Never> {\\n        self.$password.debounce(for: 0.2, scheduler: RunLoop.main)\\n            .removeDuplicates()\\n            .map { password in\\n                \\n                if password.isEmpty{\\n                    return FormValidation(success: false, message: \\\"\\\")\\n                }\\n                if password.count < PasswordConfig.recommendedLength{\\n                    return FormValidation(success: false, message: \\\"The password length must be greater than \\\\(PasswordConfig.recommendedLength) \\\")\\n                }\\n                \\n                let regEx = \\\"^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}$\\\"\\n                let predicate = NSPredicate(format:\\\"SELF MATCHES %@\\\", regEx)\\n                \\n                if !predicate.evaluate(with: password){\\n                    return FormValidation(success: false, message: \\\"The password is must contain numbers, uppercase and special characters\\\")\\n                }\\n                \\n                return FormValidation(success: true, message: \\\"\\\")\\n        }.eraseToAnyPublisher()\\n    }\\n\\nprivate var confirmPasswordPublisher: AnyPublisher<FormValidation, Never> {\\n         self.$confirmedPassword.debounce(for: 0.2, scheduler: RunLoop.main)\\n                   .removeDuplicates()\\n                    \\n                   .map { password in\\n                       \\n                       if password.isEmpty{\\n                           return FormValidation(success: false, message: \\\"\\\")\\n                       }\\n                    \\n                       if password.count < PasswordConfig.recommendedLength{\\n                           return FormValidation(success: false, message: \\\"The password length must be greater than \\\\(PasswordConfig.recommendedLength) \\\")\\n                       }\\n                       \\n                       let regEx = \\\"^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}$\\\"\\n                       let predicate = NSPredicate(format:\\\"SELF MATCHES %@\\\", regEx)\\n                       \\n                       if !predicate.evaluate(with: password){\\n                           return FormValidation(success: false, message: \\\"The password is must contain numbers, uppercase and special characters\\\")\\n                       }\\n                    \\n                       \\n                       return FormValidation(success: true, message: \\\"\\\")\\n               }.eraseToAnyPublisher()\\n    }\\n\")), mdx(\"p\", null, \"We do the same thing for both the password and confirmPassword.\\nThe above code does almost exactly the same thing, the only part that differs is the validation which goes like this:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We check whether the password is empty first\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then check the count\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we validate the password against the password predicate to check whether the password contains at least 1 uppercase letter, 1 lowercase letter ,  at least one  special character and at least 1 digit\\nIf all goes well, we return true with an empty msg.\")), mdx(\"p\", null, \"Next up, let\\u2019s check the similarity:\"), mdx(\"h2\", null, \"similarityPublisher\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" private var similarityPublisher: AnyPublisher<FormValidation, Never> {\\n        Publishers.CombineLatest($password, $confirmedPassword)\\n            .map { password, confirmedPassword in\\n                \\n                if password.isEmpty || confirmedPassword.isEmpty{\\n                     return FormValidation(success: false, message: \\\"\\\")\\n                }\\n                \\n                if password != confirmedPassword{\\n                     return FormValidation(success: false, message: \\\"Passwords do not match!\\\")\\n                }\\n                 return FormValidation(success: true, message: \\\"\\\")\\n        }.eraseToAnyPublisher()\\n    }\\n    \\n\")), mdx(\"p\", null, \"The above code just checks whether the password and confirmPassword are equal.\"), mdx(\"p\", null, \"Now, create the constructor if it not there yet...\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func init() {}\\n\")), mdx(\"p\", null, \"This is where we are going to subscribe to the publishers we\\u2019ve just created.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init()\"), \", add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"      emailPublisher\\n            .assign(to: \\\\.emailValidation, on: self)\\n            .store(in: &self.cancellableSet)\\n\")), mdx(\"p\", null, \"Here, we subscribe to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"email\"), \" publisher using the assign operator. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Assign\"), \" is a subscriber that\\u2019s used to set the value returned from a publisher to a property. As you can see in the above code we the set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"emailValidation\"), \" property that\\u2019s on self which is  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \". Then we store the cancellable into the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancellableSet\")), mdx(\"p\", null, \"Next, add the following code below that:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"       passwordPublisher\\n            .assign(to: \\\\.passwordValidation, on: self)\\n            .store(in: &self.cancellableSet)\\n        \\n        confirmPasswordPublisher\\n            .assign(to: \\\\.confirmedPasswordValidation, on: self)\\n            .store(in: &self.cancellableSet)\\n        \\n        similarityPublisher\\n            .assign(to: \\\\.similarityValidation, on: self)\\n            .store(in: &self.cancellableSet)\\n\\n\")), mdx(\"p\", null, \"We do the same thing for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"password\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"confirmPassword\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"similarityPublisher\"), \".\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Note: It is important to store the cancellable objects, failure to do so will result in the .\")), mdx(\"p\", null, \"There are two boolean properties to the top of file that we will use to activate and deactivate the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"login\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAccount\"), \" buttons which will then allow us to login or create an account.\"), mdx(\"h2\", null, \"Combine all together\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method, add the following block of code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n        Publishers.CombineLatest(emailPublisher, passwordPublisher)\\n            .map { emailValidation, passwordValidation  in\\n                emailValidation.success && passwordValidation.success\\n        }.assign(to: \\\\.canLogin, on: self)\\n            .store(in: &self.cancellableSet)\\n\\n\")), mdx(\"p\", null, \"Using combine\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CombineLatest\"), \" , we are able to combine the emailPublisher and passwordPublisher because combine is amazing. We then map through the tuple output by \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CombineLatest\"), \" to check whether the validation has been successful.\"), mdx(\"p\", null, \"Below that code , add the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" Publishers.CombineLatest4(emailPublisher, passwordPublisher, confirmPasswordPublisher, similarityPublisher)\\n            .map { emailValidation, passwordValidation, confirmedPasswordValidation, similarityValidation  in\\n                emailValidation.success && passwordValidation.success && confirmedPasswordValidation.success && similarityValidation.success\\n        }.assign(to: \\\\.canSignup, on: self)\\n            .store(in: &self.cancellableSet)\\n       \\n\")), mdx(\"p\", null, \"For the sign up button to activate, we will need all four publishers to return true. We use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CombineLastest4\"), \" for this task. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CombineLatest4\"), \" works similar to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CombineLatest\"), \".\"), mdx(\"h2\", null, \"EnvironmentObject\"), mdx(\"p\", null, \"We will use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" in a couple of places, it\\u2019s seems logical to put it in the environment object. \"), mdx(\"p\", null, \"Open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SceneDelegate.swift\"), \" file, and add the following line at the top of the class:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let authManager = AuthenticationManager()`\\n\")), mdx(\"p\", null, \"Then add this to the ContentView initialisation like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"   let contentView = ContentView()\\n         .environment(\\\\.managedObjectContext, context)\\n        .environmentObject(authManager)\\n\")), mdx(\"p\", null, \"This code will make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"authenticationManager\"), \" instance available to all of the views that need to use it. Pretty cool, right?\"), mdx(\"h2\", null, \"Login View\"), mdx(\"p\", null, \"Open the LoginView.swift file, and add the following property at the top of the file:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@EnvironmentObject private var authManager: AuthenticationManager\\n\")), mdx(\"p\", null, \"This line of code just get the environment object we set earlier, so that we can  use it in this view.\"), mdx(\"p\", null, \"Now, you will need to delete the email and password properties at the top, then replace where they are being used with the following respectively on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SharedTextfield\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordField\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.$authManager.email\\nself.$authManager.password\\n\")), mdx(\"p\", null, \"Next, replace the empty \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"errorMessage\"), \" value on both \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SharedTextfield\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PasswordField\"), \" with the following respectively:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"authManager.emailValidation.message\\nauthManager.passwordValidation.message\\n\")), mdx(\"p\", null, \"So what are we doing here? We are linking the published values in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" with the LoginView\\u2019s text fields. Run the app, add start typing in any of the textfields, you should see appropriate error message showing up.\"), mdx(\"p\", null, \"You should delete the local email and password properties in the login view\"), mdx(\"p\", null, \"Let\\u2019s deactivate the login button if there are still errors. Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"backgroundColor\"), \" with the following: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"self.authManager.canLogin ? Color.accent : Color.gray\"), \". Here we say if there are errors, set the color to gray otherwise, set it to the accent color.  \"), mdx(\"p\", null, \"Last add the following to the same login button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".disabled(!self.authManager.canLogin)\\n\")), mdx(\"p\", null, \"We use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"canLogin\"), \" published property to enable or disable the button depending on the validation state. \"), mdx(\"h2\", null, \"AccountCreationView\"), mdx(\"p\", null, \"Here we  do almost the same thing as what we did in the login view.\\nGet the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AuthenticationManager\"), \" object:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@EnvironmentObject private var authManager: AuthenticationManager\\n\")), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"email\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"password\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"confirmPassword\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.$authManager.email\\nself.$authManager.password\\nself.$authManager.confirmedPassword\\n\")), mdx(\"p\", null, \"Add the error validation message to each field respectively,:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"authManager.emailValidation.message\\nauthManager.passwordValidation.message\\nauthManager.confirmedPasswordValidation.message\\n\")), mdx(\"p\", null, \"Replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"backgroundColor\"), \" with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"self.authManager.canSignup ? Color.accent : Color.gray\\n\")), mdx(\"p\", null, \"Here we use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"canSignup\"), \" instead of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"canLogin\")), mdx(\"p\", null, \"Then add this to the sign up button:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".disabled(!self.authManager.canSignup)\\n\")), mdx(\"p\", null, \"The last thing to add here is the similarity validation message, so add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" below the password confirmation textfield:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"                      Text(self.authManager.similarityValidation.message).foregroundColor(Color.red)\\n\\n\")), mdx(\"p\", null, \"Run the app, add start playing with both forms to see the validation in action.\"), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"This is for this part folks. The main focus for this part was the validation and we did just that. If you have any issue understanding, correction or addition? Feel free to email me. Share this article and don\\u2019t forget to subscribe for more. \"), mdx(\"p\", null, \"If you want to support my work, do so by subscribing to the premium plan.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#validation-with-combine","title":"Validation with combine"},{"url":"#email-publisher","title":"Email publisher"},{"url":"#password-publisher","title":"Password publisher"},{"url":"#similaritypublisher","title":"similarityPublisher"},{"url":"#combine-all-together","title":"Combine all together"},{"url":"#environmentobject","title":"EnvironmentObject"},{"url":"#login-view","title":"Login View"},{"url":"#accountcreationview","title":"AccountCreationView"},{"url":"#conclusion","title":"Conclusion"}]}}}}]}},"pageContext":{"slug":"singlepass-the-lastpass-redesign"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}