Reputation: 1043
I have been trying to accomplish two main goals that I'm having a headache with. Sorry if it's a simple fix, I am a still bit new to Swift/SwiftUI.
@State
to change value based on how much time has passed.I've searched through Stack Overflow and found answers suggesting to use a timer:
struct CurrentDateView : View {
@State var now = Date()
let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
var body: some View {
Text("\(now)")
.onReceive(timer) {
self.now = Date()
}
}
}
But how would I incorporate this so that something like @State
can be used change my value to false
after 7.5 seconds has passed:
@State randomTF : Bool = true
Or a Text("Please Enter Above")
to change to Text("Sorry Too Late")
after 7.5 seconds has passed?
Upvotes: 69
Views: 91153
Reputation: 14328
From on John's answer, that task
is performed before the view appears. From apple's doc:
Adds an asynchronous task to perform before this view appears.
So this is not really "onAppear" and, I think, there's a chance it can be performed before view appears if delay is very short. Which may not be expected.
I suggest keep using .onAppear
and just have a Task
inside. No need to declare any extension functions.
Like this:
.onAppear {
Task { @MainActor in
try await Task.sleep(for: .seconds(0.1))
// your code here
}
}
Upvotes: 16
Reputation: 1467
It can be as simple as:
Text("\(now)")
.onAppear(delay: 1) {
self.now = Date()
}
Using these extensions, which are reusable across projects and address some related issues as well:
public extension TimeInterval {
var nanoseconds: UInt64 {
return UInt64((self * 1_000_000_000).rounded())
}
}
@available(iOS 13.0, macOS 10.15, *)
public extension Task where Success == Never, Failure == Never {
static func sleep(_ duration: TimeInterval) async throws {
try await Task.sleep(nanoseconds: duration.nanoseconds)
}
}
@available(iOS 15.0, macOS 12.0, *)
public extension View {
func onAppear(delay: TimeInterval, action: @escaping () -> Void) -> some View {
task {
do {
try await Task.sleep(delay)
} catch { // Task canceled
return
}
await MainActor.run {
action()
}
}
}
}
Upvotes: 2
Reputation: 1530
// Add a delay of 1 sec
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// your function
}
Upvotes: 22
Reputation: 30431
Create a delay, which then sets the @State
property hasTimeElapsed
to true
when the time has elapsed, updating the view body.
With Swift 5.5 and the new concurrency updates (async
& await
), you can now use task(_:)
like the following:
struct ContentView: View {
@State private var hasTimeElapsed = false
var body: some View {
Text(hasTimeElapsed ? "Sorry, too late." : "Please enter above.")
.task(delayText)
}
private func delayText() async {
// Delay of 7.5 seconds (1 second = 1_000_000_000 nanoseconds)
try? await Task.sleep(nanoseconds: 7_500_000_000)
hasTimeElapsed = true
}
}
See more info about Task.sleep(nanoseconds:)
in this answer.
Xcode 13.0+ now supports concurrency, backwards compatible! However, here is still the 'old' way to do it:
You can use DispatchQueue
to delay something. Trigger it with onAppear(perform:)
(which happens when the view first appears). You could also hook the delay up to a Button instead if wanted.
Example:
struct ContentView: View {
@State private var hasTimeElapsed = false
var body: some View {
Text(hasTimeElapsed ? "Sorry, too late." : "Please enter above.")
.onAppear(perform: delayText)
}
private func delayText() {
// Delay of 7.5 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 7.5) {
hasTimeElapsed = true
}
}
}
Upvotes: 113
Reputation: 28473
.delay
is built-in to Swift animations. I achieved my goal of launching an animation 0.5 seconds after a view appeared with the following code:
.onAppear(perform: {
withAnimation(Animation.spring().delay(0.5)) {
self.scale = 1.0
}
})
Upvotes: 32