将值从 viewcontroller 传递给 viewmodel 的更好方法
Which would be the better way to pass values to viewmodel from viewcontroller
这个问题可能看起来很基础,但我发布它是为了获得建议。
以下是使用 MVVM 模式的示例登录模块。
viewcontroller代码如下
class ViewController: UIViewController {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
//CASE 1
loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
//CASE 2
//loginviewmodel.performLogin()
}
override func viewDidLoad() {
super.viewDidLoad()
textFieldUserName.delegate = self
textFieldPassword.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inputText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
switch textField {
case textFieldUserName:
loginviewmodel.updateUsername(inputText: inputText)
case textFieldPassword:
loginviewmodel.updatePassword(inputText: inputText)
default:
return false
}
return true
}
}
而viewmodel代码如下
class LoginViewModel {
var userName: String?
var password: String?
func updateUsername(inputText: String) {
self.userName = inputText
}
func updatePassword(inputText: String) {
self.password = inputText
}
func performLogin() {
print("Login successful with username = \(userName) and password = \(password).")
}
func performLogin(name: String, pwd: String) {
print("Login successful with username = \(name) and password = \(pwd).")
}
}
我有两种情况,其中值从 viewcontroller 传递到视图模型的方式不同。
第一种直接将文本作为函数参数传递的情况
第二种情况,通过文本委托方式传递文本
这里首选哪种方式?
我更喜欢第二种情况
loginviewmodel.performLogin()
通过使用委托,您还可以在用户输入时验证两个文本字段中的输入,而不是等待用户输入错误的数据然后验证
请使用这个:
class ViewController: UIViewController {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
//CASE 1
// Check validation textfield is empty or not
loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
}
override func viewDidLoad() {
super.viewDidLoad()
textFieldUserName.delegate = self
textFieldPassword.delegate = self
}
}
class LoginViewModel {
var userName: String?
var password: String?
func performLogin(name: String, pwd: String) {
print("Login successful with username = \(name) and password = \(pwd).")
}
}
首选的一种是使用delegate method
程序员工作的一个常见部分是保持 UI 状态与模型状态同步。和用户输入。当用户与屏幕交互时。这种互动应该立即反映出来 if it useful for user
不要等到他再采取行动 press submit Button
让我们解释一下
假设您的屏幕有 2 个输入,用户名或电子邮件和密码作为 TextFields
一键登录
- 您不需要用户在输入用户名、密码之前按下登录按钮
- 您需要通知viewController用户输入数据,现在他可以提交
您需要在允许提交之前对用户输入数据进行一些验证。例如需要验证密码和电子邮件验证的字符数...等等
没有委托的第一种方法
不会进行任何验证,只是我们会在用户按下登录后通知用户错误消息,登录视图模型在用户输入时没有任何关于 ViewController 的信息,viewModel 仅在用户按下登录按钮时知道信息
Delegate 的第二种方法
在这种方法中,LoginViewModel 现在知道用户在按下登录按钮之前输入了什么,我们可以执行一些验证以启用或禁用 loginButton
ViewController:
class ViewController: UIViewController,LoginViewModelViewDelegate {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
loginviewmodel.performLogin()
}
override func viewDidLoad() {
super.viewDidLoad()
// delegate to allow ViewModel notify his view
loginviewmodel.viewDelegate = self
self.textFieldUserName.addTarget(self, action: #selector(userNameFieldDidChange(_:)), for: UIControlEvents.editingChanged)
self.textFieldPassword.addTarget(self, action: #selector(passwordFieldDidChange(_:)), for: UIControlEvents.editingChanged)
}
// MARK: - user Input notification
@objc func userNameFieldDidChange(_ textField: UITextField)
{
if let text = textField.text {
loginviewmodel.userName = text
}
}
@objc func passwordFieldDidChange(_ textField: UITextField)
{
if let text = textField.text {
loginviewmodel.password = text
}
}
// MARK: - LoginViewModel Delegate
func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool) {
// Enable or disable login button to allow user to submit input
}
}
ViewModel:
import Foundation
protocol LoginViewModelViewDelegate: class
{
func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool)
}
class LoginViewModel {
weak var viewDelegate: LoginViewModelViewDelegate?
fileprivate var passwordIsValidFormat: Bool = false
fileprivate var userNameIsValidFormat: Bool = false
/// Submit
var canSubmit: Bool {
return userNameIsValidFormat && passwordIsValidFormat
}
/// Email
var userName: String = "" {
didSet {
if oldValue != userName {
let oldCanSubmit = canSubmit
userNameIsValidFormat = validateUserNameAsEmailFormat(userName)
if canSubmit != oldCanSubmit {
viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
}
}
}
}
/// Password
var password: String = "" {
didSet {
if oldValue != password {
let oldCanSubmit = canSubmit
passwordIsValidFormat = validatePasswordFormat(password)
if canSubmit != oldCanSubmit {
viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
}
}
}
}
func performLogin() {
// perform Login and you can add anather delegate to notify View with error Message of login thow error
}
fileprivate func validateUserNameAsEmailFormat(_ userName: String) -> Bool
{
let REGEX: String
REGEX = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,32}"
return NSPredicate(format: "SELF MATCHES %@", REGEX).evaluate(with: userName)
}
/// Validate password is at least 6 characters
fileprivate func validatePasswordFormat(_ password: String) -> Bool
{
let trimmedString = password.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return trimmedString.count > 8
}
}
这个问题可能看起来很基础,但我发布它是为了获得建议。
以下是使用 MVVM 模式的示例登录模块。
viewcontroller代码如下
class ViewController: UIViewController {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
//CASE 1
loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
//CASE 2
//loginviewmodel.performLogin()
}
override func viewDidLoad() {
super.viewDidLoad()
textFieldUserName.delegate = self
textFieldPassword.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inputText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
switch textField {
case textFieldUserName:
loginviewmodel.updateUsername(inputText: inputText)
case textFieldPassword:
loginviewmodel.updatePassword(inputText: inputText)
default:
return false
}
return true
}
}
而viewmodel代码如下
class LoginViewModel {
var userName: String?
var password: String?
func updateUsername(inputText: String) {
self.userName = inputText
}
func updatePassword(inputText: String) {
self.password = inputText
}
func performLogin() {
print("Login successful with username = \(userName) and password = \(password).")
}
func performLogin(name: String, pwd: String) {
print("Login successful with username = \(name) and password = \(pwd).")
}
}
我有两种情况,其中值从 viewcontroller 传递到视图模型的方式不同。
第一种直接将文本作为函数参数传递的情况
第二种情况,通过文本委托方式传递文本
这里首选哪种方式?
我更喜欢第二种情况
loginviewmodel.performLogin()
通过使用委托,您还可以在用户输入时验证两个文本字段中的输入,而不是等待用户输入错误的数据然后验证
请使用这个:
class ViewController: UIViewController {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
//CASE 1
// Check validation textfield is empty or not
loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
}
override func viewDidLoad() {
super.viewDidLoad()
textFieldUserName.delegate = self
textFieldPassword.delegate = self
}
}
class LoginViewModel {
var userName: String?
var password: String?
func performLogin(name: String, pwd: String) {
print("Login successful with username = \(name) and password = \(pwd).")
}
}
首选的一种是使用delegate method
程序员工作的一个常见部分是保持 UI 状态与模型状态同步。和用户输入。当用户与屏幕交互时。这种互动应该立即反映出来 if it useful for user
不要等到他再采取行动 press submit Button
让我们解释一下 假设您的屏幕有 2 个输入,用户名或电子邮件和密码作为 TextFields
一键登录
- 您不需要用户在输入用户名、密码之前按下登录按钮
- 您需要通知viewController用户输入数据,现在他可以提交
您需要在允许提交之前对用户输入数据进行一些验证。例如需要验证密码和电子邮件验证的字符数...等等
没有委托的第一种方法
不会进行任何验证,只是我们会在用户按下登录后通知用户错误消息,登录视图模型在用户输入时没有任何关于 ViewController 的信息,viewModel 仅在用户按下登录按钮时知道信息
Delegate 的第二种方法
在这种方法中,LoginViewModel 现在知道用户在按下登录按钮之前输入了什么,我们可以执行一些验证以启用或禁用 loginButton
ViewController:
class ViewController: UIViewController,LoginViewModelViewDelegate {
private var loginviewmodel = LoginViewModel()
@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBAction func signIn(_ sender: Any) {
loginviewmodel.performLogin()
}
override func viewDidLoad() {
super.viewDidLoad()
// delegate to allow ViewModel notify his view
loginviewmodel.viewDelegate = self
self.textFieldUserName.addTarget(self, action: #selector(userNameFieldDidChange(_:)), for: UIControlEvents.editingChanged)
self.textFieldPassword.addTarget(self, action: #selector(passwordFieldDidChange(_:)), for: UIControlEvents.editingChanged)
}
// MARK: - user Input notification
@objc func userNameFieldDidChange(_ textField: UITextField)
{
if let text = textField.text {
loginviewmodel.userName = text
}
}
@objc func passwordFieldDidChange(_ textField: UITextField)
{
if let text = textField.text {
loginviewmodel.password = text
}
}
// MARK: - LoginViewModel Delegate
func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool) {
// Enable or disable login button to allow user to submit input
}
}
ViewModel:
import Foundation
protocol LoginViewModelViewDelegate: class
{
func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool)
}
class LoginViewModel {
weak var viewDelegate: LoginViewModelViewDelegate?
fileprivate var passwordIsValidFormat: Bool = false
fileprivate var userNameIsValidFormat: Bool = false
/// Submit
var canSubmit: Bool {
return userNameIsValidFormat && passwordIsValidFormat
}
/// Email
var userName: String = "" {
didSet {
if oldValue != userName {
let oldCanSubmit = canSubmit
userNameIsValidFormat = validateUserNameAsEmailFormat(userName)
if canSubmit != oldCanSubmit {
viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
}
}
}
}
/// Password
var password: String = "" {
didSet {
if oldValue != password {
let oldCanSubmit = canSubmit
passwordIsValidFormat = validatePasswordFormat(password)
if canSubmit != oldCanSubmit {
viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
}
}
}
}
func performLogin() {
// perform Login and you can add anather delegate to notify View with error Message of login thow error
}
fileprivate func validateUserNameAsEmailFormat(_ userName: String) -> Bool
{
let REGEX: String
REGEX = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,32}"
return NSPredicate(format: "SELF MATCHES %@", REGEX).evaluate(with: userName)
}
/// Validate password is at least 6 characters
fileprivate func validatePasswordFormat(_ password: String) -> Bool
{
let trimmedString = password.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return trimmedString.count > 8
}
}