SwiftUI EditMode 在 ListView 中无法正常工作

问题描述 投票:0回答:1

TLDR:即使 moveDisabled 修饰符值为 false(动态更新),移动指示器也不会出现在列表编辑模式中。

大家好,我来咨询一下listView中的editMode问题,让我哭了2天多。我知道 ListView 中的 editMode 仍然不稳定,但我在这里需要一些帮助。

问题是:当我们使用 onMove 修饰符移动 listView 内的项目时,它可以正常工作,直到我们对 ForEach 内的项目使用 moveDisabled 修饰符。 当我们尝试在启用 editMode 的情况下向数组添加新条目时,真正的问题就开始了。即使 moveDisabled 内的值为 false,新项目的移动指示器也不会出现,除非您必须使用标识符刷新整个列表视图。

我将在此处放置一些代码和包含问题的附件。请帮我解决这个问题。抱歉,有错别字,提前谢谢!!

This is what the problem is!!


//
//  AttributesReorderView.swift
//  OneApp
//
//  Created by Ligen Raj on 06/09/24.
//  Created for StackOverflow Questioning.
//

import SwiftUI

fileprivate struct ItemView: View {
    
    // MARK: - PROPERTIES
    
    @Binding var item: IdentifiableAttribute
    @FocusState.Binding var focused: Int?
    var index: Int?
    var disabled: Bool
    
    init(item: Binding<IdentifiableAttribute>, focused: FocusState<Int?>.Binding, index: Int?, disabled: Bool) {
        self._item = item
        self._focused = focused
        self.index = index
        self.disabled = disabled
    } //: INIT
    
    // MARK: - BODY
    
    var body: some View {
        
        HStack(alignment: .center, spacing: 16) {
            TextField("Add new item", text: $item.attribute.title)
                .focused($focused, equals: index)
                .textInputAutocapitalization(.words)
                .font(.medium)
            
            ColorPickerView
                .transition(.opacity)
                .active(if: !disabled)
            
            DividerView(color: .themeSecondary, width: 1)
                .padding(.vertical, 4)
                .hidden(disabled)
        } //: HSTACK
        .padding(8)
        .padding(.leading, 2)
        .background(alignment: .leading, content: BackgroundView)
        .overlay(alignment: .leading, content: {
            DividerView(color: item.color, width: 2)
        })
        .transition(.opacity)
        
    }
    
    @ViewBuilder private var ColorPickerView: some View {
        ZStack(alignment: .center) {
            Circle()
                .fill(item.color)
                .aspectRatio(contentMode: .fit)
                .allowsHitTesting(false)
                .background {
                    Circle()
                        .stroke(lineWidth: 1)
                        .foregroundStyle(.themeSecondary)
                }
            
            ColorPicker(selection: $item.color, label: emptyView)
                .labelsHidden()
                .blendMode(.destinationOut)
                .compositingGroup()
        } //: ZSTACK
        .scaleEffect(0.8)
        .frame(width: 20, height: 20)
    } //: VIEW_VAR
    
    @ViewBuilder private func DividerView(color: Color, width: CGFloat) -> some View {
        Capsule()
            .fill(color)
            .frame(width: width)
    } //: VIEW_FUNC
    
    @ViewBuilder private func BackgroundView() -> some View {
        RoundedCorner(radius: 6, corners: [.topRight, .bottomRight])
            .fill(.themeDarkBlue)
            .frame(width: disabled ? nil : Helper.screenWidth - 16)
    } //: VIEW_FUNC
    
}

struct AttributesReorderView: View {
    
    // MARK: - PROPERTIES
    
    @StateObject private var viewModel: AttributeReorderViewModel = .init()
    @State private var editMode: EditMode = .inactive
    @FocusState private var focused: Int?
    
    var items: [Attribute]
    var title: String
    
    init(items: [Attribute], title: String, type: String) {
        self.items = items
        self.title = title
        
        viewModel.type = type
    } //: INIT
    
    // MARK: - BODY
    
