iosswiftuiswipe

How to disable toolbar buttons while a list cell is swiped?


Setup:

My app has a toolbar with some buttons, and a list. The list cells can be swiped. While a cell is swiped, I want to disable the toolbar buttons because a swipe has to be finished, either by tapping the swipe button or by tapping the cell.

Problem:

The enable state of the buttons depends on whether a cell is swiped or not, but I could not find a way to check this.

I tried .onAppear and .onDisappear of the swipe button, but .onAppear is only called when the button is added to the view hierarchy. When the cell is tapped to unswipe the cell, the button is shifted out of sight, but not removed from the view hierarchy, and .onDisappear is not called. Also, if the cell is swiped again, the button is only shifted in to become visible, but not added to the view hierarchy, i.e. .onAppear is not called again.

I had also the idea to use a GeometryReader in the list cell, because during a swipe or unswipe, the frames of the Views in the cell change. However, a GeometryReader updates these values only when the View is redrawn, and a cell is not redrawn during swipe/unswipe.

Question:

How could I disable the toolbar buttons while a cell is swiped?


Solution

  • Problem solved using GeometryReader:

    In the ViewModel, named dataSource, I declared 2 vars. If any of them is true, all toolbar buttons are disabled:

    @Published var anyCellIsLeadingSwiped  = false
    @Published var anyCellIsTrailingSwiped = false
    

    Then I applied the following overlay to the CellView:

    .overlay {
        GeometryReader { geo in
            Color(.clear)
                .onChange(of: geo.frame(in: .global).minX) { newMinX in
                    let leadingSwiped  = newMinX > 0.0
                    let trailingSwiped = newMinX < 0.0
                    if !dataSource.anyCellIsLeadingSwiped && leadingSwiped {
                        dataSource.anyCellIsLeadingSwiped = true
                    }
                    if dataSource.anyCellIsLeadingSwiped && !leadingSwiped {
                        dataSource.anyCellIsLeadingSwiped = false
                    }
                    if !dataSource.anyCellIsTrailingSwiped && trailingSwiped {
                        dataSource.anyCellIsTrailingSwiped = true
                    }
                    if dataSource.anyCellIsTrailingSwiped && !trailingSwiped {
                        dataSource.anyCellIsTrailingSwiped = false
                    }
                }
        }
    }  
    

    The overlay is required to leave the cell layout unchanged.
    When a cell is swiped, the cell is shifted left or right to make the swipe button visible. This changes the global minX.
    Depending on the current swipe state, the vars in the ViewModel are set. Since they are published, this redraws the toolbar. Its buttons use these vars for their enabled propery.