Chào các bạn, chúng ta đều biết rằng, công cụ Xcode có thể giúp chúng ta có thể thiết kế giao diện trực quan và nhanh chóng, khi đó chúng ta dùng storyboard hay xib, nhưng nhiều khi bạn cần viết giao diện bằng code. Nếu bạn nắm được cách viết giao diện bằng code bạn có thể hiểu sâu hơn những vấn đề khi làm giao diện.

Hôm nay mình sẽ hướng dẫn làm giao diện bằng code nhé, để hiểu được phải code như thế nào thì chúng ta sẽ đi từ việc kéo thả giao diện:

Đã xong, không cần viết một dòng code, chúng ta vẫn hoàn thành được giao diện login, vậy khi code thì chúng ta sẽ code cái gì.

Trước khi sử dụng thư viện, mình hướng dẫn các bạn code chay nhé:

Đầu tiên chúng ta muốn cái gì xuất hiện trên giao diện thì chúng ta phải khởi tạo cái đó, ở đây mình khởi tạo đối tượng username và usernameTextField:

    let userName: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "User Name:"
        label.textAlignment = .left
        label.font = UIFont.systemFont(ofSize: 16)
        label.textColor = .black
        return label
    }()

    let userNameTextFiled: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.placeholder = "Click here"
        textField.font = UIFont.systemFont(ofSize: 18)
        textField.borderStyle = UITextField.BorderStyle.roundedRect
        return textField
    }()

Một thuộc tính rất quan trọng khi bạn khởi tạo đối tượng là bạn phải thiết lập translatesAutoresizingMaskIntoConstraints bằng false, mặc định là true theo tọa độ thông thường là frame và dùng AutoresizingMask.

Tiếp theo, chúng ta layout cho đối tượng:

        //username
        view.addSubview(userName)
        userName.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
        userName.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        userName.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        userName.heightAnchor.constraint(equalToConstant: 24).isActive = true
        
        //username textfield
        view.addSubview(userNameTextFiled)
        userNameTextFiled.topAnchor.constraint(equalTo: userName.bottomAnchor, constant: 8).isActive = true
        userNameTextFiled.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        userNameTextFiled.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        userNameTextFiled.heightAnchor.constraint(equalToConstant: 44).isActive = true

Ở đây khi layout cho đối tượng, mình đã sử dụng NSLayoutAnchor thay vì NSLayoutConstraint, vì khi sử dụng NSLayoutAnchor, bạn sẽ thấy ngắn gọn và dễ hiểu hơn, và nó cũng khá dễ để hình dung (gần giống với việc AutoLayout trên Storyboard)

Để thấy nó ngắn hơn như thế nào bạn có thể xem đoạn code sau đây:

        // C1: Sử dụng NSLayoutConstraint
        let constaint = NSLayoutConstraint(item: userName,
                                           attribute: .height,
                                           relatedBy: .equal,
                                           multiplier: 1.0,
                                           constant: 0)

         // C2: Sử dụng NSLayoutAnchor
         userName.heightAnchor.constraint(equalToConstant: 24).isActive = true

Qua ví dụ trên có thể thấy rõ các ưu điểm đã nêu của NSLayoutAnchor. Cách dùng cũng rất đơn giản, ở đây heightAnchor là: NSLayoutDimension chính là các thuộc tính sẽ được sử dụng để layout, ngoài heightAnchor ra có rất nhiều các thuộc tính khác như widthAnchor, topAnchor, leftAnchor, rightAnchor, bottomAnchor, ... (Chỉ cần gõ ten_doi_tuong.anchor XCode sẽ suggest ra các thuộc tính).

Quay lại với bài của chúng ta, khi mỗi đối tượng chúng ta đều phải layout lặp đi lặp lại và mã code sẽ trở nên rất cồng kềnh, dưới đây là toàn bộ đoạn code để ra giao diện như chúng ta đã thấy ban đầu

import UIKit

class ViewController: UIViewController {

