8

I have something like this in my ZStack :

if hidePopup {
      CustomButton()
      .hidden()
 } else if stateManager.isBtnClosePressed {
      CustomButton()
      .hidden()
 } else {
      CustomButton()
 }

And I need in the last else to show CustomButton() with some delay. I’ve tried to wrap it in DispatchQueue.main.async but it doesn’t suit there. I mean:

DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
      CustomButton()
 } 

TIA for your opinions and help

Lina_F
  • 131
  • 2
  • 10

2 Answers2

19

You need to change a @State var after the delay. For example:

struct ContentView: View {
    @State var isButtonHidden = true
    let button = Button("Custom", action: {})

    var body: some View {
        Group {
            if isButtonHidden {
                button.hidden()
            } else {
                button
            }
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                self.isButtonHidden = false
            }
        }
    }
}
Yonat
  • 4,382
  • 2
  • 28
  • 37
3

Adding to @Yonat's answer– You can use this as a view modifier

/// A ViewModifier that defers its rendering until after the provided threshold surpasses
private struct DeferredViewModifier: ViewModifier {

    // MARK: API

    let threshold: Double

    // MARK: - ViewModifier

    func body(content: Content) -> some View {
        _content(content)
            .onAppear {
               DispatchQueue.main.asyncAfter(deadline: .now() + threshold) {
                   self.shouldRender = true
               }
            }
    }

    // MARK: - Private

    @ViewBuilder
    private func _content(_ content: Content) -> some View {
        if shouldRender {
            content
        } else {
            content
                .hidden()
        }
    }

    @State
    private var shouldRender = false
}

extension View {
    func deferredRendering(for seconds: Double) -> some View {
        modifier(DeferredViewModifier(threshold: seconds))
    }
}

Usage:

struct MyView: View {
    var body: some View {
        Text("test")
           .deferredRendering(for: 0.5)
    } 
}
Charlton Provatas
  • 2,184
  • 25
  • 18
  • This worked great for me! I did tweak it slightly – I replaced `Double` with `DispatchTimeInterval`, so I can be more explicit about what the delay is, e.g. `.deferredRendering(for: .seconds(5))` instead of `.deferredRendering(for: 5.0)` https://github.com/alexwlchan/photo-reviewer/blob/7701214326378bc67b3d17064f1d73df81ae7a9d/BlinkReviewer/BlinkReviewer/Views/Helpers/DeferredRendering.swift – alexwlchan Jun 09 '23 at 14:26