Update swiftUI view on UserDefaults update - ios

Im a total noob to Swift and SwiftUI and im trying to build a project for myself where i can track my workouts and learn myself some Swift.
The problem im hitting is that i have a view which shows all my workout sessions and are formatted by a setting i have in UserDefaults. The user can change this setting to 'metric' or 'imperial'
When changing this the view should update to represent those changes.
The data i have is:
extension WorkoutSession {
#nonobjc public class func fetchRequest() -> NSFetchRequest<WorkoutSession> {
return NSFetchRequest<WorkoutSession>(entityName: "WorkoutSession")
}
#NSManaged public var created_at: Date?
#NSManaged public var id: UUID?
#NSManaged public var reps: Int16
#NSManaged public var sets: Int16
#NSManaged public var updated_at: Date?
#NSManaged public var weight: Double
#NSManaged public var height: Double
#NSManaged public var notes: String?
#NSManaged public var type: Workout?
override public func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: "id")
setPrimitiveValue(Date(), forKey: "updated_at")
}
override public func willSave() {
super.willSave()
if let updated_at = updated_at {
if updated_at.timeIntervalSince(Date()) > 10.0 {
self.updated_at = Date()
}
} else {
self.updated_at = Date()
}
}
var formattedWeight: String {
var measurement = Measurement(value: self.weight, unit: UserDefaultsWrapper().savedUnitWeight)
let numberFormatter = NumberFormatter()
let measurementFormatter = MeasurementFormatter()
measurement.convert(to:UserDefaultsWrapper().getWeightUnit)
numberFormatter.maximumFractionDigits = 2
measurementFormatter.unitOptions = .providedUnit
measurementFormatter.numberFormatter = numberFormatter
return measurementFormatter.string(from: measurement)
}
// No matter the user defined weight we always save the values in Kilogram so we can convert to anything afterwards
func convertWeightFromUserDefinedUnitToKilogram(weight: Double) -> Double {
let measurement = Measurement(value: weight, unit: UserDefaultsWrapper().getWeightUnit)
return measurement.converted(to: UserDefaultsWrapper().savedUnitWeight).value
}
}
extension WorkoutSession : Identifiable {
}
I also made myself a helper for userDefaults
import SwiftUI
import Foundation
enum UserDefaultsKeys: String {
case measurementUnit = "measurementUnit"
}
enum measurementUnit: String, CaseIterable {
case metric = "metric"
case imperial = "Imperial"
}
struct UserDefaultsWrapper {
let defaults = UserDefaults.standard
var savedUnitWeight = UnitMass.kilograms
var savedUnitHeight = UnitLength.meters
#AppStorage(UserDefaultsKeys.measurementUnit.rawValue) var selectedUnit: String = measurementUnit.metric.rawValue
var getMeasurementUnit: measurementUnit {
get {
let locale = Locale.current
let systemMeasurementUnit = locale.usesMetricSystem ? measurementUnit.metric : measurementUnit.imperial
return measurementUnit(rawValue: selectedUnit) ?? systemMeasurementUnit
}
set(unit) {
defaults.set(unit.rawValue, forKey: UserDefaultsKeys.measurementUnit.rawValue)
}
}
var getWeightUnit: UnitMass {
switch getMeasurementUnit {
case measurementUnit.imperial:
return UnitMass.pounds
case measurementUnit.metric:
fallthrough
default:
return UnitMass.kilograms
}
}
var getHeightUnit: UnitLength {
switch getMeasurementUnit {
case measurementUnit.imperial:
return UnitLength.feet
case measurementUnit.metric:
fallthrough
default:
return UnitLength.meters
}
}
}
And this is the view
import SwiftUI
struct WorkoutDetailView: View {
#Environment(\.managedObjectContext) var moc
#State private var showingAddWorkoutView = false
#ObservedObject var workout: Workout
var body: some View {
List {
ForEach(workout.sessionsArray, id: \.id) { session in
Text("\(session.formattedWeight)")
}
.onDelete(
perform: { offsets in
self.removeItems(at: offsets, from: workout)
}
)
}
.listStyle(InsetGroupedListStyle())
.navigationTitle(workout.name ?? "Unknown Workout")
.navigationBarItems(
leading: EditButton(),
trailing:
Button(action: {
self.showingAddWorkoutView = true
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddWorkoutView) {
AddWorkoutSession(workout: workout)
.environment(\.managedObjectContext, moc)
}
}
func removeItems(at offsets: IndexSet, from workout: Workout) {
for offset in offsets {
let sessionToDelete = workout.sessionsArray[offset]
workout.removeFromSessions(sessionToDelete)
moc.delete(sessionToDelete)
}
if moc.hasChanges{
try? moc.save()
}
}
}
The problem is that when i change the unit from imperial to metric and i go back to the session view the view is not updated, but when i go back to the workout view and open the session view again the changes are there.
Please let me know if you need more code.

Related

Return Duplicate record with Realm

I am using Relam to store the data locally and working fine but when I try to add the new record with navigation link it returns the duplicate record as well . Another problem is when I click the record , I am expecting change the navigation but since it got duplicate record , the first record does not work but the second one it work .
Here is the Model .
import SwiftUI
import RealmSwift
struct Task: Identifiable {
var id: String
var title: String
var completed: Bool = false
var completedAt: Date = Date()
init(taskObject: TaskObject) {
self.id = taskObject.id.stringValue
self.title = taskObject.title
self.completed = taskObject.completed
self.completedAt = taskObject.completedAt
}
}
Here is the Persisted Model...
import Foundation
import RealmSwift
class TaskObject: Object {
#Persisted(primaryKey: true) var id: ObjectId
#Persisted var title: String
#Persisted var completed: Bool = false
#Persisted var completedAt: Date = Date()
}
Here is the View Model ..
/
/ 2
final class TaskViewModel: ObservableObject {
// 3
#Published var tasks: [Task] = []
// 4
private var token: NotificationToken?
init() {
setupObserver()
}
deinit {
token?.invalidate()
}
// 5
private func setupObserver() {
do {
let realm = try Realm()
let results = realm.objects(TaskObject.self)
token = results.observe({ [weak self] changes in
// 6
self?.tasks = results.map(Task.init)
.sorted(by: { $0.completedAt > $1.completedAt })
.sorted(by: { !$0.completed && $1.completed })
})
} catch let error {
print(error.localizedDescription)
}
}
// 7
func addTask(title: String) {
let taskObject = TaskObject(value: [
"title": title,
"completed": false
])
do {
let realm = try Realm()
try realm.write {
realm.add(taskObject)
}
} catch let error {
print(error.localizedDescription)
}
}
// 8
func markComplete(id: String, completed: Bool) {
do {
let realm = try Realm()
let objectId = try ObjectId(string: id)
let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId)
try realm.write {
task?.completed = completed
task?.completedAt = Date()
}
} catch let error {
print(error.localizedDescription)
}
}
func remove(id: String) {
do {
let realm = try Realm()
let objectId = try ObjectId(string: id)
if let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId) {
try realm.write {
realm.delete(task)
}
}
} catch let error {
print(error.localizedDescription)
}
}
func updateTitle(id: String, newTitle: String) {
do {
let realm = try Realm()
let objectId = try ObjectId(string: id)
let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId)
try realm.write {
task?.title = newTitle
}
} catch let error {
print(error.localizedDescription)
}
}
}
Here is the code for Content view ...
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
AddTaskView()
TaskListView()
}
.navigationTitle("Todo")
.navigationBarTitleDisplayMode(.automatic)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the code for Add task view ..
import SwiftUI
struct AddTaskView: View {
#State private var taskTitle: String = ""
#EnvironmentObject private var viewModel: TaskViewModel
var body: some View {
HStack(spacing: 12) {
TextField("Enter New Task..", text: $taskTitle)
Button(action: handleSubmit) {
Image(systemName: "plus")
}
}
.padding(20)
}
private func handleSubmit() {
viewModel.addTask(title: taskTitle)
taskTitle = ""
}
}
Here is the Task list View ..
struct TaskListView: View {
#EnvironmentObject private var viewModel: TaskViewModel
var body: some View {
ScrollView {
LazyVStack (alignment: .leading) {
ForEach(viewModel.tasks, id: \.id) { task in
TaskRowView(task: task)
Divider().padding(.leading, 20)
NavigationLink (destination: TaskView(task: task)) {
TaskRowView(task: task)
}.animation(.default)
}
}
}
}
}
Here is the code for Row View ..
struct TaskRowView: View {
let task: Task
// 1
#EnvironmentObject private var viewModel: TaskViewModel
var body: some View {
HStack(spacing: 12) {
Button(action: {
// 2
viewModel.markComplete(id: task.id, completed: !task.completed)
}) {
Image(systemName: task.completed ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(task.completed ? Color.green : Color.gray)
}
VStack(alignment: .leading, spacing: 8) {
Text(task.title)
.foregroundColor(.black)
if !task.completedAt.formatted().isEmpty {
Text(task.completedAt.formatted())
.foregroundColor(.gray)
.font(.caption)
}
}
Spacer()
}
.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20))
}
}
Here is the screenshot ..
Let's troubleshoot the discrepancies one by one.
According to your code, each row in the list represents a Task. But, there are two models Task and TaskObject (persistable model) for that.
struct Task: Identifiable {
var id: String
var title: String
var completed: Bool = false
var completedAt: Date = Date()
init(taskObject: TaskObject) {
self.id = taskObject.id.stringValue
self.title = taskObject.title
self.completed = taskObject.completed
self.completedAt = taskObject.completedAt
}
}
class TaskObject: Object {
#Persisted(primaryKey: true) var id: ObjectId
#Persisted var title: String
#Persisted var completed: Bool = false
#Persisted var completedAt: Date = Date()
}
Instead of using two models, convert them into one.
class TaskObject: Object, Identifiable {
#Persisted(primaryKey: true) var id: ObjectId
#Persisted var title: String
#Persisted var completed: Bool = false
#Persisted var completedAt: Date = Date()
var idStr: String {
id.stringValue
}
}
Therefore, there's no need for mapping to another object after retrieving it from the database. The updated setupObserver function should be...
private func setupObserver() {
do {
let realm = try Realm()
let results = realm.objects(TaskObject.self)
token = results.observe({ [weak self] changes in
// 6
self?.tasks = results
.sorted(by: { $0.completedAt > $1.completedAt })
.sorted(by: { !$0.completed && $1.completed })
})
} catch let error {
print(error.localizedDescription)
}
}
Let's address your questions now.
When I try to add the new record with navigation link it returns the duplicate record as well
It does not produce duplicate data. Instead, the same data is displayed twice in the view. To correct this, remove one of the two instances of TaskRowView(task: task).
struct TaskListView: View {
#EnvironmentObject private var viewModel: TaskViewModel
var body: some View {
ScrollView {
LazyVStack (alignment: .leading) {
ForEach(viewModel.tasks, id: \.id) { task in
TaskRowView(task: task) // first row 📌
Divider().padding(.leading, 20)
NavigationLink (destination: TaskView(task: task)) {
TaskRowView(task: task) // second row 📌
}.animation(.default)
}
}
}
}
}
Next question,
I am expecting change the navigation but since it got duplicate record , the first record does not work but the second one it work.
Again, the second one changes navigation, and the first one does not, because this is exactly what is written in the code.
TaskRowView(task: task) // Why would it change navigation?
Divider().padding(.leading, 20)
NavigationLink (destination: TaskView(task: task)) {
TaskRowView(task: task) // changing navigation
}

