skip to Main Content

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!

2

Answers


  1. 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)
        }
    }
    
    Login or Signup to reply.
  2. 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()
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search