Variable List of TextFields

I’m asking for advise on the code below. Note this part…

’ List($theAddresses, id: .self) { addr in
TextField(“Address”, text: addr)
}
HStack {
TextField(“City”, text: $city)
TextField(“State”, text: $state)
TextField(“Zip Code”, text: $zipPostal)
}

Using the simulator, I can put the cursor into the “Address” field, but after a keystroke, the field looses focus and I have to click in the field again. I looked at several solutions, but they all seemed overly complicated. The complete view code is below…


//
// AddressView.swift
// Secure Address Book
//
// Created by Jim Olivi on 1/19/22.
//

import SwiftUI

struct AddressView: View {

var oldAddress: Address

@Environment(\.presentationMode) var doDismiss

@State private var theAddresses: [String] = [String]()
@State private var city: String = ""
@State private var state: String = ""
@State private var zipPostal: String = ""

@State private var statusMessage: String = ""

var body: some View {
    GeometryReader { geo in
        //MARK: Enter view
        Form {
            Section(header: HStack {
                Button(action: setData) {
                    Text("Cancel")}
                Spacer()
                Button(action: saveData) {
                    Text("Save")}
                Spacer()
                Button(action: { deleteAddress(oldAddress: oldAddress)
                    doDismiss.wrappedValue.dismiss()
                }) {Text("Delete")}
            }, footer: VStack {
                Text(statusMessage)
                    .font(.headline)
                    .fontWeight(.bold)
            }, content: {
                List($theAddresses, id: \.self) { addr in
                    TextField("Address", text: addr)
                }
                HStack {
                    TextField("City", text: $city)
                    TextField("State", text: $state)
                    TextField("Zip Code", text: $zipPostal)
                }
            })
        }
        .frame(width: geo.size.width, height: geo.size.height * 0.6, alignment: .center)
        .navigationTitle("Address")
        .environmentObject(Address())
        .onAppear{setData()}
        .onDisappear {flushAddressCoreData()}
    }
}

func setData() {
    // Restore the old properties
    theAddresses = oldAddress.address!
    city = oldAddress.city ?? ""
    state = oldAddress.state ?? ""
    zipPostal = oldAddress.zipPostalCode ?? ""
    
    statusMessage = "\(oldAddress.address![0]) information displayed."
}

func saveData() {
    oldAddress.address! = theAddresses
    oldAddress.city = city
    oldAddress.state = state
    oldAddress.zipPostalCode = zipPostal
    
    statusMessage = "Address Saved for \(theAddresses[0])"
}


//struct AddressView_Previews: PreviewProvider {
//    static var previews: some View {
//
//        var a = Address()
//
//        a.address = "125 Veterans Way"
//        a.city = "Carmel"
//        a.state = "IN"
//        a.zipPostalCode = "46032"
//
//        AddressView(oldAddress: a)
//    }
//}

}

@JimOlivi

Hi Jim,

When you post your code, to format the code nicely, place 3 back-ticks ``` on the line above your code and 3 back-ticks ``` on the line below your code. Like this:

```
Code goes here
```

The 3 back-ticks must be the ONLY characters on the line. The back-tick character is located on the same keyboard key as the tilde character ~ (which is located below the Esc key).

Alternatively after you paste in your code, select that code block and click the </> button on the toolbar. This does the same thing as manually typing the 3 back ticks on the line above and below your code.

This also makes it easier for anyone assisting as they can copy the code and carry out some testing.

With regard to your question:

Is this AddressView just so that you can either

  • edit an existing address and save any changes and/or
  • delete that address

@JimOlivi Your problem stems from looping through an array of strings and using \.self to identify them. So when you change an address string, its id changes, which means SwiftUI thinks you have changed the array and so it re-renders the entire List to the screen, which causes your TextField to lose focus.

You can see what I mean if you change your code like this:

struct AddressView: View {
    
    var oldAddress: Address
    
    @Environment(\.presentationMode) var doDismiss

    struct StreetAddress: Identifiable {
        let id = UUID()
        var address: String
    }
    
    @State private var theAddresses: [StreetAddress] = [
        StreetAddress(address: "742 Evergreen Terrace"),
        StreetAddress(address: "1313 Mockingbird Lane"),
        StreetAddress(address: "85 Royal View Rd"),
    ]
    @State private var city: String = ""
    @State private var state: String = ""
    @State private var zipPostal: String = ""
    
    @State private var statusMessage: String = ""
    
    var body: some View {
        GeometryReader { geo in
            //MARK: Enter view
            Form {
                Section {
                    List($theAddresses) { $addr in
                        TextField("Address", text: $addr.address)
                    }
                    HStack {
                        TextField("City", text: $city)
                        TextField("State", text: $state)
                        TextField("Zip Code", text: $zipPostal)
                    }
                } header: {
                    HStack {
                        Button(action: setData) {
                            Text("Cancel")
                        }
                        Spacer()
                        Button(action: saveData) {
                            Text("Save")
                        }
                        Spacer()
                        Button(action: {
                            deleteAddress(oldAddress: oldAddress)
                            doDismiss.wrappedValue.dismiss()
                        }) {
                            Text("Delete")
                        }
                    }
                } footer: {
                    VStack {
                        Text(statusMessage)
                            .font(.headline)
                            .fontWeight(.bold)
                    }
                }
            }
            .frame(width: geo.size.width, height: geo.size.height * 0.6, alignment: .center)
            .navigationTitle("Address")
            .environmentObject(Address())
            .onAppear{setData()}
            .onDisappear {flushAddressCoreData()}
        }
    }
    
    func setData() {
        // Restore the old properties
        theAddresses = oldAddress.address!
        city = oldAddress.city ?? ""
        state = oldAddress.state ?? ""
        zipPostal = oldAddress.zipPostalCode ?? ""

        statusMessage = "\(oldAddress.address![0]) information displayed."
    }
    
    func saveData() {
        oldAddress.address! = theAddresses
        oldAddress.city = city
        oldAddress.state = state
        oldAddress.zipPostalCode = zipPostal

        statusMessage = "Address Saved for \(theAddresses[0])"
    }
    
}

Here, instead of an array of strings we are using an array of StreetAddress structs which are Identifiable by something other than the address string inside. That way, updating one of the TextFields lets SwiftUI realize that you haven’t changed the array of items being looped through, just a property of one of those items. So it doesn’t re-render the entire List and the TextField doesn’t lose focus.

(Note that I also cleaned up the code a little so it’s not 100% exactly like you posted.)