Sorting items by date. And adding numbers from today

This is Model and View Model. I am using UserDefaults for saving data.
import Foundation
struct Item: Identifiable, Codable {
var id = UUID()
var name: String
var int: Int
var date = Date()
}
class ItemViewModel: ObservableObject {
#Published var ItemList = [Item] ()
init() {
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "ItemList"),
let savedItems = try? JSONDecoder().decode([Item].self, from: data) else { ItemList = []; return }
ItemList = savedItems
}
func save() {
do {
let data = try JSONEncoder().encode(ItemList)
UserDefaults.standard.set(data, forKey: "ItemList")
} catch {
print(error)
}
}
}
and this is the view. I am tryng too add new item and sort them by date. After that adding numbers on totalNumber. I tried .sorted() in ForEach but its not work for sort by date. and I try to create a func for adding numbers and that func is not work thoo.
import SwiftUI
struct ContentView: View {
#State private var name = ""
#State private var int = 0
#AppStorage("TOTAL_NUMBER") var totalNumber = 0
#StateObject var VM = ItemViewModel()
var body: some View {
VStack(spacing: 30) {
VStack(alignment: .leading) {
HStack {
Text("Name:")
TextField("Type Here...", text: $name)
}
HStack {
Text("Number:")
TextField("Type Here...", value: $int, formatter: NumberFormatter())
}
Button {
addItem()
VM.save()
name = ""
int = 0
} label: {
Text ("ADD PERSON")
}
}
.padding()
VStack(alignment: .leading) {
List(VM.ItemList) { Item in
Text(Item.name)
Text("\(Item.int)")
Text("\(Item.date, format: .dateTime.day().month().year())")
}
Text("\(totalNumber)")
.padding()
}
}
}
func addItem() {
VM.ItemList.append(Item(name: name, int: int))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First of all please name variables always with starting lowercase letter for example
#Published var itemList = [Item] ()
#StateObject var vm = ItemViewModel()
To sort the items by date in the view model replace
itemList = savedItems
with
itemList = savedItems.sorted{ $0.date < $1.date }
To show the sum of all int properties of the today items add a #Published var totalNumber in the view model and a method to calculate the value. Call this method in load and save
class ItemViewModel: ObservableObject {
#Published var itemList = [Item] ()
#Published var totalNumber = 0
init() {
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "ItemList"),
let savedItems = try? JSONDecoder().decode([Item].self, from: data) else { itemList = []; return }
itemList = savedItems.sorted{ $0.date < $1.date }
calculateTotalNumber()
}
func save() {
do {
let data = try JSONEncoder().encode(itemList)
UserDefaults.standard.set(data, forKey: "ItemList")
calculateTotalNumber()
} catch {
print(error)
}
}
func calculateTotalNumber() {
let todayItems = itemList.filter{ Calendar.current.isDateInToday($0.date) }
totalNumber = todayItems.map(\.int).reduce(0, +)
}
}
In the view delete the #AppStorage line because the value is calculated on demand and replace
Text("\(totalNumber)")
with
Text("\(vm.totalNumber)")

Update Details on a List SwiftUI

I'm fairly new to SwiftUI and I'm trying to update the details on a list and then save it.
I am able to get the details to update, but every time I try saving I'm not able to do it.
I have marked the area where I need help. Thanks in advance.
// This is the Array to store the items:
struct ExpenseItem : Identifiable, Codable {
var id = UUID()
let name: String
let amount: Int
}
// This is the UserDefault Array
class Expenses: ObservableObject {
#Published var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
}
// View to add details :
struct AddView: View {
#State private var name = ""
#State private var amount = 0
#StateObject var expenses: Expenses
#Environment(\.dismiss) var dismiss
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Add New Count")
.toolbar {
if name != "" {
Button("Save") {
let item = ExpenseItem(name: name, amount: amount)
expenses.items.append(item)
dismiss()
}
}
}
}
}
// This is the file to update the details:
struct UpdateDhikr: View {
#EnvironmentObject var expenses : Expenses
#State var name : String
#State var amount : Int
var body: some View {
Form {
TextField("Name", text: $name)
Text("\(amount)")
Button("Tap Me") {
amount += 1
}
}
.navigationTitle("Update Count")
.toolbar {
if name != "" {
Button("Save") {
// This is where I'm having problems.
}
}
}
}
}

