SwiftUI => AttributeGraph: цикл обнаружен через атрибут после обновления одной модели в списке - PullRequest
0 голосов
/ 19 марта 2020

Контекст:

У меня есть список пользовательских представлений. Массив сохраняется как @ObservableObject как @Published.

В моем пользовательском представлении есть функция, которая определяет, когда касался вида (я сделал это, потому что он срабатывает только после анимации). Это событие активирует через мой @ObservableObject событие, которое показывает представление, которое находится в ZStack с моим списком. Там я мог обновить свой переданный объект через TextField, и когда я возвращаюсь, у меня все обновляется.

Однако, когда я пытаюсь повторно показать один из каждого элемента в моем списке, моя отладка показывает эту ошибку: AttributeGraph: cycle detected through attribute.

Вместо этого, если я покажу детали без обновления данных моей модели, у меня не будет утечек.

Любое предложение?

РЕДАКТИРОВАТЬ :

вот код:

struct ProcedureList: View {

    @ObservedObject var procedureManager = ProcedureManager()
    @State private var showModal = false

    var isEmpty:Bool {
        return procedureManager.procedures.isEmpty
    }

    init() {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithTransparentBackground()
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
        UINavigationBar.appearance().standardAppearance = appearance        
    }

    var body: some View {
        NavigationView {
            GeometryReader { geometry in
                ZStack {
                    VStack{
                        if !self.isEmpty {
                            List {
                                ForEach(self.procedureManager.procedures.indices, id: \.self) { index in
                                    ProcedureCell(procedure: self.$procedureManager.procedures[index]){ procedure, position, size in

                                        self.procedureManager.selectedProcedure = procedure
                                        self.procedureManager.cardSize = size
                                        self.procedureManager.cardPosition = position
                                        self.procedureManager.size = size
                                        self.procedureManager.position = position
                                        self.procedureManager.isPressed = true

                                        withAnimation(Animation.default.delay(0.1)) {
                                            self.procedureManager.size.width = geometry.frame(in: .local).width
                                            self.procedureManager.size.height = geometry.frame(in: .local).size.height
                                            self.procedureManager.position.x = geometry.frame(in: .global).origin.x
                                            self.procedureManager.position.y = geometry.frame(in: .global).origin.y
                                        }


                                        print(
                                            """
                                            pressed procedure: \(procedure.title)
                                            at position: \(position)
                                            and with size: \(size)
                                            """
                                        )
                                    }
//                                    .tag(self.procedureManager.procedures[index])
                                    .tag(index)
                                }
                                .onDelete(perform: self.onDelete)
                            }
                            .environment(\.defaultMinListRowHeight, 120)
                            .animation(.easeInOut)
                        }else {
                            VStack{
                                Text("Non hai ancora creato una procedura!")
                                    .font(.largeTitle)
                                    .multilineTextAlignment(.center)
                                    .padding(.bottom, 30)
                                Button(action: {
                                    self.showModal.toggle()
                                }){
                                    Text("Creane una nuova!")
                                }
                                .sheet(isPresented: self.$showModal) {
                                    NewProcedure(showModal: self.$showModal) { procedure in
                                        self.procedureManager.newProcedure = procedure
                                        self.procedureManager.createProcedure()
                                    }
                                }
                            }.padding(20)
                        }
                    }
                    Rectangle()
                        .edgesIgnoringSafeArea(.all)
                        .zIndex(self.procedureManager.isPressed ? 0 : -1)
                        .opacity(self.procedureManager.isPressed ? 0.7 : 0)
                        .animation(Animation.easeInOut(duration: 0.5))
                    ProcedureDetail(action: { procedure in
                        self.procedureManager.update(procedure: procedure)
                    }, procedure: self.$procedureManager.selectedProcedure, isShowingDetail: self.$procedureManager.isPressed)
                        .frame(width: self.procedureManager.correctSize.width, height: self.procedureManager.correctSize.height)
                        .position(x: self.procedureManager.correctPosition.x, y: self.procedureManager.correctPosition.y - (geometry.frame(in: .global).origin.y))
                        .offset(x: self.procedureManager.correctSize.width / 2, y: self.procedureManager.correctSize.height / 2)
                        .animation(.easeInOut)
                        .opacity(self.procedureManager.correctOpacity)
                        .animation(Animation.easeInOut.delay(self.procedureManager.isPressed ? 0 : 0.2))

                }
                .onAppear {
                    UITableView.appearance().separatorStyle = .none
                }
                .onDisappear() {
                    UITableView.appearance().separatorStyle = .singleLine
                }
                .navigationBarTitle("", displayMode: .inline)
                .navigationBarItems(trailing:
                    !self.isEmpty && !self.procedureManager.isPressed ?
                        Button(action: {
                            self.showModal.toggle()
                        }){
                            Image(systemName: "plus.circle.fill")
                                .font(Font.system(size: 40))
                                .foregroundColor(Color.red)
                        }
                        .sheet(isPresented: self.$showModal) {
                            NewProcedure(showModal: self.$showModal) { procedure in
                                self.procedureManager.newProcedure = procedure
                                self.procedureManager.createProcedure()
                            }
                        } : nil
                )
            }
        }
    }