    let userName: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "User Name:"
        label.textAlignment = .left
        label.font = UIFont.systemFont(ofSize: 16)
        label.textColor = .black
        return label
    }()
    
    let userNameTextFiled: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.placeholder = "Click here"
        textField.font = UIFont.systemFont(ofSize: 18)
        textField.borderStyle = UITextField.BorderStyle.roundedRect
        return textField
    }()
    
    let password: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Password:"
        label.textAlignment = .left
        label.font = UIFont.systemFont(ofSize: 16)
        label.textColor = .black
        return label
    }()
    
    let passwordTextFiled: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.placeholder = " Click here"
        textField.font = UIFont.systemFont(ofSize: 18)
        textField.borderStyle = UITextField.BorderStyle.roundedRect
        return textField
    }()
    
    let login: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = .gray
        button.setTitle("Login", for: .normal)
        button.contentHorizontalAlignment = .center
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
        button.setTitleColor(UIColor.white, for: .normal)
        button.layer.cornerRadius = 5.0
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupLayout()
    }
    
    func setupLayout(){
        //username
        view.addSubview(userName)
        userName.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
        userName.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        userName.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        userName.heightAnchor.constraint(equalToConstant: 24).isActive = true
        
        //username textfield
        view.addSubview(userNameTextFiled)
        userNameTextFiled.topAnchor.constraint(equalTo: userName.bottomAnchor, constant: 8).isActive = true
        userNameTextFiled.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        userNameTextFiled.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        userNameTextFiled.heightAnchor.constraint(equalToConstant: 44).isActive = true
        
        //password
        view.addSubview(password)
        password.topAnchor.constraint(equalTo: userNameTextFiled.bottomAnchor, constant: 16).isActive = true
        password.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        password.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        password.heightAnchor.constraint(equalToConstant: 24).isActive = true
        
        //password textfield
        view.addSubview(passwordTextFiled)
        passwordTextFiled.topAnchor.constraint(equalTo: password.bottomAnchor, constant: 8).isActive = true
        passwordTextFiled.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
        passwordTextFiled.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
        passwordTextFiled.heightAnchor.constraint(equalToConstant: 44).isActive = true
        
        //login
        view.addSubview(login)
        login.topAnchor.constraint(equalTo: passwordTextFiled.bottomAnchor, constant: 32).isActive = true
        login.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        login.widthAnchor.constraint(equalToConstant: 100).isActive = true
        login.heightAnchor.constraint(equalToConstant: 44).isActive = true
    }
}

Giao diện sẽ được như sau:

Woa, để hiển thị được một chút giao diện mà phải code rất chi là dài dòng, thế nên mình sẽ giới thiệu với các bạn cách khác ngắn hơn nhé.

Chúng ta sẽ sử dụng thư viện Stevia - một thư viện Layout dành cho iOS:

Để sử dụng các thư viện ngoài thì máy tính của bạn cần phải cài đặt Cocoa Pod: 

Stevia là thư viện ngoài thế nên chúng ta cần thực hiện một vài bước trước khi bắt tay vào code.

B1: Cài đặt Cocoa Pod (bước này chỉ cài lần đầu, máy đã cài Cocoa Pod thì bỏ qua bước này nhé):

$ sudo gem install cocoapods

B2: Tạo project (cái này easy phải không)

B3: Bật Terminal cd đến thư mục project vừa tạo và enter

$ cd /Users/Desktop/DemoSteviaLayout 

B4: Gõ lệnh pod init

$ pod init

B5: Mở Pod file trong project

B6: Thêm lệnh sau vào Podfile : 

pod 'SteviaLayout'

sau đó lưu file: cmd + S hoặc File -> Save

B7: Trong terminal, gõ lệnh

$ pod install

B8: Sau khi Install xong, bạn tắt cửa sổ project vừa tạo, vào project mở file có đuôi .xcworkspace

B9: Đã xong, giờ thì chúng ta code thôi

Chúng ta mở file DemoSteviaLayout.xcworkspace, vào ViewController.swift thêm thư viện Stevia

