How do I update the data in the tabview after the one-to-many coredata has been modified?

129 views Asked by At

Purpose

I want to update the data in the tabview automatically when I return to the RootView after I rename the tag name in the tagManagemenView.

Current Status

When I do delete operation in the TagManagementView, the RootView is able to update automatically.

However, If the Tag name is modified, the RootView display will not be updated, but clicking into the ItemDetailsView is able to display the latest modified name. Only the display of the RootView is not updated.

Background and code

Item and Tag are created using coredata and have a one-to-many relationship, where one Item corresponds to multiple Tags

// RootView
struct RootView: View {
    @State private var selection = 0
    
    var body: some View {
        NavigationView {
            TabView(selection: $selection) {
                ItemListView()
                    .tabItem {
                        Label ("Items",systemImage: "shippingbox")
                    }
                    .tag(0)
                
                Settings()
                    .tabItem{
                        Label("Settings", systemImage: "gearshape")
                    }
                    .tag(1)
            }
            .navigationTitle(selection == 0 ? "Items" : "Settings")
        }
        .navigationViewStyle(.stack)
    }
}
// ItemListView
struct ItemListView: View {
    @FetchRequest var items: FetchedResults<Item>
    @State private var itemDetailViewIsShow: Bool = false
    @State private var selectedItem: Item? = nil
    
    init() {
        var predicate = NSPredicate(format: "TRUEPREDICATE"))
        
        _items = FetchRequest(fetchRequest: Item.fetchRequest(predicate))
    }
    
    var body: some View {       
        ForEach(items) { item in
            Button(action: {
                self.selectedItem = item
                self.itemDetailViewIsShow = true
            }, label: {
                ItemCellView(item: item)
            })
        }

        if selectedItem != nil {
            NavigationLink (
                destination: ItemDetailView(item: selectedItem!, detailViewIsShow: $itemDetailViewIsShow),
                isActive: $itemDetailViewIsShow
            ) {
                EmptyView()
            }
            .isDetailLink(false)
        }
    }
}
// TagManagementView
struct TagManagementView: View {
    @Environment(\.managedObjectContext) var context
    @FetchRequest(entity: Tag.entity(), sortDescriptors: []) var allTags: FetchedResults<Tag>
    
    @State var isShowDeleteAlert = false
    @State var showModel = false
    @State var selected: Tag?
    
    var body: some View {
        ZStack {
            List {
                ForEach(allTags) { tag in
                    TagCellView(tag: tag)
                        .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                            Button(role: .destructive, action: {
                                isShowDeleteAlert = true
                                selected = tag
                            }, label: {
                                Label("Delete", systemImage: "trash")
                                    .foregroundColor(.white)
                            })
                            
                            Button(action: {
                                showModel = true
                                selected = tag
                            }, label: {
                                Label("Edit", systemImage: "square.and.pencil")
                                    .foregroundColor(.white)
                            })
                        }
                }
                .confirmationDialog("Delete confirm", isPresented: self.$isShowDeleteAlert, titleVisibility: .visible) {
                    Button("Delete", role: .destructive) {
                        if self.selected != nil {
                            self.selected!.delete(context: context)
                        }
                    }
                    
                    Button(role: .cancel, action: {
                        isShowDeleteAlert = false
                    }, label: {
                        Text("Cancel")
                            .font(.system(size: 17, weight: .medium))
                    })
                }
            }
            
            if self.showModel {
                    // background...
                Color("mask").edgesIgnoringSafeArea(.all)
                TagEditorModal(selected: self.$selected, isShowing: self.$showModel)
            }
        }
    }
}
// TagEditorModal
struct TagEditorModal: View {
    @Environment(\.managedObjectContext) var context
    
    @State var tagName: String = ""
    @Binding var isShowing: Bool
    @Binding var selector: Tag?
    
    init (selected: Binding<Tag?>, isShowing: Binding<Bool>) {
        _isShowing = isShowing
        _selector = selected
        _tagName = .init(wrappedValue: selected.wrappedValue!.name)
    }
    
    var body: some View {
        VStack{
            TextField("Tag name", text: self.$tagName)

            HStack {
                Button(action: {
                    self.isShowing = false
                }) {
                    Text("Cancel")
                }
                
                Button(action: {
                    self.selector!.update(name: self.tagName, context: context)
                    self.isShowing = false
                }, label: {
                    Text("Submit")
                })
            }
        }
    }
}

// update tagName func
extension Tag {
    func update(name: String, context: NSManagedObjectContext) {
            self.name = name
            self.updatedAt = Date()
            
            self.objectWillChange.send()
            
            try? context.save()
        }
}
// ItemCellView
struct ItemCellView: View {
    @Environment(\.managedObjectContext) var context
    
    @ObservedObject var item: Item
    
    var body: some View {
        VStack {
            Text(item.name)
            TagListView(tags: .constant(item.tags))
        }
    }
}

    // tagListView
struct TagListView: View {
    @Binding var tags: [Tag]
    
    @State private var totalHeight = CGFloat.zero
    
    var body: some View {
        VStack {
            GeometryReader { geo in
                VStack(alignment: .leading,spacing: 10) {
                    ForEach (getRows(screenWidth: geo.size.width), id: \.self) {rows in
                        HStack(spacing: 4) {
                            ForEach (rows) { tag in
                                Text(tag.name)
                                    .font(.system(size: 10))
                                    .fontWeight(.medium)
                                    .lineLimit(1)
                                    .cornerRadius(40)
                            }
                        }
                    }
                }
                .frame(width: geo.size.width, alignment: .leading)
                .background(viewHeightReader($totalHeight))
            }
        }
        .frame(height: totalHeight)
    }
    
    private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
        return GeometryReader { geo -> Color in
            let rect = geo.frame(in: .local)
            DispatchQueue.main.async {
                binding.wrappedValue = rect.size.height
            }
            
            return .clear
        }
    }
    
    func getRows(screenWidth: CGFloat) -> [[Tag]] {
        var rows: [[Tag]] = []
        var currentRow: [Tag] = []
        
        var totalWidth: CGFloat = 0
    
        self.tags.forEach{ tag in
            totalWidth += (tag.size + 24)
            if totalWidth > (screenWidth) {
                totalWidth = (!currentRow.isEmpty || rows.isEmpty ? (tag.size + 24) : 0)
                rows.append(currentRow)
                currentRow.removeAll()
                currentRow.append(tag)
            } else {
                currentRow.append(tag)
            }
        }
        if !currentRow.isEmpty {
            rows.append(currentRow)
            currentRow.removeAll()
        }
        
        return rows
    }
}

1

There are 1 answers

0
IDTIMW On

I added a TagCellView and then used an @ObservedObject for the tag

struct TagChipsView: View {
    @ObservedObject var tag: Tag
    let verticalPadding: CGFloat = 2.0
    let horizontalPadding: CGFloat = 8.0
    
    var body: some View {
        Text(tag.name)
    }
}