iosswiftswiftuiactionsheetswiftui-actionsheet

SwiftUI ActionSheet does not dismiss when timer is running


I have the following simple SwiftUI setup. A timer that is running and updating a Text. If the timer is not running (stopped or paused) I can easily show an ActionSheet (by tapping on Actions) and dismiss it by choosing either "Cancel" or "Action 1" option. But if the timer is running, I have a really hard time dismissing the ActionSheet by choosing one of the "Cancel" or "Action 1" options. Do you know what's going on?

I am using Xcode 11.5.

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var stopWatch = StopWatch()
    @State private var showActionSheet: Bool = false
    
    var body: some View {
        VStack {
            Text("\(stopWatch.secondsElapsed)")
            HStack {
                if stopWatch.mode == .stopped {
                    Button(action: { self.stopWatch.start() }) {
                        Text("Start")
                    }
                } else if stopWatch.mode == .paused {
                    Button(action: { self.stopWatch.start() }) {
                        Text("Resume")
                    }
                } else if stopWatch.mode == .running {
                    Button(action: { self.stopWatch.pause() }) {
                        Text("Pause")
                    }
                }
                Button(action: { self.stopWatch.stop() }) {
                    Text("Reset")
                }
            }
            Button(action: { self.showActionSheet = true }) {
                Text("Actions")
            }
            .actionSheet(isPresented: $showActionSheet) {
                ActionSheet(title: Text("Actions"), message: nil, buttons: [.default(Text("Action 1")), .cancel()])
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

class StopWatch: ObservableObject {
    
    @Published var secondsElapsed: TimeInterval = 0.0
    @Published var mode: stopWatchMode = .stopped
    
    var timer = Timer()
    func start() {
        mode = .running
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            self.secondsElapsed += 0.1
        }
    }
    
    func stop() {
        timer.invalidate()
        secondsElapsed = 0
        mode = .stopped
    }
    
    func pause() {
        timer.invalidate()
        mode = .paused
    }
    
    enum stopWatchMode {
        case running
        case stopped
        case paused
    }
}

Solution

  • Works fine with Xcode 12 / iOS 14, but try to separate button with sheet into another subview to avoid recreate it on timer counter refresh.

    Tested with Xcode 12 / iOS 14

    struct ContentView: View {
    
        @ObservedObject var stopWatch = StopWatch()
        // @StateObject var stopWatch = StopWatch()       // << used for SwiftUI 2.0
        @State private var showActionSheet: Bool = false
    
        var body: some View {
            VStack {
                Text("\(stopWatch.secondsElapsed)")
                HStack {
                    if stopWatch.mode == .stopped {
                        Button(action: { self.stopWatch.start() }) {
                            Text("Start")
                        }
                    } else if stopWatch.mode == .paused {
                        Button(action: { self.stopWatch.start() }) {
                            Text("Resume")
                        }
                    } else if stopWatch.mode == .running {
                        Button(action: { self.stopWatch.pause() }) {
                            Text("Pause")
                        }
                    }
                    Button(action: { self.stopWatch.stop() }) {
                        Text("Reset")
                    }
                }
                ActionsSubView(showActionSheet: $showActionSheet)
            }
        }
    }
    
    struct ActionsSubView: View {
        @Binding var showActionSheet: Bool
        var body: some View {
            Button(action: { self.showActionSheet = true }) {
                Text("Actions")
            }
            .actionSheet(isPresented: $showActionSheet) {
                ActionSheet(title: Text("Actions"), message: nil, buttons: [.default(Text("Action 1")), .cancel()])
            }
        }
    }