Home > other >  Swiftui items get duplicated in all views when added to a single custom view
Swiftui items get duplicated in all views when added to a single custom view

Time:08-05

I'm struggling with the following issue: I'm trying to build a very simple app that lets you add items in a dedicated view that can be collapsed. I managed to write a simple function that lets me add multiple of these custom collapsable views. It's my first app so I wanted to follow the MVVM protocol. I think I got confused along the way because now every item I add gets automatically added to all the custom collapsable views I made. Is there any way to fix this? I thought using the UUID would solve this issue.. I'm guessing that I have to customise the "saveButtonPressed" function, but I don't know how to tell it to only add the item to the view where I pressed the "plus" button..

Here are the Models for the individual items and the collapsable view:

struct ItemModel: Identifiable, Equatable {
let id: String
let title: String

init(id: String = UUID().uuidString, title: String) {
    self.id = id
    self.title = title

 }

  func updateCompletion() -> ItemModel {
  return ItemModel(id: id, title: title)
   }
   }

--

import Foundation

struct CollapsableItem: Equatable, Identifiable, Hashable {
let id: String
var title: String

init(id: String = UUID().uuidString, title: String) {
    self.id = id
    self.title = title

    
}

    func updateCompletion() -> CollapsableItem {
return CollapsableItem(id: id, title: title)
    }
}

These are my two ViewModels:

class ListViewModel: ObservableObject  {
@Published var items: [ItemModel] = []

init() {
    getItems()
}

func getItems() {
    let newItems = [
        ItemModel(title: "List Item1"),
        ItemModel(title: "List Item2"),
        ItemModel(title: "List Item3"),
    ]
    items.append(contentsOf: newItems)
    }

func addItem(title: String) {
    let newItem = ItemModel(title: title)
    items.append(newItem)
}

func updateItem(item: ItemModel) {
    
    if let index = items.firstIndex(where: { $0.id == item.id}) {
        items[index] = item.updateCompletion()
    }
    }
    }

--

class CollapsedViewModel: ObservableObject  {
@Published var collapsableItems: [CollapsableItem] = []

@Published var id = UUID().uuidString


init() {
    getCollapsableItems()
}


func getCollapsableItems() {
    let newCollapsableItems = [
        CollapsableItem(title: "Wake up")
    ]
    collapsableItems.append(contentsOf: newCollapsableItems)
}

func addCollapsableItem(title: String) {
    let newCollapsableItem = CollapsableItem(title: title)
    collapsableItems.append(newCollapsableItem)
}




func updateCollapsableItem(collapsableItem: CollapsableItem) {
    
    if let index = collapsableItems.firstIndex(where: { $0.id == 
collapsableItem.id}) {
        collapsableItems[index] = 
 collapsableItem.updateCompletion()
    }
}

 }

The item view:

struct ListRowView: View {
@EnvironmentObject var listViewModel: ListViewModel
let item: ItemModel

var body: some View {
    HStack() {

        
        Text(item.title)
            .font(.body)
            .fontWeight(.bold)
            .foregroundColor(.white)
            .multilineTextAlignment(.center)
            .lineLimit(1)
            .frame(width: 232, height: 16)

        
    }
    .padding( )
    .frame(width: 396, height: 56)
    .background(.gray)
    .cornerRadius(12.0)

}
}

The collapsable view:

struct CollapsedView2<Content: View>: View {
@State var collapsableItem: CollapsableItem
@EnvironmentObject var collapsedViewModel: CollapsedViewModel
@State private var collapsed: Bool = true
@EnvironmentObject var listViewModel: ListViewModel
@State var label: () -> Text
@State var content: () -> Content
@State private var show = true

var body: some View {
    ZStack{
        VStack {
            HStack{
                Button(
                    action: { self.collapsed.toggle() },
                    label: {
                        HStack() {
                            Text("Hello")
                                .font(.title2.bold())
                            Spacer()
                            Image(systemName: self.collapsed ? "chevron.down" : 
                       "chevron.up")
                        }
                        .padding(.bottom, 1)
                        .background(Color.white.opacity(0.01))
                    }
                )
                .buttonStyle(PlainButtonStyle())
                
                
                Button(action: saveButtonPressed, label: {
                    Image(systemName: "plus")
                        .font(.title2)
                        .foregroundColor(.white)
                })
            }
            VStack {
                self.content()
            }
            
            ForEach(listViewModel.items) { item in ListRowView (item: item)
                
            }
            .lineLimit(1)
            .fixedSize(horizontal: true, vertical: true)
            .frame(minWidth: 396, maxWidth: 396, minHeight: 0, maxHeight: collapsed ? 
            0 : .none)
            .animation(.easeInOut(duration: 1.0), value: show)
            .clipped()
            .transition(.slide)
            
        }
    }
    
}
func saveButtonPressed() {
    listViewModel.addItem(title: "Hello")
}
        }
        
            

