Проблема SwiftUI с UserDefaults в модели

Я изучаю SwiftUI. Я начал с руководств по Apple SwiftUI. В одном из них они предоставили @EnviromentalObjects для внесения изменений в свою модель, они меняли состояние isFavorite bool. Но они не использовали @ObservedObject с UserDefaults, и когда я перезагружаю свое приложение, все изменения исчезают. Я научился использовать UserDefaults для значений в представлении, и теперь я могу указать в своем списке объектов, если я хочу отображать только объекты isFavorive или все из них, и когда я перезагружаю свое приложение, эта кнопка переключения сохраняет мои изменения.

Но я хочу сделать то же самое с редактированием моей модели.

Вот моя модель:

import SwiftUI
import CoreLocation

struct City: Hashable, Codable, Identifiable {
var id: Int
var country: String
var name: String
var description: String
fileprivate var imageName: String
fileprivate var coordinates: Coordinates
var state: String
var people: Int
var area: Int
var continent: Continent
var isFavorite: Bool
var isFeatured: Bool


var locationCoordinate: CLLocationCoordinate2D {
    CLLocationCoordinate2D(
        latitude: coordinates.latitude,
        longitude: coordinates.longitude)
}

enum Continent: String, CaseIterable, Codable, Hashable {
    case europe = "Europe"
    case asia = "Asia"
    case america = "America"
}
}

И есть файл, в который я загружаю данные из файла JSON (скопировано из учебника Apple)

import UIKit
import SwiftUI
import CoreLocation

var CityData: [City] = load("CityData.json")

func load<T: Decodable>(_ filename: String) -> T {
let data: Data

guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
}

do {
    data = try Data(contentsOf: file)
} catch {
    fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}

do {
    let decoder = JSONDecoder()
    return try decoder.decode(T.self, from: data)
} catch {
    fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}


final class ImageStore {
typealias _ImageDictionary = [String: CGImage]
fileprivate var images: _ImageDictionary = [:]

fileprivate static var scale = 2

static var shared = ImageStore()

func image(name: String) -> Image {
    let index = _guaranteeImage(name: name)

    return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(verbatim: name))
}

static func loadImage(name: String) -> CGImage {
    guard
        let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
        let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
        let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
    else {
        fatalError("Couldn't load image \(name).jpg from main bundle.")
    }
    return image
}

fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
    if let index = images.index(forKey: name) { return index }

    images[name] = ImageStore.loadImage(name: name)
    return images.index(forKey: name)!
}
}

А здесь вы можете увидеть, как используется CityData:

**%% in UserData.swift file %%**

final class UserData: ObservableObject {
@Published var city = CityData
}

**%And in my view %%**

struct CityList: View {
@EnvironmentObject private var userData: UserData
@ObservedObject var SettingsVM = SettingsViewModel()

var body: some View {
    NavigationView {
        List {
            Toggle(isOn: $SettingsVM.showFavoritesOnly) {
                Text("Show Favorites Only")
            }

            ForEach(userData.city) { City in
                if !self.SettingsVM.showFavoritesOnly || City.isFavorite {
                    NavigationLink(
                        destination: ContentView(city: City)
                            .environmentObject(self.userData)
                    )
                    {
                        CityRow(city: City)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Capitals"))
    }
}
}

%% И как я меняю значение isFavorite

   Button(action: {
                self.userData.city[self.cityIndex].isFavorite.toggle();
                print(String(self.city.isFavorite));

            }) {
                if self.userData.city[self.cityIndex].isFavorite {
                    Image(systemName: "star.fill")
                        .foregroundColor(Color.yellow)
                } else {
                    Image(systemName: "star")
                        .foregroundColor(Color.gray)
                }
            }

Я пытался встроить CityData в UserDefault.standard.object, но это не сработало. Я определенно что-то не так делаю. Можешь мне помочь?

Мое приложение со списком городов и isFavorite Toggle

1 ответ

SwiftUI @Published недостаточно умен для того, что вы хотите делать.

Ты используешь @Publishedвместе с массивом. Но вы не назначаете массив позже, вы просто получаете доступ к его элементу и изменяете его.@Publishedникогда не видит изменения, так как не было присвоения опубликованной переменной. Таким образом, повторный рендеринг не запускается.

Вызов self.userData.objectWillChange.send() прежде, чем вы измените элемент массива, чтобы вызвать повторную визуализацию.

Другие вопросы по тегам