Programming

มาลองเขียนแอพ iOS ด้วย MVC Pattern

23 ตุลาคม 20234 นาที
0
มาลองเขียนแอพ iOS ด้วย MVC Pattern
Table of Contents

Model

Model คือส่วนของ Data Layer ใช้สำหรับกำหนด Structure ของข้อมูล จะเห็นการใช้ struct อยู่บ่อยๆเพื่อกำหนดในส่วนนี้ ตัวอย่างเช่น UserModel ก็จะมีการกำหนดโครงสร้างของข้อมูลคร่าวประมาณนี้

Swift Logo
struct UserModel { let firstName: String let lastName: String let email: String }

View

View คือส่วนที่แสดงผล เป็นส่วนที่คนใช้งานแอพเรามองเห็น และเมื่อมี events ต่างๆ ก็จะถูกส่งไปที่ Controller จากนั้น Controller ก็จะเป็นคนจัดการว่า เพื่อเกิด event นี้จะให้ทำอะไรต่อ

Controller

Controller เป็นตัวกลางระหว่าง View กับ Model ที่คอยคุมการทำงานทั้งหมดของแอพ อย่างเช่น user กดปุ่มนี้ จะให้ทำอะไรต่อ ตรงนี้แหละเป็นหน้าที่ของ Controller ทั้งหมด เรียกได้ว่าแทบจะทุก events ที่ user มีการกระทำ

Workflow

ลองมาดูรูปเพื่อความเข้าใจมากขึ้น

Model-View-Controller design pattern

Implement with App

หลังจากที่ได้รู้หลักการไปแล้ว ลองมาใช้กับแอพดูบ้าง โดยแอพที่จะทำก็เป็นแอพง่ายๆ คือจำลองการ login โดยตัวแอพจะใช้ UIKit และไม่ใช้ Storyboard โดยโครงสร้างของ Project จะเป็นประมาณนี้

├── Controller │   ├── HomeViewController.swift │   └── LoginViewController.swift ├── Model │   └── UserModel.swift ├── Service │   └── NetworkService.swift └── View ├── Home.swift └── LoginView.swift

Model

มาดูในส่วนของ Model กันก่อนเลย โดยในส่วนของโครงสร้างข้อมูล ก็จะกำหนดไว้ง่ายๆ ประมาณนี้

Swift Logo
import Foundation struct User { let firstName: String let lastname: String let email: String }

View

ในส่วน View ก่อน โดยจะมี 2 ส่วน คือ

  • LoginView หน้าจอสำหรับ Login
  • HomeView คือส่วนที่แสดงผลหลังจาก Login

LoginView

ถ้าถามว่าใช้ Storyboard ได้ไหม ตอบเลยว่าได้ แต่ผมไม่ใช้ เพราะไม่ชอบลากวาง ชอบเขียน code มากกว่า 😅

Swift Logo
import UIKit import UIKit class LoginView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } lazy var emailField: UITextField = { let field = UITextField(); field.layer.borderWidth = 0.5 field.layer.cornerRadius = 6 field.placeholder = "Email" field.keyboardType = .emailAddress field.autocapitalizationType = .none let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: field.frame.size.height)) field.leftView = paddingView field.leftViewMode = .always return field }() lazy var passwordField: UITextField = { let field = UITextField() field.layer.borderWidth = 0.5 field.layer.cornerRadius = 6 field.placeholder = "Password" field.keyboardType = .default field.isSecureTextEntry = true field.autocapitalizationType = .none let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: field.frame.size.height)) field.leftView = paddingView field.leftViewMode = .always return field }() lazy var loginButton: UIButton = { let button = UIButton() button.setTitle("Login", for: .normal) button.titleLabel?.font = .systemFont(ofSize: 24, weight: .medium) button.setTitleColor(.white, for: .normal) button.backgroundColor = .systemIndigo button.alpha = 0.6 button.layer.cornerRadius = 6 button.isEnabled = false return button }() private func setupView() { backgroundColor = .systemBackground addSubview(loginButton) addSubview(emailField) addSubview(passwordField) loginButton.translatesAutoresizingMaskIntoConstraints = false emailField.translatesAutoresizingMaskIntoConstraints = false passwordField.translatesAutoresizingMaskIntoConstraints = false emailField.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true emailField.widthAnchor.constraint(equalToConstant: 200).isActive = true emailField.heightAnchor.constraint(equalToConstant: 50).isActive = true emailField.bottomAnchor.constraint(equalTo: passwordField.topAnchor,constant: -15).isActive = true passwordField.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true passwordField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true passwordField.widthAnchor.constraint(equalToConstant: 200).isActive = true passwordField.heightAnchor.constraint(equalToConstant: 50).isActive = true loginButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true loginButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 15).isActive = true loginButton.widthAnchor.constraint(equalToConstant: 100).isActive = true loginButton.heightAnchor.constraint(equalToConstant: 50).isActive = true } }

