I am growing a SwiftUI app following the MVVM sample. I’ve a ProfileDataManager
that holds a Core Information mannequin for the person profile. Right here’s the construction I’m utilizing:
Person ViewModel and Person View
The UserViewModel
makes use of ProfileDataManager
to fetch and supply the person’s profile information:
class UserViewModel: ObservableObject {
@Printed var userProfile: UserProfile?
non-public var dataManager: ProfileDataManager
init(dataManager: ProfileDataManager = .shared) {
self.dataManager = dataManager
dataManager.$userProfile
.assign(to: &$userProfile)
}
}
My UserView
can entry all of the profile information:
struct UserView: View {
@StateObject non-public var viewModel = UserViewModel()
@State non-public var showEditProfile = false
var physique: some View {
// ...
Button("Edit Profile") {
showEditProfile = true
}
.sheet(isPresented: $showEditProfile) {
if let userProfile = viewModel.userProfile {
EditView(userProfile: userProfile)
}
}
}
}
I move the Core Information mannequin UserProfile
to EditView
.
Edit View and Edit ViewModel
The EditView
permits the person to edit their profile.
struct EditView: View {
@Atmosphere(.presentationMode) var presentationMode
@StateObject non-public var viewModel: EditViewModel
@State non-public var showImagePicker = false
@State non-public var showGenderModal = false
init(userProfile: UserProfile) {
_viewModel = StateObject(wrappedValue: EditViewModel(userProfile: userProfile))
}
var physique: some View {
NavigationView {
Type {
Part(header: Textual content("Avatar")) {
AvatarSection(avatarData: Binding(
get: { viewModel.userProfile.avatarData },
set: { viewModel.userProfile.avatarData = $0 }
)) {
showImagePicker = true
}
}
Part(header: Textual content("Private Data")) {
AttributeButton(title: "Gender", worth: viewModel.userProfile.gender) {
showGenderModal = true
}
.sheet(isPresented: $showGenderModal) {
GenderModalView(gender: $viewModel.userProfile.gender)
}
}
}
.navigationBarTitle("Edit Profile", displayMode: .inline)
.navigationBarItems(
main: Button("Cancel") {
viewModel.cancelChanges()
presentationMode.wrappedValue.dismiss()
},
trailing: Button("Save") {
viewModel.saveChanges()
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
Right here’s the EditViewModel
the place I deal with save and cancel performance:
class EditViewModel: ObservableObject {
@Printed var userProfile: UserProfile
non-public var dataManager: ProfileDataManager
init(userProfile: UserProfile, dataManager: ProfileDataManager = .shared) {
self.userProfile = userProfile
self.dataManager = dataManager
}
func saveChanges() {
dataManager.saveContext()
}
func cancelChanges() {
dataManager.rollback()
}
}
Drawback
Presently, modifications made in EditView
are instantly mirrored in UserProfile
, even earlier than clicking “Save.” In fact, it’s discarded if the person clicks on “Cancel”. However nonetheless, the attributes are binded to the UserProfile
. This conduct is counter-intuitive, as an Edit Web page should not have entry to that. I need the modifications to be utilized solely after saving. Canceling ought to dismiss the view with out modifying the UserProfile
. I’m saying this as a result of Canceling with out modifying something within the EditView
might have an effect on the UserView
information!
I tried to move a duplicate of UserProfile
to EditView
, however this prompted important reminiscence and CPU utilization points. I did this by making an extension clone() to NSManagedObject
. Cloning the UserProfile
in UserView
and passing the cloned UserProfile
to EditView
.
NSManagedObject:
extension NSManagedObject {
func clone() -> NSManagedObject? {
guard let context = self.managedObjectContext else { return nil }
let entityName = self.entity.title ?? ""
guard let clonedObject = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) as? Self else { return nil }
for attribute in self.entity.attributesByName {
clonedObject.setValue(self.worth(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()
}
However once more, 95% CPU Utilization and 40GB Mem Utilization…
Momentary Information for Modal Views
Right here’s an instance of how I am utilizing momentary information in modal views, just like the gender picker:
struct GenderModalView: View {
@Atmosphere(.presentationMode) var presentationMode
@State non-public var tempGender: String
@Binding var gender: String?
init(gender: Binding) {
self._gender = gender
self._tempGender = State(initialValue: gender.wrappedValue ?? "Different")
}
var physique: some View {
NavigationView {
Type {
Picker("Gender", choice: $tempGender) {
Textual content("Male").tag("Male")
Textual content("Feminine").tag("Feminine")
Textual content("Different").tag("Different")
}
.pickerStyle(.segmented)
Part {
Button("Take away Gender") {
gender = nil
presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.purple)
}
}
.navigationBarTitle("Gender", displayMode: .inline)
.navigationBarItems(
main: Button("Cancel") {
presentationMode.wrappedValue.dismiss()
},
trailing: Button("Save") {
gender = tempGender
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
Query
- How ought to information be dealt with in
EditView
to keep away from untimely modifications to the mannequin? - Am I following greatest practices in MVVM right here?
- Am I doing something silly?
Any recommendation on information administration, accountability separation, or efficiency optimizations could be vastly appreciated! I’m making an attempt to observe greatest practices.
My MAIN query is: How do I move the UserProfile
information to the Edit (with out the Edit modifications straight affecting the storage) for manipulation, and after I “Save”, ship the brand new information again to replace it in UserProfile
in a easy and environment friendly method whereas following greatest practices?