Reputation: 364
I'm having an issue grabbing the state out of an array of SwiftUI structs.
I have a struct for cards that contain topText and bottomText. I have another struct that is a form for input components such as the card. I store these cards in an array, appending more and more as a + button is clicked. In the parent view I have a foreach over that array so they render dynamically.
I'm having an issue with trying to extract the state value from these structs. The cards contain state for the topText and bottomText. When a save button is clicked in the parent array (where the array of cards exists), it iterates over the array of cards, and prints their state through a get method. However, it is not printing the current state of those cards. It prints a blank string. I believe this is happening because when I append the struct, it's merely copying a dead struct, and not the real stateful struct.
Do I have my assumptions correct? Or is this a bug in swiftUI? Does anyone have any ideas on a proper way to do this? I just want to get the input out of the cards whenever the button is clicked. It needs to be dynamic though, as the user can click the + button however many times they want and continue making more cards.
Heres the code for the card stuct:
struct memeCard: View, Identifiable{
@State private var topText = ""
@State private var bottomText = ""
let id = UUID()
// TODO: figure out how to make text wrap so it doesn't go on one line forever.
var body: some View{
VStack(spacing: 30){
TextField("Top text...", text: $topText).multilineTextAlignment(.center)
Divider()
TextField("Bottom text...", text: $bottomText)
}
.frame(width: 300, height: 350, alignment: .center).multilineTextAlignment(.center)
.background(Color.white)
.cornerRadius(20)
.shadow(color: Color.gray, radius: 4, x: 4, y: 4)
}
func getBottomText() -> String{
return self.bottomText
}
func getTopText() -> String{
return self.topText
}
}
And here is the parent view:
struct CreateMemePage: View {
@Environment(\.presentationMode) var presentation
let realm = try! Realm()
@State private var sectionInput = ""
@State private var memeTitleInput = ""
@State private var offsetValue: CGFloat = 0.0
@State private var createCards: [memeCard] = [memeCard()]
var body: some View {
NavigationView{
VStack(spacing: 30){
ScrollView{
TextField("Section...", text: $sectionInput)
.multilineTextAlignment(.center)
.frame(width: 350, height: 75, alignment: .center)
.background(Color.white)
.cornerRadius(15)
.shadow(color: Color.gray, radius: 4, x: 4, y: 4)
TextField("Meme Title...", text: $memeTitleInput)
.multilineTextAlignment(.center)
.frame(width: 350, height: 75, alignment: .center)
.background(Color.white)
.cornerRadius(15)
.shadow(color: Color.gray, radius: 4, x: 4, y: 4)
ForEach(0..<self.createCards.count, id: \.self){ item in
self.createCards[item].padding()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}
Button(action: {
self.createCards.append(memeCard())
}) {
Image(systemName: "plus")
.accentColor(Color.white)
.frame(width: 50, height: 50, alignment: .center)
.background(LinearGradient(gradient: Gradient(colors: [ .red, .purple]), startPoint: .top, endPoint: .trailing))
.cornerRadius(60)
.shadow(color: Color.gray, radius: 2, x: 3, y: 3)
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.keyboardSensible($offsetValue)
.navigationBarTitle("Create meme")
.navigationBarItems(trailing:
Button(action: {
for item in createCards{
print(item.getBottomText())
print(item.getTopText())
}
}){
Text("Save")
}
) }
}
}
Upvotes: 1
Views: 1266
Reputation: 13300
You need to know how to use ObservableObject
, @ObservedObject
, @State
, and @Binding
.
But for simplicity, let's take a look how we can make your memeCard
functions work.
Creating a model
class Model: ObservableObject {
var topText: String = ""
var bottomText: String = ""
init(top: String, bottom: String) {
self.topText = top
self.bottomText = bottom
}
}
Using a model instance inside memeCard
View.
struct memeCard: View, Identifiable{
@ObservedObject var model = Model(top: "", bottom: "")
let id = UUID()
...
Next, we bind the model
instance to the textFields.
VStack(spacing: 30){
TextField("Top text...", text: self.$model.topText).multilineTextAlignment(.center)
Divider()
TextField("Bottom text...", text: self.$model.bottomText)
}
And then we can extract the data from the model class using your existing top and bottom functions, like so:
func getBottomText() -> String{
print("BOTTOM: \(self.model.bottomText)")
return self.model.bottomText
}
func getTopText() -> String{
print("TOP: \(self.model.topText)")
return self.model.topText
}
Voila! It now should work. This answer from another question https://stackoverflow.com/a/57623488/3231194 should help you out, it's written comprehensively.
Upvotes: 1