Search code examples
swiftuidataflowswiftui-list

How to Add Color beside text into the list in swiftUI?(Data Flow)


I try to get( text & color ) from user and add them to the list in SwiftUI I already can pass text data but unfortunately for color I can't while they should be the same, below there is an image of app.To work we should provide a Binding for PreAddTextField .Thanks for your help enter image description here here is my Code :

import SwiftUI

struct AddListView: View {


@Binding var showAddListView : Bool
@ObservedObject var appState : AppState
@StateObject private var viewModel = AddListViewViewModel()

var body: some View {
    ZStack {
        
        Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "", color: .purple)) })
        
        VStack {
            ScrollView {
                ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: 

\.id and not \.self
                        PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id), colorPickerColor: <#Binding<Color>#>)
                }
                
            }
        }
        .padding()
        .offset(y: 40)
        
        Buttons(showAddListView: $showAddListView, save: {
                        viewModel.saveToAppState(appState: appState)
                    })
        
    }
    .frame(width: 300, height: 200)
    .background(Color.white)
    .shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        AddListView(showAddListView: .constant(false),appState: AppState())
    }
}

struct PreAddTextField: View {


@Binding var textInTextField : String
@Binding var colorPickerColor :  Color

var body: some View {
    HStack {
        TextField("Enter text", text: $textInTextField)
        ColorPicker("", selection: $colorPickerColor)
    }
}
}

struct Buttons: View {
    @Binding var showAddListView : Bool
    var save : () -> Void
    var body: some View {
        VStack {
            HStack(spacing:100) {
                Button(action: {
                        showAddListView = false}) {
                    Text("Cancel")
                }
                Button(action: {
                    showAddListView = false
                    // What should happen here to add Text to List???
                    save()
                }) {
                    Text("Add")
                }
            }
        }
        .offset(y: 70)
    }
}

struct Title: View {

var addItem : () -> Void

var body: some View {
    VStack {
        HStack {
            Text("Add Text to list")
                .font(.title2)
            Spacer()
            Button(action: {
                addItem()
            }) {
                Image(systemName: "plus")
                    .font(.title2)
            }
        }
        .padding()
        Spacer()
    }
}
}

DataModel :

import SwiftUI

struct Text1 : Identifiable , Hashable{
    var id = UUID()
    var text : String
    var color : Color
}

class AppState : ObservableObject {
    @Published var textData : [Text1] = [.init(text: "Item 1", color: .purple),.init(text: "Item 2", color: .purple)]
}

class AddListViewViewModel : ObservableObject {
    @Published var textItemsToAdd : [Text1] = [.init(text: "", color: .purple)] //start with one empty item

//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
    appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}

//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
    .init { () -> String in
        self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
    } set: { (newValue) in
        self.textItemsToAdd = self.textItemsToAdd.map {
            guard $0.id == id else {
                return $0
            }
            return .init(id: id, text: newValue, color: .purple)
        }
    }
}
}

and finaly :

import SwiftUI

struct ListView: View {

@StateObject var appState = AppState() //store the AppState here
@State private var showAddListView = false

var body: some View {
    NavigationView {
        VStack {
            ZStack {
                List(appState.textData, id : \.self){ text in
                    HStack {
                        Image(systemName: "square")
                            .foregroundColor(text.color)
                        Text(text.text)
                    }
                                        
                                    }
                if showAddListView {
                                        AddListView(showAddListView: $showAddListView, appState: appState)
                                            .offset(y:-100)
                                    }
            }
        }
        .navigationTitle("List")
        .navigationBarItems(trailing:
                                Button(action: {showAddListView = true}) {
                                    Image(systemName: "plus")
                                        .font(.title2)
                                }
        )
    }
}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ListView()
    }
}

Solution

  • Instead of using a Binding for just the String, you could create a Binding for the entire Text1 item:

    class AddListViewViewModel : ObservableObject {
        @Published var textItemsToAdd : [Text1] = [.init(text: "", color: .purple)]
        
        func saveToAppState(appState: AppState) {
            appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
        }
        
        func bindingForId(id: UUID) -> Binding<Text1> { //now returns a Text1
            .init { () -> Text1 in
                self.textItemsToAdd.first(where: { $0.id == id }) ?? Text1(text: "", color: .clear)
            } set: { (newValue) in
                self.textItemsToAdd = self.textItemsToAdd.map {
                    guard $0.id == id else {
                        return $0
                    }
                    return newValue
                }
            }
        }
    }
    
    struct PreAddTextField: View {
        @Binding var item : Text1
        
        var body: some View {
            HStack {
                TextField("Enter text", text: $item.text) //gets the text property of the binding
                ColorPicker("", selection: $item.color) //gets the color property
            }
        }
    }
    
    struct AddListView: View {
        @Binding var showAddListView : Bool
        @ObservedObject var appState : AppState
        @StateObject private var viewModel = AddListViewViewModel()
        
        var body: some View {
            ZStack {
                Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "", color: .purple)) })
                VStack {
                    ScrollView {
                        ForEach(viewModel.textItemsToAdd, id: \.id) { item in
                            PreAddTextField(item: viewModel.bindingForId(id: item.id)) //parameter is changed here
                        }
                    }
                }
                .padding()
                .offset(y: 40)
                
                Buttons(showAddListView: $showAddListView, save: {
                    viewModel.saveToAppState(appState: appState)
                })
                
            }
            .frame(width: 300, height: 200)
            .background(Color.white)
            .shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
        }
    }