How to pass data from a TextField to a List (SwiftUI)

I have an app I'm working on where you make little digital ID cards with four pieces of data that I need to pass from a TextField to a List:
  1. Your Name (name)

  2. Your ID for Card (id)

  3. QR Code or Barcode? (qrcode Toggle)

  4. Card Name (cname)

This is my CardsView:
Code Block
import SwiftUI
struct Card: Identifiable {
    let id = UUID()
    let title: String
}
struct CardsView: View {
    @State private var editMode = EditMode.inactive
    @State private var cards: [Card] = []
        private static var count = 0
    var body: some View {
        NavigationView {
            List {
                ForEach(cards) { cards in
                    NavigationLink(destination: CardFullView(cname: "Moore Lunch")) {
                        CardRow(cname: "Lunch", name: "John Doe", id: "123456")
                    }
                }
                .onDelete(perform: onDelete)
                .onMove(perform: onMove)
            }
                .navigationTitle("Cards")
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        EditButton()
                    }
                    ToolbarItem(placement: .navigationBarTrailing) {
                        NavigationLink(
                            destination: AddView(),
                            label: {
                                Image(systemName: "plus")
                            })
                    }
            }
            .environment(\.editMode, $editMode)
        }
    }
    private var addButton: some View {
        switch editMode {
        case .inactive:
            return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
        default:
            return AnyView(EmptyView())
        }
    }
    func onAdd() {
        cards.append(Card(title: "Card #\(Self.count)"))
            Self.count += 1
    }
    private func onDelete(offsets: IndexSet) {
        cards.remove(atOffsets: offsets)
    }
    private func onMove(source: IndexSet, destination: Int) {
        cards.move(fromOffsets: source, toOffset: destination)
    }
}
struct CardsView_Previews: PreviewProvider {
    static var previews: some View {
        CardsView()
    }
}

...and my AddView:
Code Block
import SwiftUI
struct CardFullView: View {
    let cname: String
    var body: some View {
        NavigationView {
            CardView(name: "John Doe", id: "123456")
                .navigationTitle(cname)
                .navigationBarTitleDisplayMode(.inline)
        }
    }
}
struct CardFullView_Previews: PreviewProvider {
    static var previews: some View {
        CardFullView(cname: "Lunch")
    }
}

CardRow shows an HStack, consisting of a generated QR Code or Barcode, a VStack with a title showing cname, a headline showing name, and a subheadline showing id.

I want to pass the value of the TextFields into the CardRows.

Accepted Reply

Sorry for the late reply.

Thanks for clarifying and sorry for missing the reply.

Most parts get cleared, but it seems you have touched your code since then.

There's another bug happening and I do not understand what this means or why you do not want it.

I figured out a way to show the cardInfo data in the list, but it's not the way I want it

I think it's time for you to start your new thread to solve such another thing.
Please do not forget to include all the latest code when starting a new thread.

Replies

what about making cards a binding instead of a State ?

    @State private var cards: [Card] = []
Claude31:
I've tried this and it gave me three errors:
  1. Cannot convert value of type '[Any]' to specified type 'Binding<[Card]?>'

  2. Extraneous argument label 'wrappedValue:' in call

  3. Value of optional type 'Binding<[Card]>?' must be unwrapped to refer to member 'wrappedValue' of wrapped base type 'Binding<[Card]>'

Anything I missed?
By the way I totally messed up my post, this is AddView:
Code Block
import SwiftUI
struct AddView: View {
    @State var name: String = ""
    @State var id: String = ""
    @State var cname: String = ""
    @State var qrcode = true
    
    var body: some View {
        NavigationView {
            VStack {
                CardView(name: name, id: id)
                TextField("Name", text: $name)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                TextField("ID", text: $id)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                Toggle(isOn: $qrcode) {
                    if qrcode {
                        Text("QR Code")
                    } else {
                        Text("Barcode")
                    }
                }
                TextField("Card Name", text: $cname)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                Button(action: {}) {
                    Text("Create")
                        .bold()
                }
                .disabled(self.name.isEmpty self.id.isEmpty self.cname.isEmpty)
                .foregroundColor(.white)
                .padding()
                .padding(.horizontal, 100)
                .background(Color.accentColor)
                .cornerRadius(10)
            }.padding()
            .navigationTitle(cname)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}
struct AddView_Previews: PreviewProvider {
    static var previews: some View {
        AddView()
    }
}

Can you show where exactly you get the errors ?
Can't provide an image (Apple's policy, I guess), but here's a rough area of where it is:
Code Block
struct CardsView: View {
    @State private var editMode = EditMode.inactive
    @Binding private var cards: [Card] = []
        private static var count = 0

And where @Binding is is where the error occurred. To further this, here's the options it provided:
  1. Insert "

  2. Chain the optional using '?' to access member 'wrappedValue' only for non-'nil' base values

  3. Force-unwrap using '!' to abort execution if the optional value contains 'nil'

Option 1 does literally nothing while the others add a ? or ! after the empty square brackets of line 3.

this is AddView:

Your AddView has 4 input fields: name, id, cname and qrcode.
But the struct Card used for List does not have properties for them.

How do you want to store the values received from AddView?

How do you want to store the values received from AddView?

First off, I probably should have provided the code for CardRow:
Code Block
import SwiftUI
import CoreImage.CIFilterBuiltins
struct CardRow: View {
    let context = CIContext()
    let filter = CIFilter.qrCodeGenerator()
    let cname: String
    let name: String
    let id: String
    func generateQRCode(from string: String) -> UIImage {
        let data = Data(string.utf8)
        filter.setValue(data, forKey: "inputMessage")
        if let outputImage = filter.outputImage {
            if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
                return UIImage(cgImage: cgimg)
            }
        }
        return UIImage(systemName: "xmark.circle") ?? UIImage()
    }
    
