SwiftUI MVVM — обработка временных изменений в режиме редактирования без прямого изменения основных данныхIOS

Программируем под IOS
Ответить
Anonymous
 SwiftUI MVVM — обработка временных изменений в режиме редактирования без прямого изменения основных данных

Сообщение Anonymous »

Я разрабатываю приложение SwiftUI по шаблону MVVM. У меня есть ProfileDataManager, который содержит модель основных данных для профиля пользователя. Вот структура, которую я использую:
User ViewModel и User View
UserViewModel использует ProfileDataManager для выборки и предоставить данные профиля пользователя:

Код: Выделить всё

class UserViewModel: ObservableObject {
@Published var userProfile: UserProfile?
private var dataManager: ProfileDataManager

init(dataManager: ProfileDataManager = .shared) {
self.dataManager = dataManager
dataManager.$userProfile
.assign(to: &$userProfile)
}
}
My UserView может получить доступ ко всем данным профиля:

Код: Выделить всё

struct UserView: View {
@StateObject private var viewModel = UserViewModel()
@State private var showEditProfile = false

var body: some View {
// ...
Button("Edit Profile") {
showEditProfile = true
}
.sheet(isPresented: $showEditProfile) {
if let userProfile = viewModel.userProfile {
EditView(userProfile: userProfile)
}
}
}
}
Я передаю профиль пользователя базовой модели данных в EditView.
Редактировать представление и редактировать модель представленияEditView позволяет пользователю редактировать свой профиль.

Код: Выделить всё

struct EditView: View {
@Environment(\.presentationMode) var presentationMode
@StateObject private var viewModel: EditViewModel
@State private var showImagePicker = false
@State private var showGenderModal = false

init(userProfile: UserProfile) {
_viewModel = StateObject(wrappedValue: EditViewModel(userProfile: userProfile))
}

var body: some View {
NavigationView {
Form {
Section(header: Text("Avatar")) {
AvatarSection(avatarData: Binding(
get: { viewModel.userProfile.avatarData },
set: { viewModel.userProfile.avatarData = $0 }
)) {
showImagePicker = true
}
}

Section(header: Text("Personal Info")) {
AttributeButton(title: "Gender", value: viewModel.userProfile.gender) {
showGenderModal = true
}
.sheet(isPresented: $showGenderModal) {
GenderModalView(gender: $viewModel.userProfile.gender)
}
}
}
.navigationBarTitle("Edit Profile", displayMode: .inline)
.navigationBarItems(
leading: Button("Cancel") {
viewModel.cancelChanges()
presentationMode.wrappedValue.dismiss()
},
trailing: Button("Save") {
viewModel.saveChanges()
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
Вот EditViewModel, в котором я обрабатываю функции сохранения и отмены:

Код: Выделить всё

class EditViewModel: ObservableObject {
@Published var userProfile: UserProfile
private var dataManager: ProfileDataManager

init(userProfile: UserProfile, dataManager: ProfileDataManager = .shared) {
self.userProfile = userProfile
self.dataManager = dataManager
}

func saveChanges() {
dataManager.saveContext()
}

func cancelChanges() {
dataManager.rollback()
}
}
Проблема
В настоящее время изменения, внесенные в EditView, немедленно отражаются в профиле пользователя, даже до нажатия кнопки «Сохранить». Конечно, он отбрасывается, если пользователь нажимает «Отменить». Но все равно атрибуты привязаны к UserProfile. Такое поведение противоречит здравому смыслу, поскольку страница редактирования не должна иметь к нему доступа. Я хочу, чтобы изменения применялись только после сохранения. Отмена должна закрыть представление без изменения профиля пользователя. Я говорю это потому, что отмена без изменения чего-либо в EditView может повлиять на данные UserView!
Я попытался передать копию UserProfile в EditView, но это вызвало значительные проблемы с использованием памяти и процессора. Я сделал это, создав расширение clone() для NSManagedObject. Клонирование UserProfile в UserView и передача клонированного UserProfile в EditView.
NSManagedObject:

Код: Выделить всё

extension NSManagedObject {
func clone() -> NSManagedObject? {
guard let context = self.managedObjectContext else { return nil }
let entityName = self.entity.name ?? ""
guard let clonedObject = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) as? Self else { return nil }

for attribute in self.entity.attributesByName {
clonedObject.setValue(self.value(forKey: attribute.key), forKey: attribute.key)
}

return clonedObject
}
}
UserViewModel:

Код: Выделить всё

func cloneUserProfile() -> UserProfile? {
return userProfile?.clone() as? UserProfile
}

func saveProfile(_ modifiedProfile: UserProfile) {
guard let userProfile = userProfile else { return }
userProfile.setValuesForKeys(modifiedProfile.dictionaryWithValues(forKeys: Array(modifiedProfile.entity.attributesByName.keys)))
dataManager.saveContext()
}
UserView:

Код: Выделить всё

.sheet(isPresented: $showEditProfile) {
if let clonedProfile = viewModel.cloneUserProfile() {
EditView(userProfile: clonedProfile) { modifiedProfile in
viewModel.saveProfile(modifiedProfile)
}
}
}
EditView:

Код: Выделить всё

trailing: Button("Save") {
onSave(viewModel.userProfile)
presentationMode.wrappedValue.dismiss()
}
Но опять же, загрузка ЦП 95% и использование памяти 40 ГБ...
Временные данные для модальных представлений
Вот пример того, как я использую временные данные в модальных представлениях, например, средство выбора пола:

Код: Выделить всё

struct GenderModalView: View {
@Environment(\.presentationMode) var presentationMode
@State private var tempGender: String
@Binding var gender: String?

init(gender: Binding) {
self._gender = gender
self._tempGender = State(initialValue: gender.wrappedValue ?? "Other")
}

var body: some View {
NavigationView {
Form {
Picker("Gender", selection: $tempGender) {
Text("Male").tag("Male")
Text("Female").tag("Female")
Text("Other").tag("Other")
}
.pickerStyle(.segmented)

Section {
Button("Remove Gender") {
gender = nil
presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.red)
}
}
.navigationBarTitle("Gender", displayMode: .inline)
.navigationBarItems(
leading: Button("Cancel") {
presentationMode.wrappedValue.dismiss()
},
trailing: Button("Save") {
gender = tempGender
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
Вопрос
  • Как следует обрабатывать данные в EditView, чтобы избежать преждевременных изменений в модели?< /li>
    Следую ли я здесь передовому опыту в MVVM?
  • Делаю ли я какую-нибудь глупость?
Будем очень признательны за любые советы по управлению данными, разделению ответственности или оптимизации производительности! Я пытаюсь следовать лучшим практикам.
Мой ГЛАВНЫЙ вопрос: как передать данные UserProfile в Edit (без изменений Edit, непосредственно влияющих на хранилище) для манипуляций , и когда я «сохраняю», отправляю новые данные обратно, чтобы обновить их в UserProfile простым и эффективным способом, следуя рекомендациям?

Подробнее здесь: https://stackoverflow.com/questions/791 ... y-modifyin
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»