79353623

Date: 2025-01-13 23:00:17
Score: 0.5
Natty:
Report link

Ok, so I came up with this custom router implementation to achieve the desired behaviour I want

import SwiftUI

protocol Closable {
  func close()
}

class Router: ObservableObject {
  @Published var values: [Int: Closable?] = [:]
  @Published var path: [Int] = [] {
    didSet {
      let difference: CollectionDifference<Int> = path.difference(from: oldValue)
      difference.forEach { change in
        switch change {
        case .remove(_, let key, _):
          values.removeValue(forKey: key)??.close()
        default:
          break
        }
      }
    }
  }
  
  func register(key: Int, value: Closable) {
    values[key] = value
  }
  
  func push(key: Int){
    values[key] = nil
    path.append(key)
  }
  
  func pop(){
    let key = path.removeLast()
    values.removeValue(forKey: key)??.close()
  }
}

struct TimerList: View {
  @State private var times = [0, 1, 2, 3, 4, 5]
  @StateObject var router = Router()
  
  var body: some View {
    NavigationStack(path: $router.path) {
      List(times, id: \.self) { time in
        Button(
          action: { router.push(key: time) },
          label: {
            Text("\(time)")
          }
        )
      }
      .navigationDestination(for: Int.self) { time in
        TimerView(time: time)
          .environmentObject(router)
      }
    }
  }
}

class TimerViewModel: ObservableObject, Closable {
  let initial: Int
  @Published var time: Int
  
  private var task: Task<Void, Never>? = nil
  
  init(time: Int) {
    self.initial = time
    self.time = time
  }
  
  func close() {
    task?.cancel()
  }
  
  @MainActor
  func start() {
    if task != nil { return }
    task = Task { [weak self] in
      guard let self = self else { return }
      repeat {
        do { try await Task.sleep(nanoseconds: 1_000_000_000) }
        catch { return }
        self.time += 1
        print("Timer \(initial) incremented to \(time)")
      } while !Task.isCancelled
    }
  }
}

struct TimerView: View {
  let time: Int
  @EnvironmentObject var router: Router
  @StateObject var viewModel: TimerViewModel
  
  init(time: Int) {
    self.time = time
    _viewModel = StateObject(wrappedValue: TimerViewModel(time: time))
  }
  
  var body: some View {
    VStack {
      Text("Timer #\(viewModel.initial) is \(viewModel.time)")
      NavigationLink(value: time + 1, label: { Text("Next") })
    }
    .onAppear {
      viewModel.start()
      router.register(key: time, value: viewModel)
    }
  }
}

Not sure if this is the best way to do it, but it does work

Reasons:
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: xxfast