    var body: some View {
        HStack {
            Image(uiImage: generateQRCode(from: id))
                .interpolation(.none)
                .resizable()
                .scaledToFit()
                .frame(width: 87.5, height: 87.5)
                .cornerRadius(5)
            VStack(alignment: .leading) {
                Text(cname)
                    .font(.title)
                    .bold()
                Text(name)
                    .font(.headline)
                    .bold()
                Text(id)
                    .font(.subheadline)
            }
        }
    }
}

And second, like I said in my original post, I want the data to pass through CardRow so it can show in the List in CardsView. And I also need to know the right declaration of name, id, cname, and qrcode. Whether it's a let or a var (or neither) and if it's a String or a Double (or neither). Sorry if this is not your answer, I just need some elaboration 😁.

so it can show in the List in CardsView.

If you want to show name, id, cname and qrcode in the List, you need to hold a collection containing name, id, cname and qrcode.
How do you want to have it?

....you need to hold a collection containing name, id, cname and qrcode.
How do you want to have it?

I want an HStack with qrcode on the left while on the right there is a VStack showing cname, name, and id respectively.
It would just show that data in the CardRow. The list is calling CardRow with the data:
Code Block
List {
ForEach(cards) { cards in
NavigationLink(destination: CardFullView(cname: [how AddView() provides cname])) {
CardRow(cname: [how AddView() provides cname], name: [how AddView() provides name], id: [how AddView() provides id])                   
}               
}               
.onDelete(perform: onDelete)               
.onMove(perform: onMove)           
}

CardRow automatically generates a QR code from what id provides.
CardRow is just a View which cannot be a storage for showing a List.

In detail, in which way do you want to store the passed values?
  • Extend Card and add properties for the values

  • Add another @State array(s) to CardsView

  • Define a ObservableObject holding the values and use it as @ObservedObject in CardView

  • Some other way

Define a ObservableObject holding the values and use it as @ObservedObject in CardView

I would probably want to do this, but would it just be the CardView that's just the ZStack or CardsView with the List?

I would probably want to do this, but would it just be the CardView that's just the ZStack or CardsView with the List?

Sorry, you are right. It should be CardsVew as well as the second option.
You're fine!
Since I'm not too experienced with Swift/SwiftUI yet, I'll need some help ObservableObject...could you help me out?

could you help me out?

ObservableObject is just a class:
  • Conforming to protocol ObservableObject

  • Holding @Published values, which triggers UI updates

An example.

CardsInfo.swift
Code Block
import Foundation
struct CardInfo {
var name: String = ""
var id: String = ""
var cname: String = ""
var qrcode = true
}
class CardsInfo: ObservableObject {
@Published var newCard: CardInfo = CardInfo()
}


Using this class, CardsView would look like:
Code Block
struct CardsView: View {
@StateObject var cardsInfo = CardsInfo() //<-
@State private var editMode = EditMode.inactive
@State private var cards: [Card] = []
private static var count = 0
var body: some View {
NavigationView {
List {
ForEach(cards) { cards in
NavigationLink(destination: CardFullView(cname: "Moore Lunch")) {
CardRow(cname: "Lunch", name: "John Doe", id: "123456")
}
}
.onDelete(perform: onDelete)
.onMove(perform: onMove)
}
.navigationTitle("Cards")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(
destination: AddView(cardInfo: $cardsInfo.newCard), //<-
label: {
Image(systemName: "plus")
})
}
}
.environment(\.editMode, $editMode)
}
}
//...
}


And AddView:
Code Block
struct AddView: View {
//Remove the followings...
// @State var name: String = ""
// @State var id: String = ""
// @State var cname: String = ""
// @State var qrcode = true
//And add binding
@Binding var cardInfo: CardInfo
var body: some View {
NavigationView {
VStack {
CardView(name: cardInfo.name, id: cardInfo.id) //<-
TextField("Name", text: $cardInfo.name) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
TextField("ID", text: $cardInfo.id) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
Toggle(isOn: $cardInfo.qrcode) { //<-
if cardInfo.qrcode {
Text("QR Code")
} else {
Text("Barcode")
}
}
TextField("Card Name", text: $cardInfo.cname) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
Button(action: {}) {
Text("Create")
.bold()
}
.disabled(self.cardInfo.name.isEmpty self.cardInfo.id.isEmpty self.cardInfo.cname.isEmpty) //<-
.foregroundColor(.white)
.padding()
.padding(.horizontal, 100)
.background(Color.accentColor)
.cornerRadius(10)
}.padding()
.navigationTitle(cardInfo.cname) //<-
.navigationBarTitleDisplayMode(.inline)
}
}
}


it looks like your onAdd is under development, but you can use  cardsInfo.newCard.namecardsInfo.newCard.id, cardsInfo.newCard.cname and cardsInfo.newCard.qrcode, in action closures or instance methods of CardsView.
I'll try this out!