我正在开发一个遵循 MVVM 模式的 SwiftUI 应用程序。我有一个
ProfileDataManager
,其中包含用户配置文件的核心数据模型。这是我正在使用的结构:
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)
}
}
我的
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)
}
}
}
}
我将核心数据模型
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
中,甚至在单击“保存”之前也是如此。当然,如果用户点击“取消”,它就会被丢弃。但属性仍然绑定到 UserProfile
。这种行为是违反直觉的,因为编辑页面不应该有权访问它。我希望仅在保存后应用更改。取消应该关闭视图而不修改 UserProfile
。我这样说是因为取消而不修改 EditView
中的任何内容可能会影响 UserView
数据!
我尝试将
UserProfile
的副本传递给 EditView
,但这导致了严重的内存和 CPU 使用问题。我通过对 NSManagedObject
进行扩展clone() 来做到这一点。将 UserProfile
克隆到 UserView
中,并将克隆的 UserProfile
传递给 EditView
。
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
}
}
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()
}
.sheet(isPresented: $showEditProfile) {
if let clonedProfile = viewModel.cloneUserProfile() {
EditView(userProfile: clonedProfile) { modifiedProfile in
viewModel.saveProfile(modifiedProfile)
}
}
}
trailing: Button("Save") {
onSave(viewModel.userProfile)
presentationMode.wrappedValue.dismiss()
}
但是,95% 的 CPU 使用率和 40GB 内存使用率...
这是我如何在模态视图中使用临时数据的示例,例如性别选择器:
struct GenderModalView: View {
@Environment(\.presentationMode) var presentationMode
@State private var tempGender: String
@Binding var gender: String?
init(gender: Binding<String?>) {
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
中应该如何处理数据以避免模型过早改变?任何有关数据管理、责任分离或性能优化的建议将不胜感激!我正在尝试遵循最佳实践。
我的主要问题是:如何将
UserProfile
数据传递到编辑(编辑更改不直接影响存储)进行操作,以及当我“保存”时,将新数据发送回以在中更新它UserProfile
以简单有效的方式同时遵循最佳实践?
展示如何使用局部变量的编辑视图并在点击保存按钮时调用 saveAction 来更新用户配置文件的示例。
import SwiftUI
struct UserProfile {
var pseudo: String
var gender: String?
static let defaultUser = UserProfile(pseudo: "pseudo", gender: "Male")
}
class UserViewModel: ObservableObject {
@Published var userProfile: UserProfile
init(userProfile: UserProfile) {
self.userProfile = userProfile
}
// The view will call this action to save the user profile in the database
func saveUserProfile(pseudo: String, gender: String?) {
userProfile.pseudo = pseudo
userProfile.gender = gender
// Save the user profile in database
// ...
}
}
struct UserView: View {
@StateObject private var viewModel: UserViewModel
@State private var showEditProfile = false
init(userProfile: UserProfile) {
self._viewModel = StateObject(wrappedValue: UserViewModel(userProfile: userProfile))
self.showEditProfile = showEditProfile
}
var body: some View {
// ...
Text("User pseudo = \(viewModel.userProfile.pseudo)")
Text("User gender = \(viewModel.userProfile.gender ?? "nogender")")
Button("Edit Profile") {
showEditProfile = true
}
.sheet(isPresented: $showEditProfile) {
EditView(userProfile: viewModel.userProfile, saveAction: viewModel.saveUserProfile(pseudo:gender:))
}
}
}
struct EditView: View {
@Environment(\.presentationMode) var presentationMode
@State private var showImagePicker = false
@State private var showGenderModal = false
// Following properties are modified by the view
@State var pseudo: String = ""
@State var gender: String? = nil
// the save action will really save the modification
var saveAction: (String, String?)->Void
init(userProfile: UserProfile, saveAction: @escaping (String, String?)->Void) {
pseudo = userProfile.pseudo
gender = userProfile.gender
self.saveAction = saveAction
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Personal Info")) {
TextField("Pseudo", text: $pseudo)
Button("Gender \(gender ?? "no gender")") {
showGenderModal = true
}
.sheet(isPresented: $showGenderModal) {
GenderModalView(gender: $gender)
}
}
}
.navigationBarTitle("Edit Profile", displayMode: .inline)
.navigationBarItems(
leading: Button("Cancel") {
presentationMode.wrappedValue.dismiss()
},
trailing: Button("Save") {
// This where you call the saveAction that will update the user profile in the calling view
saveAction(pseudo, gender)
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
struct GenderModalView: View {
@Environment(\.presentationMode) var presentationMode
@State var tempGender: String? = nil
@Binding var gender: String?
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()
}
)
}
}
}
#Preview("User view") {
UserView(userProfile: .defaultUser)
}
注意:我没有放更新数据库代码,而是写在应该放的地方