NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

Code review request

Forums > SwiftUI

I've been working on an app for some time to teach myself SwiftUI and Core Data. Would anyone be willing to take a look and help me understand a few things:

1) I'd like to switch to using @Binding for closing subviews, but can't seem to get it to work. 2) I would like to figure out how to best refactor for creating a few "edit" screens.

Let me know and Thanks in advance.

   

Hi Michael

I'd be happy to help.

is your code on github or somewhere to take a look?

Richard

   

Hi @rlong405 it's on github https://github.com/michaelrowe01/Card-Tracker.org

Thanks for any feedback.

   

Hacking with Swift is sponsored by Stream

SPONSORED Check out Stream's cross-platform open source chat SDK on GitHub! Write once and deploy your app with fully featured chat UI on iOS and macOS.

Go to GitHub

Sponsor Hacking with Swift and reach the world's largest Swift community!

Hi @michaelrowe01,

In terms of dismissing subviews, a lot of your subviews are modals (sheets) and I see you have used

self.presentationMode.wrappedValue.dismiss()

This is a perfectly fine way to dismiss a sheet. Are you looking to use bindings to toggle a bool back to false and close the sheet that way? There really is no need to use that method unless you particularly want to but happy to guide on how to do it if you'd like me to .

Richard

   

Then in terms of your edit screens, below is something to edit your recipient (I've assume this would be called from a new button on ViewEventsView).

This would be a new view

Note - I wouldn't normally NOT have the save function in the view but for ease have done that here. My main piece of advise across your code is to get your logic out of your views. - It's not essential right now but it will tidy things up a lot. I can help point you in the right direction on how to do that if you'd like

Below is the code for the new edit view. Let me know if you need help with how to show this new view. You can call it as a sheet but I have assumed it will be pushed on the navigation view stack (and have dismissed it as such once the changes are made).

import SwiftUI

struct EditRecipient: View {

   var recipient: Recipient

   @State private var firstName: String = ""
   @State private var lastName: String = ""
   @State private var addressLine1: String = ""
   @State private var addressLine2: String = ""
   @State private var city: String = ""
   @State private var state: String = ""
   @State private var zip: String = ""
   @State private var country: String = ""

   @State private var showHome: Bool = false

   init(recipient: Recipient){
      self.recipient = recipient
      self._firstName = State(initialValue: recipient.firstName ?? "")
      self._lastName = State(initialValue: recipient.lastName ?? "")
      self._addressLine1 = State(initialValue: recipient.addressLine1 ?? "")
      self._addressLine2 = State(initialValue: recipient.addressLine2 ?? "")
      self._city = State(initialValue: recipient.city ?? "")
      self._state = State(initialValue: recipient.state ?? "")
      self._zip = State(initialValue: recipient.zip ?? "")
      self._country = State(initialValue: recipient.country ?? "")
   }

    var body: some View {
      NavigationLink (destination: ViewRecipientsView(), isActive: self.$showHome){ EmptyView() }
      VStack {
         HStack {
         Text("First Name")
            TextField("FirstName", text: $firstName)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("Last Name")
            TextField("Last Name", text: $lastName)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("Address Line 1")
            TextField("Address Line 1", text: $addressLine1)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("Address Line 2")
            TextField("Address Line 2", text: $addressLine2)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("City")
            TextField("City", text: $city)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("State")
            TextField("State", text: $state)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("Zip")
            TextField("Zip", text: $zip)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }
         HStack {
         Text("Country")
            TextField("Country", text: $country)
               .textFieldStyle(RoundedBorderTextFieldStyle())
         }

         Button(action: {saveRecipient(recipient: recipient, firstName: firstName, lastName: lastName, addLine1: addressLine1, addLine2: addressLine2, city: city, state: state, zip: zip, country: country)

            showHome.toggle()

         })
            {
            Text("Save")
         }
         Spacer()

      }.padding()
    }
}

func saveRecipient(recipient:Recipient,firstName:String, lastName:String, addLine1: String, addLine2: String, city:String, state:String, zip:String, country:String){

   let context = PersistentCloudKitContainer.persistentContainer.viewContext
   recipient.firstName = firstName
   recipient.lastName  = lastName
   recipient.addressLine1 = addLine1
   recipient.addressLine2 = addLine2
   recipient.city = city
   recipient.state = state
   recipient.zip = zip
   recipient.country = country

   do {
      try context.save()

   }
   catch {
      print(error.localizedDescription)
   }

}

   

thanks for the thourough response. I do want to remove the .presentationMode as the app ends up closing the new Receipient modal, when I cancel the ContactsPicker. I can't seem to figure out how to stop that.

I've never seen the State(initialValue:) function. That makes things much clearer. I will give it a shot this week and see if I can figure out how to write it up correctly!

   

I was looking at AddNewRecipientView and while using .textFieldStyle(RoundedBorderTextFieldStyle()) reduces the code does not give you the same look however if you create a file with this in

import SwiftUI

struct TextFieldModifier: ViewModifier {
    let borderWidth: CGFloat = 1.0
    func body(content: Content) -> some View {
        content
            .padding(10)
            .font(Font.system(size: 15, weight: .medium, design: .serif))
            .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.green, lineWidth: borderWidth))
    }
}

extension View {
    func customTextField() -> some View {
        self.modifier(TextFieldModifier())
    }
}

then you can change the input fields to

TextField("First Name", text: $firstName)
  .customTextField()

or

TextField("Zip", text: $zip)
  .customTextField()
  .frame(width: geomtry.size.width * 0.28)

PS you can delete private let borderWidth:CGFloat = 1.0 as it in the modilfier

PSS you can use this anywhere that you have a TextField in the app to have the same look

1      

Hi Michael

no problems,

