在 SwiftUI 中使用 didSet 闪烁符号

Blinking symbol with didSet in SwiftUI

这是从一个更大的应用程序合成的。我试图通过激活 属性 的 didSet 中的计时器来使 SwiftUI 中的 SF 符号闪烁。计时器内的打印语句打印预期值,但视图未更新。

我在整个模型数据中都使用了结构,我猜这与值类型和引用类型有关。我试图避免从结构转换为 类.

import SwiftUI
import Combine

@main
struct TestBlinkApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

class Model: ObservableObject {
  @Published var items: [Item] = []
  
  static var loadData: Model {
    let model = Model()
    model.items = [Item("Item1"), Item("Item2"), Item("Item3"), Item("Item4")]
    
    return model
  }
}

struct Item {
  static let ledBlinkTimer: TimeInterval = 0.5
  
  private let ledTimer = Timer.publish(every: ledBlinkTimer, tolerance: ledBlinkTimer * 0.1, on: .main, in: .default).autoconnect()
  
  private var timerSubscription: AnyCancellable? = nil
  
  var name: String
  
  var isLEDon = false
  
  var isLedBlinking = false {
    didSet {
      var result = self
      print("in didSet: isLedBlinking: \(result.isLedBlinking) isLEDon: \(result.isLEDon)")
      guard result.isLedBlinking else {
        result.isLEDon = true
        result.ledTimer.upstream.connect().cancel()
        print("Cancelling timer.")
        return
      }
      result.timerSubscription = result.ledTimer
        .sink {  _ in
          result.isLEDon.toggle()
          print("\(result.name) in ledTimer isLEDon: \(result.isLEDon)")
        }
    }
  }
  
  init(_ name: String) {
    self.name = name
  }
}


struct ContentView: View {
  @StateObject var model = Model.loadData
  
  let color = Color(UIColor.label)
  
  public var body: some View {
    VStack {
      Text(model.items[0].name)
      Image(systemName: model.items[0].isLEDon ? "circle.fill" : "circle")
        .foregroundColor(model.items[0].isLEDon ? .green : color)
      Button("Toggle") {
        model.items[0].isLedBlinking.toggle()
      }
    }
    .foregroundColor(color)
  }
}

触摸“切换”按钮会启动假定使圆圈闪烁的计时器。打印语句显示值发生变化,但视图没有更新。为什么??

您可以使用动画使其闪烁,而不是计时器。

Item的模型被简化了,你只需要一个布尔变量,像这样:

struct Item {
    
    var name: String
    
    // Just a toggle: blink/ no blink
    var isLedBlinking = false

    init(_ name: String) {
        self.name = name
    }
}

视图完成了“艰苦的工作”:更改变量触发或停止闪烁。动画有魔力:

struct ContentView: View {
    @StateObject var model = Model.loadData
    
    let color = Color(UIColor.label)
    
    public var body: some View {
        VStack {
            Text(model.items[0].name)
                .padding()
            
            // Change based on isLedBlinking
            Image(systemName: model.items[0].isLedBlinking ? "circle.fill" : "circle")
                .font(.largeTitle)
                .foregroundColor(model.items[0].isLedBlinking ? .green : color)
            
                // Animates the view based on isLedBlinking: when is blinking, blinks forever, otherwise does nothing
                .animation(model.items[0].isLedBlinking ? .easeInOut.repeatForever() : .default, value: model.items[0].isLedBlinking)
                .padding()
            
            Button("Toggle: \(model.items[0].isLedBlinking ? "Blinking" : "Still")") {
                model.items[0].isLedBlinking.toggle()
            }
            .padding()
        }
        .foregroundColor(color)
    }
}

使用计时器的不同方法:

struct ContentView: View {
  @StateObject var model = Model.loadData
  
  let timer = Timer.publish(every: 0.25, tolerance: 0.1, on: .main, in: .common).autoconnect()
  
  let color = Color(UIColor.label)
  
  public var body: some View {
    VStack {
      Text(model.items[0].name)
      
      if model.items[0].isLedBlinking {
        Image(systemName: model.items[0].isLEDon ? "circle.fill" : "circle")
          .onReceive(timer) { _ in
            model.items[0].isLEDon.toggle()
          }
          .foregroundColor(model.items[0].isLEDon ? .green : color)
      } else {
        Image(systemName: model.items[0].isLEDon ? "circle.fill" : "circle")
          .foregroundColor(model.items[0].isLEDon ? .green : color)
      }
      
      Button("Toggle: \(model.items[0].isLedBlinking ? "Blinking" : "Still")") {
        model.items[0].isLedBlinking.toggle()
      }
    }
    .foregroundColor(color)
  }
}