Chúng ta vẫn cần khai báo các đối tượng nhé:

    let username = UILabel()
    let usernameTextField = UITextField()
    let password = UILabel()
    let passwordTextField = UITextField()
    let login = UIButton()

Trong viewDidLoad(), chúng ta sẽ thêm các đối tượng trên vào view:

        view.sv(
            username,
            usernameTextField,
            password,
            passwordTextField,
            login
        )

sv([])sv() là cách gọi khác của addSubview()translateAutoresizingMaskIntoConstraints = false

Layout các đối tượng:

  username.top(50).left(30).right(30).height(40)
  ==
  view.layout(
            50,
            |-30-username-30-| ~ 40,
        )

Các bạn layout tương tự với  các đối tượng còn lại, chúng ta sẽ được:

        //layout
        view.layout(
            50,
            |-30-username-30-| ~ 40,
            8,
            |-30-usernameTextField-30-| ~ 40,
            16,
            |-30-password-30-| ~ 40,
            8,
            |-30-passwordTextField-30-| ~ 40,
            32,
            |-100-login-100-|
        )

Sau khi layout xong, chúng ta sẽ set đến font, color, text, ...

        //style
        usernameTextField.style(textFieldStyle)
        passwordTextField.style(textFieldStyle)
        login.style(buttonStyle)
        
        
        //content
        username.text = "User Name:"
        usernameTextField.placeholder = "Text here"
        password.text = "Password:"
        passwordTextField.placeholder = "Text here"
        login.setTitle("Login", for: .normal)

textFieldStylebuttonStyle mình viết thành hàm riêng để có thể sử dụng lại nhiều lần:

    func textFieldStyle(_ f: UITextField){
        f.borderStyle = .roundedRect
        f.font = UIFont.systemFont(ofSize: 18)
    }

    func buttonStyle(_ b: UIButton){
        b.backgroundColor = .gray
        b.setTitleColor(.white, for: .normal)
        b.layer.cornerRadius = 10
    }

Sau đó, bạn hãy Run project và giao diện sẽ được như sau:

Như vậy là chúng ta đã layout xong giao diện Login dưới sự hỗ trợ của thư viện, nếu bạn là developer iOS, bạn sẽ thích cách nào 😀

Dưới đây là toàn bộ mã code của mình khi sử dụng thư viện Stevia

//
//  ViewController.swift
//  DemoSteviaLayout
//

import UIKit
import Stevia

class ViewController: UIViewController {
    
    let username = UILabel()
    let usernameTextField = UITextField()
    let password = UILabel()
    let passwordTextField = UITextField()
    let login = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()        
        view.backgroundColor = .white
        
        //addSubview
        view.sv(
            username,
            usernameTextField,
            password,
            passwordTextField,
            login
        )
        
        //layout
        view.layout(
            50,
            |-30-username-30-| ~ 40,
            8,
            |-30-usernameTextField-30-| ~ 40,
            16,
            |-30-password-30-| ~ 40,
            8,
            |-30-passwordTextField-30-| ~ 40,
            32,
            |-100-login-100-|
        )
        
        //style
        usernameTextField.style(textFieldStyle)
        passwordTextField.style(textFieldStyle)
        login.style(buttonStyle)
        
        
        //content
        username.text = "User Name:"
        usernameTextField.placeholder = "Text here"
        password.text = "Password:"
        passwordTextField.placeholder = "Text here"
        login.setTitle("Login", for: .normal)

    }
    
    func textFieldStyle(_ f: UITextField){
        f.borderStyle = .roundedRect
        f.font = UIFont.systemFont(ofSize: 18)
    }

    func buttonStyle(_ b: UIButton){
        b.backgroundColor = .gray
        b.setTitleColor(.white, for: .normal)
        b.layer.cornerRadius = 10
    }
}

Các bài viết tiếp theo mình sẽ layout một số giao diện phức tạp hơn, các bạn chờ đón đọc nhé 😊