Getting Unresolved Identifier 'Self' in Swiftui Code Trying to Use Timer.Scheduledtimer

getting unresolved identifier 'self' in SwiftUI code trying to use Timer.scheduledTimer

It isn't clear to me that you need a timer for your example, but since there is a great deal of misinformation out there about how to include a Timer in a SwiftUI app, I'll demonstrate.

The key is to put the timer elsewhere and publish each time it fires. We can easily do this by adding a class that holds the timer as a bindable object to our environment (note that you will need to import Combine):

class TimerHolder : BindableObject {
var timer : Timer!
let didChange = PassthroughSubject<TimerHolder,Never>()
var count = 0 {
didSet {
self.didChange.send(self)
}
}
func start() {
self.timer?.invalidate()
self.count = 0
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] _ in
guard let self = self else { return }
self.count += 1
}
}
}

We will need to pass an instance of this class down through the environment, so we modify the scene delegate:

window.rootViewController = 
UIHostingController(rootView: ContentView())

becomes

window.rootViewController = 
UIHostingController(rootView: ContentView()
.environmentObject(TimerHolder()))

Finally, lets put up some UI that starts the timer and displays the count, to prove that it is working:

struct ContentView : View {
@EnvironmentObject var timerHolder : TimerHolder
var body: some View {
VStack {
Button("Start Timer") { self.timerHolder.start() }
Text(String(self.timerHolder.count))
}
}
}

EDIT Update for those who have not been following the plot: BindableObject has migrated into ObservableObject, and there is no need any more for manual signalling. So:

class TimerHolder : ObservableObject {
var timer : Timer!
@Published var count = 0
// ... and the rest is as before ...

Assigning an ObservedState’s published value to a state

If you want to update local state on view model published property (for whatever reason), here is a way to do this

Text(time)   // << not binding, just property
.onReceive(timerWrapper.$remainingSeconds) { value in // listen for publisher $
self.time = value
}

SwiftUI - Optional Timer, reset and recreate

There is no need to throw away the TimerPublisher itself. Timer.publish creates a Timer.TimerPublisher instance, which like all other publishers, only starts emitting values when you create a subscription to it - and it stops emitting as soon as the subscription is closed.

So instead of recreating the TimerPublisher, you just need to recreate the subscription to it - when the need arises.

So assign the Timer.publish on declaration, but don't autoconnect() it. Whenever you want to start the timer, call connect on it and save the Cancellable in an instance property. Then whenever you want to stop the timer, call cancel on the Cancellable and set it to nil.

You can find below a fully working view with a preview that starts the timer after 5 seconds, updates the view every second and stops streaming after 30 seconds.

This can be improved further by storing the publisher and the subscription on a view model and just injecting that into the view.

struct TimerView: View {
@State private var text: String = "Not started"

private var timerSubscription: Cancellable?

private let timer = Timer.publish(every: 1, on: .main, in: .common)

var body: some View {
Text(text)
.onReceive(timer) {
self.text = "The time is now \($0)"
}
}

mutating func startTimer() {
timerSubscription = timer.connect()
}

mutating func stopTimer() {
timerSubscription?.cancel()
timerSubscription = nil
}
}

struct TimerView_Previews: PreviewProvider {
static var previews: some View {
var timerView = TimerView()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
timerView.startTimer()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
timerView.stopTimer()
}
return timerView
}
}

With a view model, you don't even need to expose a TimerPublisher (or any Publisher) to the view, but can simply update an @Published property and display that in the body of your view. This enables you to declare timer as autoconnect, which means you don't manually need to call cancel on it, you can simply nil out the subscription reference to stop the timer.

class TimerViewModel: ObservableObject {
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

private var timerSubscription: Cancellable?

@Published var time: Date = Date()

func startTimer() {
timerSubscription = timer.assign(to: \.time, on: self)
}

func stopTimer() {
timerSubscription = nil
}
}

struct TimerView: View {
@ObservedObject var viewModel: TimerViewModel

var body: some View {
Text(viewModel.time.description)
}
}

struct TimerView_Previews: PreviewProvider {
static var previews: some View {
let viewModel = TimerViewModel()
let timerView = TimerView(viewModel: viewModel)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
viewModel.startTimer()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
viewModel.stopTimer()
}
return timerView
}
}

How to update text using timer in SwiftUI

i have managed to update text using alert.

i have declared date as State so whenever date is changed using alert text will also get updated.

struct CurrentDateView : View {
@State var newDate = Date()

let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()

var body: some View {
Text("\(newDate)")
.onReceive(timer) {
self.newDate = Date()
}
}
}

How can I make a countdown with NSTimer?

Question 1:

@IBOutlet var countDownLabel: UILabel!

var count = 10

override func viewDidLoad() {
super.viewDidLoad()

var timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(UIMenuController.update), userInfo: nil, repeats: true)
}

func update() {
if(count > 0) {
countDownLabel.text = String(count--)
}
}

Question 2:

You can do both. SpriteKit is the SDK you use for scene, motion, etc. Simple View Application is the project template. They should not conflict

Retrieve value from function with swift closure

If I'm understanding the structure/question correctly, your goal is to access the WholeDocument array in another SwiftUI view. The way to do that could be:

class UserData : ObservableObject {

// Use Firebase library to configure APIs
private var db = Firestore.firestore()

@Published var WholeDocument : Array<String> = []

init() {
db.collection("Users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion([""])
} else {
for document in querySnapshot!.documents {
// print("\(document.documentID) => \(document.data())")
self.WholeDocument.append(document.documentID)
}
}
}
}
}

struct MyContentView : View {

@ObservedObject var database: UserData

var body: some View {
// access database.WholeDocument
List(database.WholeDocument, id: \.self) {
// do something
}
}

That way, when you initialize MyContentView with an instance of UserData, you will be able to observe WholeDocument in that view.

How Do I Cancel and Restart a Timed Event in Swift?

Use a timer:

weak var clearTimer: Timer?

And:

override func viewDidLoad() {
super.viewDidLoad()
startClearTimer()
}

func startClearTimer() {
clearTimer = Timer.scheduledTimer(
timeInterval: 3.0,
target: self,
selector: #selector(clearLabel(_:)),
userInfo: nil,
repeats: false)
}

func clearLabel(_ timer: Timer) {
label.text = ""
}

func volumeSliderValueChange(sender: UISlider) {
clearTimer?.invalidate() //Kill the timer
//do whatever you need to do with the slider value
startClearTimer() //Start a new timer
}


Related Topics



Leave a reply



Submit