Showing Alert or Sheet after Some Delay?

In the following lines of code, I tap a button and an alert message will appear. I wonder if I can change the code such that the alert message will appear after some time, say, 5 seconds? During this period of delay, I want to determine whether or not the app should show an alert message.

import SwiftUI
import Combine

struct ContentView: View {
	@State var cancellables = Set<AnyCancellable>()
	@State var disabled: Bool = false
	@State private var showingAlert = false
	let timeInterval: Double = 5.0
	
	var body: some View {
		VStack {
			Spacer()
			Button("Tap me") {
				showingAlert = true
				disabled = true
				// some Combine work //
			}
			.font(.system(size: 24.0))
			.disabled(disabled)
			.alert("GGGG", isPresented: $showingAlert) {
				/* showing alert after timeInterval */
			}
		}
	}
}

I could do it in UIKit, but I'm not sure if that's possible in SwiftUI.

Muchos thankos.

Answered by Tomato in 700698022

I've figured out a way of doing it with StateObject. It's something like the following.

import SwiftUI
import Combine

struct ContentView2: View {
	@State var disabled: Bool = false
	@StateObject var delayMonitor = DelayMonitor()
	@State private var showingAlert = false
	
	var body: some View {
		VStack {
			Spacer()
			Button("Tap to connect me") {
				disabled = true
				delayMonitor.start()
			}
			.font(.system(size: 24.0))
			.disabled(disabled)
			
			Spacer()
				.frame(height: 30.0)
		}.onChange(of: delayMonitor.failed) { newValue in
			print("You've failed?: \(newValue)")
			disabled = !newValue
			showingAlert = newValue
		}
		.alert("Something is wrong...", isPresented: $showingAlert) {
			Button("OK", role: .cancel) { }
		}
	}
}

class DelayMonitor: ObservableObject {
	var timer = Timer()
	var seconds: Double = 0.0
	@Published var failed: Bool = false
	
	func start() {
		timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
			self.seconds += 1.0
			if self.seconds == 5.0 { // arbitrary timeout
				self.timer.invalidate()
				DispatchQueue.main.async() {
					[weak self] in
					guard let strongSelf = self else { return }
					strongSelf.failed = true
				}
			}
		})
	}
}

The onChange guy will let me know only if the value (delayMonitor.failed) has changed. Since its initial value is set to false, I'll get a call only if it changes to true.

Accepted Answer

I've figured out a way of doing it with StateObject. It's something like the following.

import SwiftUI
import Combine

struct ContentView2: View {
	@State var disabled: Bool = false
	@StateObject var delayMonitor = DelayMonitor()
	@State private var showingAlert = false
	
	var body: some View {
		VStack {
			Spacer()
			Button("Tap to connect me") {
				disabled = true
				delayMonitor.start()
			}
			.font(.system(size: 24.0))
			.disabled(disabled)
			
			Spacer()
				.frame(height: 30.0)
		}.onChange(of: delayMonitor.failed) { newValue in
			print("You've failed?: \(newValue)")
			disabled = !newValue
			showingAlert = newValue
		}
		.alert("Something is wrong...", isPresented: $showingAlert) {
			Button("OK", role: .cancel) { }
		}
	}
}

class DelayMonitor: ObservableObject {
	var timer = Timer()
	var seconds: Double = 0.0
	@Published var failed: Bool = false
	
	func start() {
		timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
			self.seconds += 1.0
			if self.seconds == 5.0 { // arbitrary timeout
				self.timer.invalidate()
				DispatchQueue.main.async() {
					[weak self] in
					guard let strongSelf = self else { return }
					strongSelf.failed = true
				}
			}
		})
	}
}

The onChange guy will let me know only if the value (delayMonitor.failed) has changed. Since its initial value is set to false, I'll get a call only if it changes to true.

Showing Alert or Sheet after Some Delay?
 
 
Q