Timer onReceive not working inside NavigationView
It is better to attach observers to non-conditional views, like
var body: some View {
NavigationView{
VStack {
if(self.timeRemaining > 0) {
Text("\(timeRemaining)")
} else {
Text("Time is up!")
}
}
.onReceive(timer) { _ in // << to VStack
if self.timeRemaining > 0 {
self.timeRemaining -= 1
}
}
}
}
Update: some thoughts added (of course SwiftUI internals are known only for Apple).
.onReceive must be attached to persistently present view in NavigationView, the reason is most probably in conditional ViewBuilder and internal of NavigationView wrapper around UIKit UINavigationControler.
if you remove the condition and have a single Text view with onReceive inside the VStack inside the NavigationView, nothing is ever received
If after condition removed it is attached to Text("\(timeRemaining)")
then all works (tested with Xcode 11.4), because there is state dependency in body.
If it is attached to constant Text then there is nothing changed in body, ie. dependent on changed state - timeRemaining
, so SwiftUI rendering engine interprets body as static.
onReceive(self.timer) doesn't work inside a NavigationView
Here is a demo of possible solution for your case. Tested with Xcode 11.4 / iOS 13.4 (and it is forward compatible)
The idea is to react not by timer but by view position change that has been read/tracked by view preferences.
struct ViewOffsetKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct DemoView: View {
@State var showHeader: Bool = false
var body: some View {
NavigationView {
ZStack(alignment: .top) {
ScrollView(.vertical) {
Text("User info component")
.background(GeometryReader {
Color.clear.preference(key: ViewOffsetKey.self,
value: -$0.frame(in: .named("scroll_area")).origin.y) })
VStack {
Text("content")
}.frame(maxWidth: .infinity, minHeight: 1500)
}.coordinateSpace(name: "scroll_area")
if showHeader {
Text("ProfileHeader")
}
}
}
.onPreferenceChange(ViewOffsetKey.self) {
self.showHeader = $0 > 200 // << your condition
}
}
}
SwiftUI : How I can maintain onReceive when the View in closed
Put onReceive on some always-shown view, like
var body: some View {
NavigationView {
ZStack {
Color("BackgroundColor").ignoresSafeArea(.all)
.onReceive(tm.timer) { _ in // << here !!
from handling purpose it does not matter where it is used.
SwiftUI Navigation: why is Timer.publish() in View body breaking nav stack
First, if you remove @StateObject
from model declaration in ContentView, it will work.
You should not set the whole model as a State for the root view.
If you do, on each change of any published property, your whole hierarchy will be reconstructed. You will agree that if you type changes in the text field, you don't want the complete UI to rebuild at each letter.
Now, about the behaviour you describe, that's weird.
Given what's said above, it looks like when you type, the whole view is reconstructed, as expected since your model is a @State
object, but reconstruction is broken by this unmanaged timer.. I have no real clue to explain it, but I have a rule to avoid it ;)
Rule:
You should not make timers in view builders. Remember swiftUI views are builders and not 'views' as we used to represent before. The concrete view object is returned by the 'body' function.
If you put a break on timer creation, you will notice your timer is called as soon as the root view is displayed. ( from NavigationLink(destination: NavLevel2())
That's probably not what you expect.
If you move your timer creation in the body, it will work, because the timer is then created when the view is created.
var body: some View {
var timer = Timer.publish(every: 2, on: .main, in: .common)
print(Self._printChanges())
return NavigationLink(
destination: NavLevel3()
) { Text("Level 2") }
}
However, it is usually not the right way neither.
You should create the timer:
in the
.appear
handler, keep the reference,
and cancel the timer in.disappear
handler.in a
.task
handler that is reserved for asynchronous tasks.
I personally only declare wrapped values ( @State
, @Binding
, .. ) in view builders structs, or very simple primitives variables ( Bool, Int, .. ) that I use as conditions when building the view.
I keep all functional stuffs in the body or in handlers.
SwiftUI sharing timer through views in navigation view
NavigationView
can only push on one detail NavigationLink
so to have more levels you need to set .isDetailLink(false)
on the link. Alternatively, if you don't expect to run in landscape split view, you could set .navigationViewStyle(.stack)
on the navigation.
How to navigate another view in onReceive timer closure SwiftUI iOS
Here is possible approach (of course assuming that TwitterWebView
has NavigationView
somewhere in parents)
struct TwitterWebView: View {
@State var timerTime : Float
@State var minute: Float = 0.0
@State private var showLinkTarget = false
let timer = Timer.publish(every: 60.0, on: .main, in: .common).autoconnect()
@State private var shouldNavigate = false
var body: some View {
WebView(url: "https://twitter.com/")
.navigationBarTitle("")
.navigationBarHidden(true)
.background(
NavigationLink(destination: BreakView(),
isActive: $shouldNavigate) { EmptyView() }
)
.onReceive(timer) { _ in
if self.minute == self.timerTime {
print("Timer completed navigate to Break view")
self.timer.upstream.connect().cancel()
self.shouldNavigate = true // << here
} else {
self.minute += 1.0
}
}
}
}
Swiftui NavigationView timer delay
This should work:
struct MyView: View {
@State var pushNewView: Bool = false
var body: some View {
NavigationView {
NavigationLink(isActive: $pushNewView) {
Text("Second View")
} label: {
Text("First View") //Replace with some animation content
}
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
pushNewView = true
}
}
}
}
Related Topics
Add Uiimage as Texture to a Plane in Realitykit
Accessibility (Voice Over) with Sprite Kit
How to Keep a Reference to Another Object in the Parameters of the Class
Error: Initializer 'Init(_:)' Requires That 'Binding<String>' Conform to 'Stringprotocol'
Swiftui MACos Scroll a List with Arrow Keys While a Textfield Is Active
How to Get the Kvc-String from Swift 4 Keypath
Nskeyedunarchiver Fails to Decode a Custom Object in Swift
Increment Integer in Nsuserdefaults
How to Check the Type of a Variable Against Another Variable in Swift
Uicollectionview Scrolltoitematindexpath, Not Loading Visible Cells Until Animation Complete
How to Hash a String to Sha512 in Swift
Swift Get Nsdata of a Video from Photos Library
Prevent Redirect Response with Alamofire in Swift
How to Create an Uppercase Version of a String in Swiftui
Make a Type Itself -- Not Its Instances -- Conform to a Protocol