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

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 กันก่อนเลย โดยในส่วนของโครงสร้างข้อมูล ก็จะกำหนดไว้ง่ายๆ ประมาณนี้
import Foundation
struct User {
let firstName: String
let lastname: String
let email: String
}
View
ในส่วน View ก่อน โดยจะมี 2 ส่วน คือ
- LoginView หน้าจอสำหรับ Login
- HomeView คือส่วนที่แสดงผลหลังจาก Login
LoginView
ถ้าถามว่าใช้ Storyboard ได้ไหม ตอบเลยว่าได้ แต่ผมไม่ใช้ เพราะไม่ชอบลากวาง ชอบเขียน code มากกว่า 😅
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 ก็จะมีหน้าตาประมาณนี้

HomeView
ใน HomeView ก็ไม่มีอะไรมาก หลังจากที่ login ผ่าน ก็จะแสดงชื่อแค่นั้น
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
}
}

Controller
LoginViewController
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
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)"
}
}