    private func onDelete(offsets: IndexSet) {
        self.procedureManager.procedures.remove(atOffsets: offsets)
    }
}
struct ProcedureCell: View {
    @Binding var procedure: Procedure
    @State var position:CGPoint = .zero
    @State var size:CGSize = .zero

    var action:(_ procedure:Procedure, _ position: CGPoint, _ size:CGSize)->Void

    var body: some View {
        return
            GeometryReader { geometry in

                Button(action: {
                    let position = geometry.frame(in: .global).origin
                    let size = geometry.frame(in: .global).size
                    self.action(self.procedure, position, size)
                }){
                    HStack {
                        VStack(alignment: .leading) {
                            Text(self.procedure.title)
                                .font(.largeTitle)
                            Text(self.procedure.subtitle)
                                .font(.title)
                        }
                        .padding(10)
                        Spacer()
                    }
                }
                .buttonStyle(MyButtonStyle())
                .padding([.top, .bottom])
                .edgesIgnoringSafeArea(.all)
        }
    }
}

struct MyButtonStyle:ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .background(
                Rectangle()
                    .fill(configuration.isPressed ? Color.red : Color.orange)
                    .cornerRadius(20)
                    .shadow(radius: configuration.isPressed ? 5 : 0)
        )
            .scaleEffect(configuration.isPressed ? 1.1 : 1)
            .animation(.easeInOut)
    }
}
struct Procedure: Identifiable {
    var title: String
    var subtitle: String

    var id: String

    static var empty:Procedure {
        return Procedure(title: "", subtitle: "")
    }

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

}
class ProcedureManager: ObservableObject {
    @Published var procedures: [Procedure]
    @Published var newProcedure = Procedure.empty
    @Published var selectedProcedure = Procedure.empty
    @Published var cardSize:CGSize = .zero
    @Published var cardPosition:CGPoint = .zero
    @Published var size:CGSize = .zero
    @Published var position:CGPoint = .zero
    @Published var isPressed:Bool = false

    var correctSize:CGSize {
        if isPressed {
            return size
        }
        else{
            return cardSize
        }
    }

    var correctPosition:CGPoint {
        if isPressed {
            return position
        }
        else{
            return cardPosition
        }
    }

    var correctOpacity: Double {
        return isPressed ? 1 : 0
    }

    func update(procedure:Procedure) {
        if let index = procedures.compactMap({$0.id}).firstIndex(of: procedure.id) {

            procedures[index].title = procedure.title
            procedures[index].subtitle = procedure.subtitle
            objectWillChange.send()
        }
    }

    func createProcedure(){
        procedures.append(newProcedure)
        newProcedure = .empty
    }

    func createProcedure(with title:String, andSubtitle subtitle:String) {
        let procedure = Procedure(title: title, subtitle: subtitle)
        procedures.append(procedure)
    }

    init(){
        procedures = [
            Procedure(title: "test1", subtitle: "subtitletest1"),
            Procedure(title: "test2", subtitle: "subtitletest2"),
            Procedure(title: "test3", subtitle: "subtitletest3"),
            Procedure(title: "test4", subtitle: "subtitletest4"),
            Procedure(title: "test5", subtitle: "subtitletest5"),
        ]

    }
}

...