    var body: some View {
        
        VStack(alignment: .center, spacing: 8) {
            HeaderView
            
            List {
                ForEach($viewModel.items, id: \.id) { $item in
                    let isDisabled: Bool = viewModel.isTitleEmpty(item)
                    let index: Int? = viewModel.getIndex(item)
                    
                    ItemView(item: $item, focused: $focused, index: index, disabled: isDisabled)
// Not updating properly => .moveDisabled(isDisabled)
                        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
                            Button(role: .destructive, action: {
                                viewModel.onDelete(item)
                            }) {
                                Image(systemName: "trash")
                            } //: BUTTON
                            .active(if: !isDisabled)
                        }
                } //: LOOP
                .onMove(perform: viewModel.onMove)
            } //: LIST
            .listStyle(.plain)
            .environment(\.editMode, $editMode)
            .emptyListBackground()
            .hideScrollContentIfAvailable()
            .animation(.easeIn, value: viewModel.items)
            .animation(.easeOut, value: editMode.isEditing)
            
            MinimalButtonView(title: "Done", action: {})
                .padding([.horizontal, .bottom])
        } //: VSTACK
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .foregroundStyle(.white)
        .customSheetBG(color: .themeBlack)
        .environment(\.colorScheme, .dark)
        .onAppear(perform: onAppear)
        .onReceive(viewModel.itemRemovedPublisher, perform: onReceiveItemRemovedAtIndex)
        .onSubmit(onSubmit)
        
    }
    
    @ViewBuilder private var HeaderView: some View {
        CommonNavBar(title: title, centerAligned: true, foregroundColor: .white)
            .overlay(alignment: .trailing, content: {
                HStack(alignment: .center, spacing: 0) {
                    PaddingButton(systemName: "plus", padding: 16, renderingMode: .template(.white), action: {
 // The problem is here => viewModel.appendAttribute()
                    })
                    .symbolRenderingMode(editMode.isEditing ? .palette : .hierarchical)
                    
                    PaddingButton(systemName: "square.and.pencil", padding: 16, renderingMode: .template(.white), action: {
                        editMode = editMode.isEditing ? .inactive : .active
                    })
                    .symbolRenderingMode(editMode.isEditing ? .palette : .hierarchical)
                }
            })
    } //: VIEW_VAR
    
    private func onAppear() {
        viewModel.items = viewModel.cast(items)
        
        guard !items.contains(where: \.title.isEmpty) else { return }
        
        viewModel.appendAttribute()
    } //: FUNC
    
    private func onSubmit() {
        guard focused != viewModel.items.indices.last else { return }
        
        focused = (focused ?? 0) + 1
    } //: FUNC
    
    func onReceiveItemRemovedAtIndex(_ index: Int?) {
        guard index != .zero else { return }
        
        focused = (index ?? 0) - 1
    } //: FUNC
    
}

// MARK: - PREVIEW

struct AttributeReorderPreviewView: View {
    
    @State private var items: [Attribute] = [.status1, .status2, .status3]
    
    var body: some View {
        
        AttributesReorderView(items: items, title: "Project Status", type: "status")
        
    }
}

#Preview {
    AttributeReorderPreviewView()
}


如有任何关于代码的疑问,请随时询问.. 任何有关此问题的帮助将不胜感激。

generics swiftui swiftui-list swiftui-navigationview swiftui-tabview
1个回答
0
投票

我在过去的项目中也遇到过这个问题。

在 SwiftUI 中使用 .moveDisabled() 时,您遇到的移动指示未显示新添加对象的问题很可能与 SwiftUI 管理视图状态修改和 ForEach 循环的方式有关。当数组动态更新时(例如,通过添加项目),SwiftUI 并不总是正确刷新新条目的 UI,尤其是当列表可编辑时。

我有一个简单的解决方案,我刚刚尝试过:每当添加新项目时,使用唯一标识符(例如.id)刷新列表视图。这将需要 SwiftUI 重新渲染完整列表,确保移动指示器正确显示。

将.id(viewModel.items)添加到ForEach循环中,使SwiftUI识别列表数据中的更改:

List {
    ForEach($viewModel.items, id: \.id) { $item in
        let isDisabled: Bool = viewModel.isTitleEmpty(item)

        let index: Int? = viewModel.getIndex(item)



        ItemView(item: $item, focused: $focused, index: index, disabled: isDisabled)

            .moveDisabled(isDisabled) // Correct usage of moveDisabled

            .swipeActions(edge: .trailing, allowsFullSwipe: true) {

                Button(role: .destructive, action: {

                    viewModel.onDelete(item)

                }) {

                    Image(systemName: "trash")

                }

                .active(if: !isDisabled)

            }

    }

    .id(viewModel.items)  // Add this line to refresh the list when items change

    .onMove(perform: viewModel.onMove)

}

通过添加 .id(viewModel.items),它可以确保 SwiftUI 在 viewModel.items 发生更改时重新渲染列表,要求它使用正确的编辑模式行为,包括移动指示器。

希望这有助于解决添加新项目后在编辑模式下无法正确显示移动指示器的问题。

© www.soinside.com 2019 - 2024. All rights reserved.