ok I'll take a look at the example you've mentioned about closing the modals tonight and see if I can understand why that's happening / change to binding for you.

the State initial value is good to use as it is straight away setting the values of your state variables so your textfields pre-populate with the data from core data (ready for editing).

   

Below code will stop the Recipient view being closed when you close your contacts picker.

 .sheet(isPresented: $showPicker) {
                ContactPicker(showPicker: $showPicker, onSelectContact: {contact in
                    firstName = contact.givenName
                    lastName = contact.familyName
                    if contact.postalAddresses.count > 0 {
                        if let addressString = (((contact.postalAddresses[0] as AnyObject).value(forKey: "labelValuePair") as AnyObject).value(forKey: "value")) as? CNPostalAddress {
                            let mailAddress = CNPostalAddressFormatter.string(from:addressString, style: .mailingAddress)
                            addressLine1 = "\(addressString.street)"
                            addressLine2 = ""
                            city = "\(addressString.city)"
                            state = "\(addressString.state)"
                            zip = "\(addressString.postalCode)"
                            country = "\(addressString.country)"
                            print(mailAddress)
                        }
                       self.showPicker.toggle()
                    }
                },  onCancel: nil)
                }

There is an issue though where the contacts picker will show an blak screen briefly as it loads and disappears.

This does seem to be a known issue though, see link.

https://stackoverflow.com/questions/60116066/list-of-contacts-from-contacts-app-in-swiftui

   

This contacts picker seems to work quite well (after you change animated to true within the open func).

https://github.com/sramp/SwiftUIContactPicker

It also doesn't need any external frameworks.

Migth be worth a look

Richard

1      

Awesome thanks Robert! Will check it out...

   

Tried the first one, and while it does fix it from going away when you hit cancel, it also shows a blank sheet if you select a contact, instead of coming back to an editable form.

On the second example, there is also a bit of a weird screen behavior. The picker seems to flash behind the screen when it is being dismissed.

   

Hi Micheal,

Thank you for the credit when you undated github. I was thinking you might want to have a different colour for the required fields you could do that with

import SwiftUI

struct TextFieldModifier: ViewModifier {
    let borderWidth: CGFloat = 1.0
    let color: Color // <- add color property

    func body(content: Content) -> some View {
        content
            .padding(10)
            .font(Font.system(size: 15, weight: .medium, design: .serif))
            .overlay(RoundedRectangle(cornerRadius: 10)
                        .stroke(color, lineWidth: borderWidth) // <- pass in color
            )
    }
}

extension View {
    func customTextField(color: Color = .green) -> some View { // <- Set to default color
        self.modifier(TextFieldModifier(color: color))
    }
}

then when you want a different colour

TextField("First Name", text: $firstName)
  .customTextField(color: .red)

and when you want default colour (.green)

TextField("Address Line 2", text: $addressLine2)
  .customTextField()

   

This is a learning project for me, so both you and Robert will get mention in the GIT. I hope to put it up as a free app on the app store sometime soon.

   

Hi Michael;

I've integrated that second Contact Picker I found into your code and I don't see the weird screen behaviour you mention on dismiss.

Did you put EmbeddedContactPicker() at the bottom of the ZStack so that it is at the front?

e.g.

 ZStack {
                   if (!self.contactObj.showContactPicker){

                VStack{
                    Spacer()
                    HStack {
                        VStack (alignment: .leading){
                           TextField("First Name", text: $contactObj.firstName)
                                .customTextField()
                        }
                        VStack (alignment: .leading) {
                            TextField("Last Name", text: $contactObj.lastName)
                                .customTextField()
                        }
                    }
                    TextField("Address Line 1", text: $contactObj.addressLine1)
                        .customTextField()
                    TextField("Address Line 2", text: $contactObj.addressLine2)
                        .customTextField()
                    HStack{
                        TextField("City", text: $contactObj.city)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.48)
                        Spacer()
                        TextField("ST", text: $contactObj.state)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.18)
                        Spacer()
                        TextField("Zip", text: $contactObj.zip)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.28)
                    }
                    TextField("Country", text: $contactObj.country)
                        .customTextField()
                    Spacer()
                    Spacer()
                }

                   }
                   else {

                     EmbeddedContactPicker()
                   }
               }

   

hi Robert, I did, i will try again tomorrow, and see if I did something wrong.

   

ok

i implemented on top of your code base from last night (copy took about 23:00 UK time) so can share that code if it helps. Im not great with github so not sure if there is a way I can share my updates with you there without overwriting your code. Maybe you know more

P.S. my names Richard btw, not Robert :)

Thanks

   

Damn, How'd I get the name wrong, my deepest appology.... Thank you for the correction...

If you just want to commit the code in Git.. that would work I have not made changes last night or today. Alternatively drop me an email at michaelrowe01 over at gmail

When I tried it, I am getting a weird screen blink.

   

Hey Michael;

No problems at all, don't worry, no apology necessary.

You are right actually, there is a screen blink and I've just noted a bigger problem with the contact picker I found - when I load the picker but then cancel, the form behind it disappears completely so that's no good.

I think I'm probably reaching the point where I'm just guessing and trying various things (no real change there :-)). I've never implemented a contactPicker before.

I suggest maybe creating a new question specifically on the contact picker to see if anyone has any neat code or can work out what the problem is here.

If I can help at any further with anything else in your code then please do let me know though.

1      

Thanks Richard, I am thinking at this point in time, I will keep the one I have and just say, if you can cancel the picker, you are canceling the whole new Reipient. Thanks again for the help on that! Have a great weekend!

   

Hacking with Swift is sponsored by Stream

SPONSORED Check out Stream's cross-platform open source chat SDK on GitHub! Write once and deploy your app with fully featured chat UI on iOS and macOS.

Go to GitHub

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.