how to edit an existing reminder in an array

I have an array of reminders(reminder model) in a view model and want to be able to edit existing reminders specifically through and edit swipe action and then through the reminders detail screen. I tried adding a button with a sheet to my Homeview in a list and then tried updating the edited reminder in the reminders array to a property in my view model called existingRemindData by using an update function in the reminder model. this should work but the remind var created by the foreach loop in the home view doesn't keep its value when it is called in the sheet. In the home view under the edit swipe action when I assign homevm.existingRemindData = remind.data it is equal to whatever reminder I swipe on because I did a print statement to confirm but as soon as I try to use the remind var inside of the sheet for the edit action the remind var defaults to the first item in the reminder array in the view model which is obviously not right. how would I make it so it uses the correct reminder index value when trying to update the reminder or is there another way which I could implement this functionality. any help would be great and look in the code for clarification on what I talk about.
HomeView
'''
import SwiftUI
struct HomeView: View {
#StateObject private var homeVM = HomeViewModel()
#State var percent: Int = 1
#State var showDetailEditView = false
#State var showAddView = false
#State var dropDown = false
//#State var filter = false
var body: some View {
ZStack {
VStack {
List {
ForEach($homeVM.reminds) { $remind in
ReminderView(remind: $remind)
//.background(remind.theme.mainColor)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.swipeActions(edge: .leading) {
Button(action: {
self.showDetailEditView.toggle()
homeVM.existingRemindData = remind.data
print(homeVM.existingRemindData.title)
}) {
Label("Edit", systemImage: "pencil")
}
}
.sheet(isPresented: $showDetailEditView) {
NavigationView {
ReminderEditView(data: $homeVM.existingRemindData)
.navigationTitle(homeVM.existingRemindData.title)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
self.showDetailEditView.toggle()
homeVM.existingRemindData = Reminder.Data()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
self.showDetailEditView.toggle()
print("\(remind.id) \(remind.title)")
print("\(homeVM.existingRemindData.id) \(homeVM.existingRemindData.title)")
remind.update(from: homeVM.existingRemindData)
homeVM.newRemindData = Reminder.Data()
}
}
}
.background(LinearGradient(gradient: Gradient(colors: [
Color(UIColor(red: 0.376, green: 0.627, blue: 0.420, alpha: 1)),
Color(UIColor(red: 0.722, green: 0.808, blue: 0.725, alpha: 1))
]), startPoint: .topLeading, endPoint: .bottomTrailing))
}
}
.swipeActions(allowsFullSwipe: true) {
Button (role: .destructive, action: {
homeVM.deleteReminder(remind: remind)
}) {
Label("Delete", systemImage: "trash.fill")
}
}
}
}
.onAppear(
perform: {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
})
'''
Reminder edit view
'''
import SwiftUI
extension Binding {
static func ??(lhs: Binding<Optional<Value>>, rhs: Value) -> Binding<Value> {
return Binding(get: {lhs.wrappedValue ?? rhs}, set: {lhs.wrappedValue = $0})
}
}
struct ReminderEditView: View {
#ObservedObject var editVM: EditViewModel
init(data: Binding<Reminder.Data>) {
editVM = EditViewModel(data: data)
}
var body: some View {
Form {
Section {
TextField("Title", text: $editVM.data.title)
TextField("Notes", text: $editVM.data.notes ?? "")
.frame(height: 100, alignment: .top)
}
Section {
Toggle(isOn: $editVM.data.hasDueDate, label: {
if editVM.data.hasDueDate {
VStack(alignment: .leading) {
Text("Date")
Text(editVM.data.hasDueDate ? editVM.data.formatDate(date: editVM.data.date!) : "\(editVM.data.formatDate(date: Date.now))")
.font(.caption)
.foregroundColor(.red)
}
} else {
Text("Date")
}
})
if editVM.data.hasDueDate {
DatePicker("Date", selection: $editVM.data.dueDate, in: Date()..., displayedComponents: .date)
.datePickerStyle(.graphical)
}
'''
Reminder model
'''
extension Reminder {
struct Data: Identifiable {
var title: String = ""
var notes: String?
var date: Date?
var time: Date?
var theme: Theme = .poppy
var iscomplete: Bool = false
var priority: RemindPriority = .None
let id: UUID = UUID()
var dueDate: Date {
get {
return date ?? Date()
}
set {
date = newValue
}
}
var dueTime: Date {
get {
return time ?? Date()
}
set {
time = newValue
}
}
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.timeStyle = .none
return formatter.string(from: date)
}
func formatTime(time: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter.string(from: time)
}
var hasDueDate: Bool {
get {
date != nil
}
set {
if newValue == true {
date = Date()
}
else {
date = nil
hasDueTime = false
}
}
}
var hasDueTime: Bool {
get {
time != nil
}
set {
if newValue == true {
time = Date()
hasDueDate = true
}
else {
time = nil
}
}
}
}
var data: Data {
Data(title: title, notes: notes, date: date, time: time, theme: theme, iscomplete: iscomplete, priority: priority)
}
mutating func update(from data: Data) {
title = data.title
notes = data.notes
date = data.date
time = data.time
theme = data.theme
iscomplete = data.iscomplete
priority = data.priority
}
init(data: Data) {
title = data.title
notes = data.notes
date = data.date
time = data.time
theme = data.theme
iscomplete = data.iscomplete
priority = data.priority
id = data.id
}
}
'''
HomeViewModel(View model talked about)
'''
import Foundation
import SwiftUI
import Combine
class HomeViewModel: ObservableObject {
#Published var reminds: [Reminder] = Reminder.sampleReminders
#Published var newRemindData = Reminder.Data()
#Published var existingRemindData = Reminder.Data()
#Published var selectedRemind = Reminder(data: Reminder.Data())
#Published var compReminds: [Reminder] = []
private var cancellables = Set<AnyCancellable>()
/*init(reminds: [Reminder]) {
self.reminds = reminds
}*/
func newReminder() {
let newRemind = Reminder(data: newRemindData)
reminds.append(newRemind)
newRemindData = Reminder.Data()
}
func deleteReminder(remind: Reminder) {
Just(remind)
.delay(for: .seconds(0.25), scheduler: RunLoop.main)
.sink {remind in
if remind.iscomplete {
self.removeRemind(remind: remind)
}
if !remind.iscomplete {
self.removeRemind(remind: remind)
}
self.reminds.removeAll { $0.id == remind.id }
}
.store(in: &cancellables)
}
func appendRemind(complete: Reminder) {
compReminds.append(complete)
}
func removeRemind(remind: Reminder) {
compReminds.removeAll() { $0.id == remind.id }
}
func remindIndex() -> Int {
return reminds.firstIndex(where: {
$0.id == existingRemindData.id
}) ?? 1
}
We don't use view model objects in SwiftUI. Change EditViewModel class to be an EditConfig struct, declare it as #State var config: EditConfig? use it as the item in sheet(item:onDismiss:content:) instead of the bool version.
Also, your date formatting is not SwiftUI compatible, you won't benefit from the labels being updated automatically when the user changes their region settings. To fix that remove the formatDate code and instead supply the formatter to Text or simply use .date. If using a formatter object make sure you aren't initing a new one every time, e.g. store one inside an #State struct or a static var. in SwiftUI we must not init objects in a View's init and body, only value types.

Async call not getting updated using publisher in SwiftUI

I am trying to load HealthKit data and display a total distance and last workout date in a SwiftUI view (for a Widget). I am getting the data in the print statement but it's not getting updated in the HTWidgetView below:
class WidgetViewModel: ObservableObject {
#Published var workoutDate: Date = Date()
#Published var totalDistance: Double = 0.0
func getWorkoutDataForWidget() {
WorkoutManager.loadWorkouts { (workouts, error) in
DispatchQueue.main.async {
guard let unwrappedWorkouts = workouts else { return }
if let first = unwrappedWorkouts.first {
self.workoutDate = first.startDate
}
let distancesFromWorkouts = unwrappedWorkouts.compactMap {$0.totalDistance?.doubleValue(for: .foot())}
let total = distancesFromWorkouts.sum()
self.totalDistance = total
print("TOtal Distance = \(total)")
}
}
}
}
extension Sequence where Element: AdditiveArithmetic {
func sum() -> Element { reduce(.zero, +) }
}
struct HTWidgetView: View {
#ObservedObject var viewModel: WidgetViewModel
var body: some View {
VStack {
Text("Last Workout = \(viewModel.workoutDate)")
Text("Total Distance")
Text("\(viewModel.totalDistance)")
}
.onAppear {
viewModel.getWorkoutDataForWidget()
}
}
}

Resources