ในส่วน LoginView ก็จะมีหน้าตาประมาณนี้

LoginView

HomeView

ใน HomeView ก็ไม่มีอะไรมาก หลังจากที่ login ผ่าน ก็จะแสดงชื่อแค่นั้น

Swift Logo
import UIKit class HomeView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } lazy var welcomeLabel: UILabel = { let label = UILabel() label.text = "Welcome" label.font = .systemFont(ofSize: 32, weight: .medium) return label }() private func setupView() { backgroundColor = .systemBackground addSubview(welcomeLabel) welcomeLabel.translatesAutoresizingMaskIntoConstraints = false welcomeLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true welcomeLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true } }
LoginView

Controller

LoginViewController

Swift Logo
import UIKit class LoginViewController: UIViewController, UITextFieldDelegate { let loginView = LoginView() override func viewDidLoad() { super.viewDidLoad() view.addSubview(loginView) setupUI() loginView.emailField.delegate = self loginView.passwordField.delegate = self loginView.emailField.addTarget(self, action: #selector(validateField), for: .editingChanged) loginView.passwordField.addTarget(self, action: #selector(validateField), for: .editingChanged) } func setupUI() { loginView.translatesAutoresizingMaskIntoConstraints = false loginView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true loginView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true } func textFieldShouldReturn(_ textField: UITextField) -> Bool { if textField == loginView.emailField { loginView.passwordField.becomeFirstResponder() } else if textField == loginView.passwordField { performLogin() } return true } @objc func loginButtonTapped() { performLogin() } @objc private func validateField() { loginView.loginButton.isEnabled = loginView.emailField.text != "" && loginView.passwordField.text != "" if loginView.loginButton.isEnabled { loginView.loginButton.alpha = 1 loginView.loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside) } else { loginView.loginButton.alpha = 0.5 } } func performLogin() { let email = loginView.emailField.text ?? "" let password = loginView.passwordField.text ?? "" print("logging...") let homeVC = HomeViewController() NetworkService.share.login(email: email, password: password) { success in if success { self.present(homeVC, animated: true) self.loginView.emailField.text = "" self.loginView.passwordField.text = "" } else { let alertController = UIAlertController(title: "เข้าไม่ได้อ่ะ", message: "แน่ใจนะว่า Email กับ Password ถูก ?", preferredStyle: .alert) let tryAction = UIAlertAction(title: "ลองใหม่", style: .default) { (action) in print(action.title!) } alertController.addAction(tryAction) self.present(alertController, animated: true) } } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { self.view.endEditing(true) } }

HomeViewController

Swift Logo
import UIKit class HomeViewController: UIViewController { let homeView = HomeView() var user: User! override func viewDidLoad() { super.viewDidLoad() user = NetworkService.share.getUser() greetingUser() view.addSubview(homeView) homeView.translatesAutoresizingMaskIntoConstraints = false homeView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true homeView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true } private func greetingUser() { homeView.welcomeLabel.text = "👋 \(user.firstName) \(user.lastname)" } }

คลิกเพื่อแสดงความคิดเห็น