I am trying to implement MVVM Architecture pattern using Boxing. I have done it simply by Adding the Boxing Class:
class Dynamic<T> {
typealias Listener = (T) -> Void
var listener: Listener?
func bind(listener: Listener?) {
self.listener = listener
}
func bindAndFire(listener: Listener?) {
self.listener = listener
listener?(value)
}
var value: T {
didSet {
listener?(value)
}
}
init(_ v: T) {
value = v
}}
And then In the ViewController I have referenced a ViewModel, this is my View Controller:
class SignUpViewController: UIViewController {
// UI Outlets
#IBOutlet weak var emailLoginTextField: FloatLabelTextField!
#IBOutlet weak var passwordLoginTextField: FloatLabelTextField!
var viewModel = AuthenticationViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.user.email.bind{
self.emailLoginTextField.text = $0
}
}}
And This is my View Model:
class AuthenticationViewModel{
let defaults = UserDefaults.standard
let serviceManager = ServiceManager()
var user = User()
func signupUser(email : String?, password: String?){
let parameters : [String:String] = ["email":emailField, "password": password!, "system": "ios"]
serviceManager.initWithPOSTConnection(server: Utitlites.getServerName(), parameters: parameters, methodName: "/api/user/register", completion: { (responseData , errorMessage) -> Void in
let json = (responseData as AnyObject) as! JSON
print(json)
if ErrorHandling.handleErrorMessage(responseData: responseData).0 == true {
self.defaults.set("userId", forKey: json["user"]["id"].stringValue)
//self.userId.value = json["user"]["id"].stringValue
self.user = User(json: json)
}
})
}}
And this is my Model:
class User{
var id = Dynamic("")
var name = Dynamic("")
var email = Dynamic("")
init(){
}
init(json: JSON){
id.value = json["user"]["id"].stringValue
email.value = json["user"]["email"].stringValue
}}
My Question is:
MVVM Architecture wise, is it right to access the model using this line in the ViewController:
viewModel.user.email.bind{
self.emailLoginTextField.text = $0
}
Because I can see now that the View is accessing the Model which I think is not what MVVM Stands for. I need someone to clarify
The best practice to go about this (imo) and according to this raywanderlich video at 31:18 is to actually set the Model to be private, your VC doesn't need to know about it at all, only the ViewModel.
After that, set getters for the Model in the ViewModel like this:
var id: Dynamic<String> = Dynamic("")
var name: Dynamic<String> = Dynamic("")
var email: Dynamic<String> = Dynamic("")
And then, in your ViewModel also, set the User object to have a didSet notifier that will update the ViewModel's data accordingly:
private var user = User() {
didSet {
id = user.id
name = user.name
email = user.email
}
}
Now, you can access these properties only from the ViewModel instead of the Model directly:
viewModel.email.bind{
self.emailLoginTextField.text = $0
}
Oh, and don't forget to set the properties on the Model to be just regular strings ;)
Related
Most articles about MVVM describe when the model is changed, for example when new data is made available and we need to update the UI, the Model notifies the View Model.
But I don’t get how Model communicate with View Model to notify about its change.
In the code below, I used property observer to bind View and ViewModel. And I know that I can change my Model by assigning new value like self.person.value.name = name in ViewModel.
I read a lot of articles about mvvm and think I wrote an appropriate example as follows, but even in this code I cannot get the concept that Model notifies its change to ViewModel. In my code below, does model notifies viewmodel about its change? Can you explain with examples?
class Observable<T> {
var value: T {
didSet {
self.listener?(value)
}
}
var listener: ((T) -> Void)?
init(_ value: T) {
self.value = value
}
func subscribe(listener: #escaping (T) -> Void) {
listener(value)
self.listener = listener
}
}
struct Person {
var name: String
var age: Int
}
struct MyViewModel {
var person: Observable<Person>
init(person: Person) {
self.person = Observable(person)
}
func changePersonName(with name: String) {
person.value.name = name
}
}
class ViewController: UIViewController {
#IBOutlet weak var infoLabel: UILabel!
let viewModel = MyViewModel(person: Person(name: “Mike“, age: 100))
override func viewDidLoad() {
viewModel.person.subscribe { [weak self] person in
self?.infoLabel.text = person.name + “& " + "\(person.age)"
}
}
}
I have a page controller where I added UIViewControllers and display a bunch of form in each viewcontroller. The issue I am facing now is that I need to get the data supplied in each of the forms and save it which is done in the last view controller. I have tried using delegates but the moment the next button is clicked, the previous value stored becomes nil and only the value of the latest VC is displayed. How can I pass data in this textfields. Any help is appritated.
My delegate
protocol NextDelegate: AnyObject {
func next(pageIndex: Int, model: CreatePropertyModel)
func previous(pageIndex: Int, model: CreatePropertyModel)
}
how I created array of VC
lazy var controllers: [UIViewController] = {
let descVC = DescVC()
descVC.delegate = self
let priceVC = PriceVC()
priceVC.delegate = self
let featuresVC = FeaturesVC()
featuresVC.delegate = self
let picturesVC = PicturesVC()
picturesVC.delegate = self
return [descVC, priceVC, featuresVC, picturesVC]
}()
Model Example
class CreatePropertyModel: DictionaryEncodable {
var title: String?
var desc: String?
var property_type_id: Int?
var property_sub_type_id: Int?
var location_id: Int?
var currency: String?
var price: Int?
}
For all your steps, store it in a singleton.
protocol Answer {
var isDone: Bool { get }
}
class Answer1: Answer {
static public let updatedNotification = Notification.Name("Answer1Updated")
var name: String? {
didSet {
NotificationCenter.default.post(name: Answer1.updatedNotification, object: nil)
}
}
var isDone: Bool {
return name != nil
}
}
class Answer2: Answer {
var age: Int?
var isDone: Bool {
return age != nil
}
}
class Form {
static let shared = Form()
var answers: [Answer] = [Answer1(), Answer2()]
var isDone: Bool {
return answers.allSatisfy { $0.isDone == true }
}
private init() {}
func reset() {
answers = [Answer1(), Answer2()]
}
var answer1: Answer1? {
return Form.shared.answers.filter { $0 is Answer1 }.first as? Answer1
}
var answer2: Answer2? {
return Form.shared.answers.filter { $0 is Answer2 }.first as? Answer2
}
}
Then, in your view controller, read / write values like this.
class MyViewControllerForAnswer1: UIViewController {
var answer: Answer1? {
return Form.shared.answer1
}
var name: String? {
get {
return answer?.name
}
set {
answer?.name = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(answerUpdated(notification:)), name: Answer1.updatedNotification, object: nil)
}
#objc func answerUpdated(notification: Notification) {
// Update your content
}
}
I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.
Here's a User model class. This model will be container for data while registering new user, logging an already registered user and displaying profile.
struct User {
typealias message = (Bool,String)
var name: String?
var username: String
var password: String
var image: String?
func isValidForLogin() -> message {
let emailMessage = isValidEmail(testStr: username)
let passwordMessage = isValidPassowrd(testStr: password)
if emailMessage.0 && passwordMessage.0 {
return (true,"Valid")
}
if !emailMessage.0{
return (emailMessage.0, emailMessage.1)
}else{
return (passwordMessage.0, passwordMessage.1)
}
}
func isValidForRegister() -> message {
if let name = self.name{
let nameMessage = isValidName(testStr: name)
let emailMessage = isValidEmail(testStr: username)
let passwordMessage = isValidPassowrd(testStr: password)
if emailMessage.0 && passwordMessage.0 && nameMessage.0{
return (true,"Valid")
}
if !emailMessage.0{
return (emailMessage.0, emailMessage.1)
}else if !passwordMessage.0{
return (passwordMessage.0, passwordMessage.1)
}else{
return (nameMessage.0, nameMessage.1)
}
}
return (false, "Name " + Constants.emptyField)
}
private func isValidName(testStr: String) -> message{
if testStr.isEmpty{
return (false, "Name " + Constants.emptyField )
}
return (true, "Valid")
}
private func isValidPassowrd(testStr: String) -> (Bool, String) {
if testStr.isEmpty{
return (false, "Password " + Constants.emptyField )
}
if testStr.count > 6{
return (true, "Valid")
}
return (false, Constants.invalidPassword)
}
private func isValidEmail(testStr: String) -> message {
if testStr.isEmpty{
return (false, "Email " + Constants.emptyField)
}
let emailRegEx = "^(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?(?:(?:(?:[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+(?:\\.[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+)*)|(?:\"(?:(?:(?:(?: )*(?:(?:[!#-Z^-~]|\\[|\\])|(?:\\\\(?:\\t|[ -~]))))+(?: )*)|(?: )+)\"))(?:#)(?:(?:(?:[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)(?:\\.[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)*)|(?:\\[(?:(?:(?:(?:(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))\\.){3}(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))))|(?:(?:(?: )*[!-Z^-~])*(?: )*)|(?:[Vv][0-9A-Fa-f]+\\.[-A-Za-z0-9._~!$&'()*+,;=:]+))\\])))(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?$"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluate(with: testStr)
if result{
return (result, "Valid")
}else{
return (result, Constants.invalidEmail)
}
}
}
I am trying to follow MVVM pattern. So, my ViewModel class for RegisterViewViewModel:
struct RegisterViewModel {
private let minUserNameLength = 4
private let minPasswordLength = 6
var name: String
var email: String
var password: String
private var userModel: User{
return User(name: name, username: email, password: password, image: "")
}
func isValid() -> (Bool, String) {
return userModel.isValidForRegister()
}
func register(){
....
}
}
And in my RegisterViewController :
class RegisterViewController: UIViewController{
#IBOutlet weak var txtName: UITextField!
#IBOutlet weak var txtUsername: UITextField!
#IBOutlet weak var txtPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnSignUpPressed(_ sender: UIButton) {
if let name = txtName.text, let email = txtUsername.text, let password = txtPassword.text{
let userModel = RegisterViewModel(name: name, email: email, password: password)
let validate = userModel.isValid()
if validate.0{
userModel.register()
}else{
//do error handling here
print(validate.1)
}
}
}
}
Am I going in right direction? Any suggestion will be appreciated.
I would recommend you to use RxSwift with MVVM. Also you could export validation to a separate ValidationService class. Otherwise you will probably have to copy same validation methods between different models.
enum ValidationResult {
case ok
case empty
case validating
case failed(message: String)
}
extension ValidationResult {
var isValid: Bool {
switch self {
case .ok:
return true
default:
return false
}
}
var isEmpty: Bool {
switch self {
case .empty:
return true
default:
return false
}
}
}
class ValidationService {
let minPasswordCount = 4
static let shared = ValidationService()
func validateName(_ name: String) -> Observable<ValidationResult> {
if name.isEmpty {
return .just(.empty)
}
if name.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return .just(.failed(message: "Invalid name"))
}
return .just(.ok)
}
}
What you are trying to do is not MVVM pattern.
You are creating a new ViewModel when button is clicked. It is the same as you are creating a business class to handle some business logics.
ViewModel and View are communicating through data binding. If you are familiar with RxSwift, the I suggest to use this library: https://github.com/duyduong/DDMvvm
I wrote this library after using it a lot on private projects. There are examples for you to start and understand how MVVM works. Give it a try!
To implement MVVM in iOS we can use a simple combination of Closure and didSet to avoid third-party dependencies.
public final class Observable<Value> {
private var closure: ((Value) -> ())?
public var value: Value {
didSet { closure?(value) }
}
public init(_ value: Value) {
self.value = value
}
public func observe(_ closure: #escaping (Value) -> Void) {
self.closure = closure
closure(value)
}
}
An example of data binding from ViewController:
final class ExampleViewController: UIViewController {
private func bind(to viewModel: ViewModel) {
viewModel.items.observe(on: self) { [weak self] items in
self?.tableViewController?.items = items
// self?.tableViewController?.items = viewModel.items.value // This would be Momory leak. You can access viewModel only with self?.viewModel
}
// Or in one line:
viewModel.items.observe(on: self) { [weak self] in self?.tableViewController?.items = $0 }
}
override func viewDidLoad() {
super.viewDidLoad()
bind(to: viewModel)
viewModel.viewDidLoad()
}
}
protocol ViewModelInput {
func viewDidLoad()
}
protocol ViewModelOutput {
var items: Observable<[ItemViewModel]> { get }
}
protocol ViewModel: ViewModelInput, ViewModelOutput {}
final class DefaultViewModel: ViewModel {
let items: Observable<[ItemViewModel]> = Observable([])
// Implmentation details...
}
Later it can be replaced with SwiftUI and Combine (when a minimum iOS version in of your app is 13)
In this article, there is a more detailed description of MVVM
https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
class RegisterViewController: UIViewController {
var user = User() {
didSet {
// update UI
}
}
}
Most MVVM/RxSwift developers don't understand the notion of "over-engineering", as can be seen from all previous answers. Two of them refer you to a even more complicated design pattern, and one of them built the said pattern from scratch.
You don't need any of the RxSwift nonsense. MVVM isn't about having an object called view model and shoving everything to it.
Build a model so that when it changes, it updates associated view.
Simple, as all things should be.
Below is the pinnacle of over-engineering
protocol ViewModel: ViewModelInput, ViewModelOutput {}
After you define all these, write them down, train colleagues, draw diagrams, and implement them, you would've realized that it's all boilerplate and you should just drop them.
I'm currently doing a login system test, I'd like to log in the accountTextfield of the string stored in the Account I built the class, so I can use in other controllers, this method should be how to achieve, can anyone help me, thank you
Here is class of Account
class Account {
var id:String?
init(id:String)
self.id = id
}
And here is my LoginViewController
#IBOutlet weak var accountTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBAction func DoLogin(_ sender: AnyObject) {
login_now(username:accountTextField.text!,password:passwordTextField.text!)
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
let url = URL(string: "http://163.18.22.78/api/Login")!
.
.
.
}
class Account: NSObject {
var id:String?
}
class Apimanager: NSObject {
static let instance = Apimanager()
var user :Account = Account()
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
Apimanager.instance.user.id = accountTextField.text!
print(Apimanager.instance.user.id!)
}
In another view controller
Print(Apimanager.instance.user.id!
if you want use init() method in Account class then you can use
UserDefaults.standard.set(accountTextField.text!, forKey: "account")
UserDefaults.standard.synchronize()
to store particular value and to get value in another view controller you can use
print(UserDefaults.standard.value(forKey: "account"))..
note that both key value should be match for setting and getting value from UserDefaults..hope it work for you!!