1

I have a class with a timer (updating every millisecond).

class TimeCount {
    let currentTimePublisher = Timer.TimerPublisher(interval: 0.001, runLoop: .main, mode: .common)
    let cancellable: AnyCancellable?

    init() {
        self.cancellable = currentTimePublisher.connect() as? AnyCancellable
    }

    deinit {
        self.cancellable?.cancel()
    }
}

I also have a List() of TimerView objects

List() {
    ForEach(self.timers) { timer in
        TimerPlusView(timer: timer)
    }
}

And inside each object i have a Text that updates its content listening to the timer

Text("\(currentTime.timeIntervalSince(timer.time ?? Date()))")
    .font(.largeTitle)
    .fontWeight(.bold)
    .foregroundColor(.black)
    .opacity(0.5)
    .onReceive(timeCount.currentTimePublisher) { newCurrentTime in
        self.currentTime = newCurrentTime
    }

Thing is, after (not while) scrolling the list for about 100px, the timer stops working and the labels stop updating, and I have no idea why.

Behavior I get: enter image description here

UPD: here's a link to the full project for reference. https://www.dropbox.com/s/47zoizfqp6upz1e/TimerMinimal.zip?dl=0

Alexey Primechaev
  • 907
  • 1
  • 6
  • 13
  • 2
    From your code, I don't get a clue why your timers stop updating. Maybe you could post a link to a minimal xcode project to help others reproduce it on their machine. Maybe this also is a simulator bug - did you try it on a device also? – Andreas Oetjen Jan 10 '20 at 07:22
  • 1
    Yeah, neither do I. NP, here's a link to the project https://www.dropbox.com/s/47zoizfqp6upz1e/TimerMinimal.zip?dl=0 And yes, I tried using both a simulator and a real device — same deal everywhere. – Alexey Primechaev Jan 10 '20 at 08:00
  • 1
    When a scroll view is scrolling, a different run loop takes over. You should add your timer to the tracking run loop as well. https://stackoverflow.com/questions/7551411/uiscrollview-blocks-run-loop – EmilioPelaez Jan 28 '20 at 20:52

1 Answers1

0

I think the problem is a bug in Xcode. Or at least something very undocumented. Or just a mystery.

The issue seems to occur when the views are being re-used from the table view (like the old dequeue-stuff) which then somehow causes the subscription to get lost, and no timer events are published any more.

Nevertheless, I found something like a work-around.

In TimerPlusView.swift, move the .onReceive handler to the very bottom of the body view, e.g.

var body: some View {

    Button(action: {
        self.timer.title = "Tapped"
    }) {
        VStack(alignment: .leading) {
            Text(timer.title ?? "New Timer")
                .font(.largeTitle)
                .fontWeight(.bold)
                .foregroundColor(.black)
            Text("\(currentTime.timeIntervalSince(timer.time ?? Date()))")
                .font(.largeTitle)
                .fontWeight(.bold)
                .foregroundColor(.black)
                .opacity(0.5)
            // onReceive doesn't seem to work here:
            //.onReceive(timeCount.currentTimePublisher) { newCurrentTime in
            //  self.currentTime = newCurrentTime
            // }

        }
    }
    .buttonStyle(BorderlessButtonStyle())
    .contextMenu {
        Button(action: {
            self.context.delete(self.timer)
        }) {
            Text("Delete")
        }
    }
    // here, onReceive works:
    .onReceive(timeCount.currentTimePublisher) { newCurrentTime in
      self.currentTime = newCurrentTime
    }
}
Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34