and finally the main view:

struct ListView: View {

@EnvironmentObject var listViewModel: ListViewModel
@EnvironmentObject var collapsedViewModel: CollapsedViewModel

var body: some View {
    ZStack{
            ScrollView{
                VStack{
                    HStack{
                        Text("MyFirstApp")
                            .font(.largeTitle.bold())
                        
                        Button(action: newCollapsablePressed, label: {
                            
                            Image(systemName: "plus")
                                .font(.title2)
                                .foregroundColor(.white)
                            
                        })
                    }
                    .padding()
                    .padding(.leading)
                    
                    ForEach(collapsedViewModel.collapsableItems) { collapsableItem in 
                    CollapsedView2 (collapsableItem: collapsableItem,                      
                    label: { Text("") .font(.title2.bold()) },
                                                                                                      
                    content: {
                        
                        HStack {
                            Text("")
                            Spacer() }
                        .frame(maxWidth: .infinity)
                        
                        
                    })
                        
                        
                        
                    }
                    .padding()
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .statusBar(hidden: false)
            .navigationBarHidden(true)
        }
}

func newCollapsablePressed() {
    collapsedViewModel.addCollapsableItem(title: "hello2")
}
}


       

Would love to understand how I could fix this!

CodePudding user response:

There is the anwser for your comment about add item in each CollapsedView2. Because ListViewModel is not ObservableObject (ListViewModel is difference from each CollapsableItem). You should use "@State var items: [ItemModel]".

struct CollapsedView2<Content: View>: View {
    @State var collapsableItem: CollapsableItem
//    @State var listViewModel = ListViewModel()
    @State var collapsed: Bool = true
    @State var label: () -> Text
    @State var content: () -> Content
    @State private var show = true
    @State var items: [ItemModel] = []
    @State var count = 1

    var body: some View {
            VStack {
                HStack{
                    Text("Hello")
                        .font(.title2.bold())
                    
                    Spacer()
                    
                    Button( action: { self.collapsed.toggle() },
                            label: {
                        Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
                    }
                    )
                    .buttonStyle(PlainButtonStyle())

                    Button(action: saveButtonPressed, label: {
                        Image(systemName: "plus")
                            .font(.title2)
//                            .foregroundColor(.white)
                    })
                }
                VStack {
                    self.content()
                }

                ForEach(items) { item in
                    ListRowView (item: item)
                }
                .lineLimit(1)
                .fixedSize(horizontal: true, vertical: true)
                .frame(minHeight: 0, maxHeight: collapsed ? 0 : .none)
                .animation(.easeInOut(duration: 1.0), value: show)
                .clipped()
                .transition(.slide)
            }
    }

    func saveButtonPressed() {
        addItem(title: "Hello \(count)")
        count  = 1
    }
    
    func addItem(title: String) {
        let newItem = ItemModel(title: title)
        items.append(newItem)
    }

    func updateItem(item: ItemModel) {

        if let index = items.firstIndex(where: { $0.id == item.id}) {
            items[index] = item.updateCompletion()
        }
    }
}

CodePudding user response:

There is the anwser. Ask me if you have some questions

struct ListView: View {
    @StateObject var collapsedViewModel = CollapsedViewModel()
    
    var body: some View {
        ScrollView{
            VStack{
                HStack{
                    Text("MyFirstApp")
                        .font(.largeTitle.bold())
                    
                    Button(action: newCollapsablePressed, label: {
                        Image(systemName: "plus")
                            .font(.title2)
//                            .foregroundColor(.white)
                    })
                }
                
                ForEach(collapsedViewModel.collapsableItems) { collapsableItem in
                    CollapsedView2 (collapsableItem: collapsableItem,
                                    label: { Text("") .font(.title2.bold()) },
                                    content: {
                        HStack {
                            Text("")
                            Spacer()
                        }
                    })
                }
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .statusBar(hidden: false)
        .navigationBarHidden(true)
    }
    
    func newCollapsablePressed() {
        collapsedViewModel.addCollapsableItem(title: "hello2")
    }
}

struct CollapsedView2<Content: View>: View {
    @State var collapsableItem: CollapsableItem
    @State var listViewModel = ListViewModel()
    @State var collapsed: Bool = true
    @State var label: () -> Text
    @State var content: () -> Content
    @State private var show = true
    
    var body: some View {
            VStack {
                HStack{
                    Button( action: { self.collapsed.toggle() },
                            label: {
                        HStack() {
                            Text("Hello")
                                .font(.title2.bold())
                            Spacer()
                            Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
                        }
                        .padding(.bottom, 1)
                        .background(Color.white.opacity(0.01))
                    }
                    )
                    .buttonStyle(PlainButtonStyle())
                    
                    Button(action: saveButtonPressed, label: {
                        Image(systemName: "plus")
                            .font(.title2)
                            .foregroundColor(.white)
                    })
                }
                VStack {
                    self.content()
                }
                
                ForEach(listViewModel.items) { item in
                    ListRowView (item: item)
                }
                .lineLimit(1)
                .fixedSize(horizontal: true, vertical: true)
                .frame(minHeight: 0, maxHeight: collapsed ? 0 : .none)
                .animation(.easeInOut(duration: 1.0), value: show)
                .clipped()
                .transition(.slide)
            }
    }
    
    func saveButtonPressed() {
        listViewModel.addItem(title: "Hello")
    }
}

struct ListRowView: View {
    let item: ItemModel
    
    var body: some View {
        HStack() {
            Text(item.title)
                .font(.body)
                .fontWeight(.bold)
                .foregroundColor(.white)
                .multilineTextAlignment(.center)
                .lineLimit(1)
                .frame(width: 232, height: 16)
        }
        .padding( )
        .frame(width: 396, height: 56)
        .background(.gray)
        .cornerRadius(12.0)
        
    }
}

class ListViewModel {
    var items: [ItemModel] = []
    
    init() {
        getItems()
    }
    
    func getItems() {
        let newItems = [
            ItemModel(title: "List Item1"),
            ItemModel(title: "List Item2"),
            ItemModel(title: "List Item3"),
        ]
        items.append(contentsOf: newItems)
    }
    
    func addItem(title: String) {
        let newItem = ItemModel(title: title)
        items.append(newItem)
    }
    
    func updateItem(item: ItemModel) {
        
        if let index = items.firstIndex(where: { $0.id == item.id}) {
            items[index] = item.updateCompletion()
        }
    }
}

class CollapsedViewModel: ObservableObject  {
    @Published var collapsableItems: [CollapsableItem] = []
    
    @Published var id = UUID().uuidString
    
    
    init() {
        getCollapsableItems()
    }
    
    
    func getCollapsableItems() {
        let newCollapsableItems = [
            CollapsableItem(title: "Wake up")
        ]
        collapsableItems.append(contentsOf: newCollapsableItems)
    }
    
    func addCollapsableItem(title: String) {
        let newCollapsableItem = CollapsableItem(title: title)
        collapsableItems.append(newCollapsableItem)
    }
    
    func updateCollapsableItem(collapsableItem: CollapsableItem) {
        
        if let index = collapsableItems.firstIndex(where: { $0.id ==
            collapsableItem.id}) {
            collapsableItems[index] =
            collapsableItem.updateCompletion()
        }
    }
    
}

struct CollapsableItem: Equatable, Identifiable, Hashable {
    let id: String
    var title: String
    
    init(id: String = UUID().uuidString, title: String) {
        self.id = id
        self.title = title
    }
    
    func updateCompletion() -> CollapsableItem {
        return CollapsableItem(id: id, title: title)
    }
}

struct ItemModel: Identifiable, Equatable {
    let id: String
    let title: String
    
    init(id: String = UUID().uuidString, title: String) {
        self.id = id
        self.title = title
        
    }
    
    func updateCompletion() -> ItemModel {
        return ItemModel(id: id, title: title)
    }
}
  • Related