如何使用@Bindable 值动态更新 SwiftUI 视图

How to dynamically update a SwiftUI View with an @Bindable value

我有一个 SwiftUI ProgressBar 视图,它根据您作为可绑定参数输入的百分比值显示进度百分比。 progress percentage-value 是由一个ReminderHelper class 计算的,它以两个Int 为参数,totalDaysdaysLeft,输入到ReminderHelper 的值class 来自保存在 Core Data 中的 Reminder 对象。

由于对 SwiftUI/Combine、@Binding、@Published、@State 等的工作原理了解甚少,我对如何构建我的代码来完成此类事情感到非常困惑。

根据下面的代码,我希望看到两个提醒,Cut the Grass 在 20% 和 Power Wash Siding 在 50%。同样,确定总百分比进度的两个 Ints 来自 Core Data 中保存的 Reminder 对象,实际总百分比结果来自 RemindersHelper class.

知道如何完成我上面描述的内容吗?

型号:

这保存在核心数据中。

class Reminder:Identifiable{
    var name = ""
    var totalDays = 0
    var daysLeft = 0
    
    init(name:String, totalDays:Int, daysLeft:Int){
        self.name = name
        self.totalDays = totalDays
        self.daysLeft = daysLeft
    }
}

帮手class

这需要负责计算将传递给 ProgressBar View 的总百分比 来自保存在 Core Data 中的 Reminder 对象。

class ReminderHelper:ObservableObject{
    @Published var percentageLeft: Float = 0.80
    
    func calculatePerentageLeft(daysLeft: Int, totalDays:Int)->Float{
        percentageLeft  = Float(daysLeft / totalDays)
        return percentageLeft
    }
}

内容视图:

在这里,我调用 calculatePerentageLeft 方法来准备 percentageLeft 属性,然后再显示 ProgressBar。这当然是行不通的。 我看到一个错误:

Static method 'buildBlock' requires that 'Float' conform to 'View'

struct ContentView: View {
    var reminders = [Reminder(name: "Cut the Grass", totalDays: 50, daysLeft: 10),
                Reminder(name: "Power Wash Siding", totalDays: 30, daysLeft: 15)]
    
    @StateObject var reminderModel = ReminderHelper()
    
    var body: some View {
        List {
            ForEach(reminders) { reminder in
                HStack{
                    Text(reminder.name)
                    reminderModel.calculatePerentageLeft(daysLeft: reminder.daysLeft, totalDays: reminder.totalDays)
                    ProgressBar(progress: reminderModel.percentageLeft)
                }
            }
        }
    }
}

进度条视图

这是负责绘制和显示百分比值的视图。

struct ProgressBar: View {
    @Binding var progress: Float
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth:5.0)
                .opacity(0.3)
                .foregroundColor(Color.orange)
            
            Circle()
                .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
                .stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round))
                .foregroundColor(Color.orange)
                .rotationEffect(Angle(degrees: 270.0))
                .animation(.linear, value: progress)

            VStack{
                Text(String(format: "%.0f %%", min(self.progress, 1.0)*100.0))
                    .font(.caption2)
            }
        }
    }
}

错误出现是因为你的函数 return 是一个 Float 并且在像 HStack 这样的构建块中调用的每个函数都应该 return 一个视图。

您应该更改模型的设计以完成您想要的。

在您的模型中创建一个计算变量(这不会影响 Coredata)。

class Reminder:Identifiable{
    var name = ""
    var totalDays = 0
    var daysLeft = 0
    
    var percentageLeft: Float {
        Float(daysLeft) / Float(totalDays)
    }
    
    init(name:String, totalDays:Int, daysLeft:Int){
        self.name = name
        self.totalDays = totalDays
        self.daysLeft = daysLeft
    }
}

然后用这个变量显示进度:

struct ContentView: View {
    var reminders = [Reminder(name: "Cut the Grass", totalDays: 50, daysLeft: 10),
                Reminder(name: "Power Wash Siding", totalDays: 30, daysLeft: 15)]
    
    var body: some View {
        List {
            ForEach(reminders) { reminder in
                HStack{
                    Text(reminder.name)
                    ProgressBar(progress: reminder.percentageLeft)
                }
            }
        }
    }
}

其他更改:

  • 根据您的整体设计,您可能不再需要此处的 Viewmodel。
  • 我将 Float(daysLeft / totalDays) 更改为 Float(dayLeft) / Float(totalDays) 因为第一个总是会产生 0
  • 我把@Bining var progress: Float改成了var progress: Float。你不需要在这里绑定。如果进度值在 ProgressView 内部发生更改并且需要传播到父级 ContentView
  • ,则只需要一个