Post

Replies

Boosts

Views

Activity

Reply to Stop using MVVM for SwiftUI
Model layer class MyWebService {     static let shared = MyWebService()          // URLSession instance / configuration     // Environments (dev, qa, prod, test / mocks)     // Requests and token management          var token: String? = nil          func request<T>(/* path, method, query, payload*/) async throws -> T { ... }          func requestAccess(username: String, password: String) async throws { ... }     func revokeAccess() async throws { ... } } // Other names: User, UserStore, CurrentUser, ... class Account: ObservableObject {     @Published var isLogged: Bool = false     @Published var error: Error? = nil          struct Profile {         let name: String         let avatar: URL?     }          @Published var profile: Profile? = nil          enum SignInError: Error {         case mandatoryEmail         case invalidEmail         case mandatoryPassword         case userNotFound         case userActivationNeeded     }          func signIn(email: String, password: String) async {         // Validation         if email.isEmpty {             error = SignInError.mandatoryEmail             return         } else if email.isEmail {             error = SignInError.invalidEmail             return         }                  if password.isEmpty {             error = SignInError.mandatoryPassword             return         }                  // Submit         do {             try await MyWebService.shared.requestAccess(username: email,                                                         password: password)             isLogged = true             error = nil         } catch HTTPError.forbidden {             error = SignInError.userActivationNeeded         } catch HTTPError.notFound {             error = SignInError.userNotFound         } catch {             self.error = error         }     }          func signOut() async {         // Ignore any error         try? await MyWebService.shared.revokeAccess()         isLogged = false     }          func loadProfile() async {         do {             profile = try await MyWebService.shared.request(...)             error = nil         } catch {             self.error = error         }     } } View layer @main struct MyApp: App {     @StateObject var account = Account()          var body: some Scene {         WindowGroup {             Group {                 if account.isLogged {                     TabView { ... }                         .task {                             async account.loadProfile()                         }                 } else {                     SignInView()                 }             }             .environmentObject(account)         }     } } struct SignInView: View {     @EnvironmentObject var account: Account          @State private var email: String = ""     @State private var password: String = ""          // Text fields, button, error exception     var body: some View {         ScrollView { ... }     }          func signIn() {         Task {             await account.signIn(email: email,                                  password: password)         }     } }
Aug ’22
Reply to Stop using MVVM for SwiftUI
Again, you don’t need a middle layer, just your model! Everything becomes easy, composable and flexible. And yes! You can do Unit / Integration tests. UI = f(State) => View = f(Model) SignIn // State (shared in view hierarchy), part of your model class Account: ObservableObject {     @Published var isLogged: Bool = false     func signIn(email: String, password: String) async throws { ... } } // View struct SignInView: View {     @EnvironmentObject private var account: Account     @State private var email: String     @State private var password: String     // Focus, error state     var body: some View { ... }     // Call from button     func signIn() {         // Validation (can be on model side)         // Change error state if needed         Task {             do {                 try await account.signIn(email: email,                                          password: password)             } catch {                 // Change error state             }         }     } } SignUp // Data (Active Record), part of model, can be an ObservableObject if needed struct Registration: Codable {     var name: String = ""     var email: String = ""     var password: String = ""     var carBrand: String? = nil          func signUp() async throws { ... } } // State (Store), part of model class CarBrandStore: ObservableObject {     @Published var brands: [String] = []          // Phase (loading, loaded, empty, error)     func load() async { ... } } // View struct SignUpView: View {     @State private var registration = Registration()     @StateObject private var carBrandStore = CarBrandStore()          // Focus, error state          var body: some View { ... }     // Call from button     func signUp() {         // Validation (can be on model side)         // Change error state if needed         Task {             do {                 try await registration.signUp()             } catch {                 // Change error state             }         }     } }
Aug ’22
Reply to Stop using MVVM for SwiftUI
The model is composed by Data Objects (structs), Service Objects (providers, shared classes) and State Objects (observable objects, “life cycle” / “data projection” classes). We should use the state objects for specific (or related) data and functionality, not for screen / view, as they should be independent from specific UI structure / needs, When needed we can share then in view hierarchy. Remember (using state objects in views): StateObject - strong reference, single source of truth EnvironmentObject / ObservedObject - weak reference Also (when async calls needed): Define state objects (or base class) as MainActor to avoid warnings and concurrency problems Define state object tasks as async, e.g “func load() async”, because for some situations you need to do other jobs after data load completed and async is more simple / sequencial than checking the “phase” (.loaded) using onChange(of:) Big apps can have more models. This is just an example:
Jul ’22
Reply to Stop using MVVM for SwiftUI
I've seen a lot of questions about MVVM (in general and about testability) unnecessary limitations & problems in WWDC22 SwiftUI digital lounge. I think developers should start understand & think SwiftUI, instead another platform's strategy. Apple patterns has been used very successfully by Cocoa developers for decades. It just works, and you don't have to fight it. Jumping into more complicated patterns is not a good approach for developers. Your self, your team, your company and your users will thank you!
Jun ’22
Reply to Stop using MVVM for SwiftUI
Remember: Avoid sacrificing software design for testability Automated tests didn’t ensure a quality product Last years I see a correlation between the increased reliance on automated testing and the decline in quality of software. A real project that failed and I fixed, one of many problematic projects! Companies must be careful with devs obsessed with SOLID principles and testability. In 20 years of software engineering I've never seen soo bad & inefficient developers than in last years. In case of iOS there are very bad developers that come from Web or Android. They fight the system with other platforms concepts and testability talk.
Jun ’22
Reply to Stop using MVVM for SwiftUI
Sorry, the examples are not testable? Not clean? Today developers are obsessed with tests and other ugly thinks. I see projects delayed with lots problems because devs today want to be SOLID and testable. Agile, SOLID and over testing works like a cancer for development teams. Sorry but if people want to be complicated they should move to Android or other platforms.
Jun ’22
Reply to NavigationStack suggestions
Another suggestion. Example from docs: NavigationStack { List(parks) { park in NavigationLink(park.name, value: park) } .navigationDestination(for: Park.self) { park in ParkDetails(park: park) } } In this case, we are duplicating the “park” data because that park is already in the stack. The navigation stack contains the park and we are making an unnecessary copy for ParkDetails view. Why not to use the @Environment to get the pushed value (in this case the park) on ParkDetail? Every pushed View most have the corresponding value available in the @Environment. Before: struct ParkDetail: View { let park: Park } After: struct ParkDetail: View { @Environment(\.pushedData) var park: Park } Also we can do the same for other cases like modals: struct ParkPreview: View { @Environment(\.presentedData) var park: Park }
Jun ’22
Reply to NavigationStack suggestions
Another suggestion. In many cases we don’t use the NavigationLink because we have a custom UI and can’t change de pressed style. Will be great to have a “NavigationLinkStyle” (with isPressed) or a NavigationButton or data-drive push capabilities for the Button. In fact Button is used for push all the time.
Jun ’22