Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on October 16, 2019

Basic iOS tutorial : Custom View

iOS & Swift

Contents

  • Chuẩn bị
  • 1. Custom View là làm gì?
    • 1.1. Các mức cho custom
    • 1.2. Các công việc chính trong custom view
  • 2. Tạo giao diện
    • 2.1. Code chay giao diện
    • 2.2. Thay đổi thuộc tính của Custom View
    • 2.3. Bắt sự kiện người dùng
    • 2.4. Kéo thả giao diện
      • Các bước thực hiện như sau:
  • 3. Quản lý sự kiện
    • 3.1. Tạo protocol cho custom view
    • 3.2. Implement Protocol trong ViewController
  • Tạm kết:

Chào bạn, chúng ta lại tiếp tục với seri học iOS cơ bản. Bài viết này sẽ hướng dẫn bạn custom view cho giao diện ứng dụng iOS.

Nếu bạn chưa biết gì về View thì có thể đọc bài viết này: Basic iOS tutorial : View

Chuẩn bị

  • MacOS 10.14.4
  • Xcode 11.0
  • Swift 5.1

1. Custom View là làm gì?

  • Custom View là tạo ra các UI Control mới, để cho giao diện ứng dụng theo đúng với thiết kế giao diện của ứng dụng iOS.
  • Các thiết kế giao diện thường sẽ phức tạp và không sử dụng các UI Control cơ bản.

1.1. Các mức cho custom

  • Kế thừa:
    • Tạo Sub-class
    • Tuỳ chỉnh giao diện
  • Kết hợp:
    • Sử dụng các UI Control cơ bản để tạo thành giao diện phức tạp
    • Tuỳ biến các thuộc tính

1.2. Các công việc chính trong custom view

  • Tạo giao diện
  • Quản lý sự kiện

2. Tạo giao diện

  • Xem sơ đồ sau về các con đường tạo giao diện cho 1 custom view

  • Việc quan trọng đầu tiên của bạn sẽ làm

Tạo sub-class từ class UIView để tạo class mới cho custom view của bạn.

  • Phương pháp tạo thì theo 2 trường phái chính:
    • Code chay
      • Add Subview: Cách tạo giao diện từ việc sử dụng các UI Control đơn giản kết hợp thành UI Controll phức tạp
      • Draw: vẽ, dùng cho việc tạo giao diện theo phương pháp vẽ và thao tác với canvas
    • Kéo thả
      • LoadNibName: load 1 file *.xib chứa giao diện của Custom View, sau đó ép kiểu về thành kiểu class của Custom View

2.1. Code chay giao diện

  • Bước 1: New file –> Tạo sub-class từ UIView

 

  • Bước 2: Add các UI Control cơ bản. Trong ví dụ thì custom view bao gồm:
    • 1 UIImageView
    • 1 UILabel
    • 1 UIButton
import UIKit

class MyView: UIView {
       
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .blue
        
        // add user avatar
        let userAvatar = UIImageView(image: UIImage(named: "no_avatar"))
        userAvatar.frame = CGRect(x: 0,
                                  y: 0,
                                  width: frame.size.width,
                                  height: frame.size.height*4/5)
        userAvatar.contentMode = .scaleAspectFit
        self.addSubview(userAvatar)
               
       // add user name
        let userName = UILabel(frame: CGRect(x: 0,
                                             y: frame.size.height*4/5,
                                             width: frame.size.width,
                                             height: frame.size.height/5))
        userName.text = "Fx Studio"
        userName.backgroundColor = .lightGray
        userName.textAlignment = .center
        userName.textColor = .blue
        self.addSubview(userName)
               
        // add button
        let button = UIButton(frame: CGRect(x: 0,
                                            y: 0,
                                            width: frame.size.width,
                                            height: frame.size.height))
        button.backgroundColor = .clear
        self.addSubview(button)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}
  • Bước 4: Tạo đối tượng của custom view
let myView = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
  • Bước 5: add đối tượng vào view
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myView = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
        view.addSubview(myView)
    }


}
  • Kết quả:

2.2. Thay đổi thuộc tính của Custom View

  • Việc custom view có ý nghĩa thì nó như 1 UIView bình thường
  • Nghĩa là:
    • Có thể tạo nhiều đối tượng
    • Tái sử dụng được nhiều lần
    • Tuỳ biến được giao diện
  • Đảm bảo cho các việc đó thì chúng ta phải khai báo
    • Thuộc tính là public
    • Các hàm init khác nhau với tham số khác nhau truyền vào
  • Quay lại file MyView.swift
    • Khai báo 3 thuộc tính public
    • Viết lại hàm init
import UIKit

class MyView: UIView {
    
    var avatarImageView: UIImageView?
    var nameLabel: UILabel?
    var markButton: UIButton?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .blue
        
        // add user avatar
        avatarImageView = UIImageView(image: UIImage(named: "no_avatar"))
        avatarImageView!.frame = CGRect(x: 0,
                                  y: 0,
                                  width: frame.size.width,
                                  height: frame.size.height*4/5)
        avatarImageView!.contentMode = .scaleAspectFill
        self.addSubview(avatarImageView!)
               
       // add user name
        nameLabel = UILabel(frame: CGRect(x: 0,
                                             y: frame.size.height*4/5,
                                             width: frame.size.width,
                                             height: frame.size.height/5))
        nameLabel!.text = "Fx Studio"
        nameLabel!.backgroundColor = .lightGray
        nameLabel!.textAlignment = .center
        nameLabel!.textColor = .blue
        self.addSubview(nameLabel!)
               
        // add button
        markButton = UIButton(frame: CGRect(x: 0,
                                            y: 0,
                                            width: frame.size.width,
                                            height: frame.size.height))
        markButton!.backgroundColor = .clear
        self.addSubview(markButton!)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}
  • Mở file ViewController.swift
    • Tạo 2 đối tương MyView
    • addSubview vào View của ViewController
override func viewDidLoad() {
        super.viewDidLoad()
        
        let hamster = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
        hamster.nameLabel?.text = "hamster"
        hamster.avatarImageView?.image = UIImage(named: "hamster")
        view.addSubview(hamster)
        
        let husky = MyView(frame: CGRect(x: 200, y: 100, width: 100, height: 125))
        husky.nameLabel?.text = "husky"
        husky.avatarImageView?.image = UIImage(named: "husky")
        view.addSubview(husky)
    }
  • Kết quả:

2.3. Bắt sự kiện người dùng

  • Với việc bắt sự kiện người dùng bằng phương pháp code chay thì chúng ta có addTarget cho UI Control
  • Mở file MyView.swift
    • Khai báo thêm 1 biến class count
var count = 0
    • addTarget cho markButton
 // add button
        markButton = UIButton(frame: CGRect(x: 0,
                                            y: 0,
                                            width: frame.size.width,
                                            height: frame.size.height))
        markButton!.backgroundColor = .clear
        markButton?.addTarget(self, action: #selector(tap), for: .touchUpInside)
        self.addSubview(markButton!)
    • Thêm function đếm số lần tap vào
@objc func tap() {
        count += 1
        nameLabel?.text = "\(count)"
    }
  • Kết quả:

 

2.4. Kéo thả giao diện

  • Khó khăn khi code chay
    • Việc code chay thì rất thú vị nhưng cũng rất tốn công sức.
    • Bạn không thể hình dung ra giao diện của mình như thế nào trong lúc code
    • Thay đổi vị trí các control sẽ gặp khó khăn
  • Giải pháp:

Sử dụng phương pháp LoadNibName để load 1 file giao diện (*.xib) và ép kiểu của nó về với kiểu class của custom và sử dụng như bình thường.

  • NibName là gì?
    • Là tên gọi của các file giao diện *.xib
    • Format chính là xml
    • 1 Custom có thể có nhiều file *.xib khác nhau
    • 1 cái tên Nib có thể cho nhiều file *.xib khác nhau

Các bước thực hiện như sau:

  • Bước 1: Tạo sub-class

  • Bước 2: Tạo file *.xib với tên là tên của sub-class
    • New File > User Interface > View
    • Đặt tên giống với sub-class custom view

  • Bước 3: tiến hành kéo thả giao diện, tạo outlet & action
    • Tuỳ chỉnh Freeform để có thể kéo thả size của view

    • Kéo thả outlet và action
    • Chú ý class name cho custom view

    • Code xử lý ở trong
import UIKit

class UserView: UIView {
    
    var count = 0
    
    @IBOutlet weak var avatarImageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    
    @IBAction func tap(_ sender: Any) {
        count += 1
        nameLabel.text = "\(count)"
    }
    
}
  • Bước 4: loadNibName và addSubView
    • Dùng main Bundle để load file *.xib với tham số là tên file
    • Chú ý tên của file Nib nếu sai thì app sẽ bị crash
    • Vì dữ liệu trả về là 1 array nên sử dụng phần tử đầu tiên .first
    • Ép kiểm về UserView
let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as? UserView
userView?.frame = CGRect(x: 50, y: 250, width: 100, height: 125)
view.addSubview(userView!)
  • Kết quả

  • Các Action vẫn hoạt động như bình thường

3. Quản lý sự kiện

  • Đây là sơ đồ các kiểu bắt sự kiện của người dùng tác động lên Custom View
  • Cần lưu ý điểm sau
    • Sự kiện mà Custom view bắt được thì sẽ được xử lý ở đối tượng custom view
    • Chúng không thuộc ViewController nên ViewController không thể xử lý được các sự kiện này
  • Muốn truyền sự kiện về ViewController thì sử dụng phương pháp an toán nhất là protocol

Truyền sự kiện và dữ liệu về ViewController

3.1. Tạo protocol cho custom view

  • Mở file UserView.swift thêm đoạn code sau:
protocol UserViewDelegate {
    func didTap(view: UserView, count: Int)
}
  • Tạo đối tượng protocol và sử dụng, chú ý:
    • Phải kiểu optional
    • Kiểm tra khác nil  trước khi gọi hàm
import UIKit

protocol UserViewDelegate {
    func didTap(view: UserView, count: Int)
}

class UserView: UIView {
    
    var count = 0
    var delegate: UserViewDelegate?
    
    @IBOutlet weak var avatarImageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    
    @IBAction func tap(_ sender: Any) {
        count += 1
        nameLabel.text = "\(count)"
        
        if let delegate = delegate {
            delegate.didTap(view: self, count: count)
        }
    }
    
}

3.2. Implement Protocol trong ViewController

  • Mở file ViewController.swift thêm 1 extention
    • Kế thừa lại UserViewDelegate
    • Định nghĩa lại hàm didTap
extension ViewController: UserViewDelegate {
    func didTap(view: UserView, count: Int) {
        print("Count = \(count)")
    }
}
  • Set delegate của đối tượng custom view bằng ViewController
    • Vì ViewController đã kế thừa protocol UserViewDelegate nên cùng kiểu với thuộc tính delegate của đối tượng custom view
let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as? UserView
userView?.frame = CGRect(x: 50, y: 250, width: 100, height: 125)
userView?.delegate = self
view.addSubview(userView!)
  • Khi sự kiện được bắt ở Custom View thì nó sử dụng delegate của mình
    • Gọi function didTap để truyền sự kiện đi
    • Vì ViewController đã xét cho delegate nên delegate chính là ViewController
    • Trong ViewController đã cài đặt lại function didTap nên khi delegate gọi hàm đó thực thi với các đối số truyền vào. Thì cũng chính là ViewController gọi hàm didTap và nhận được các giá trị mới thông qua các đối số.
  • Kết quả

 

Tới đây thì tạm thời chúng ta xong được phần Custom View. Các giao diện phức tạp vẫn cấu thành từ các giao diện đơn giản hơn. Nên bạn cần phải phân tích giao diện và custom cho phù hợp.

Tạm kết:

  • Custom View bằng code chay và LoadNibName
  • Xử lý được các sự kiện người dùng
  • Truyền sự kiện và dữ liệu về ViewController
  • Sử dụng được Protocol
FacebookTweetPinYummlyLinkedInPrintEmailShares35

Related Posts:

  • feature_bg_3
    Clean Architecture trong iOS
  • testing
    Hello Testing iOS
Tags: basic ios tutorial, iOS
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

2 comments

  • Antoniopluff has written: December 31, 2021 at 7:05 pm Reply

    Broadly speaking, there are three types of user interface design approaches that you can take, each with its pros and cons, its fans and haters: iOS Storyboards : A visual tool for laying out multiple application views and the transitions between them. NIBs or XIBs : Each NIB file corresponds to a single view element and can be laid out in the Interface Builder, making it a visual tool as well. Custom Code : i.e., no GUI tools, but rather, handling all custom positioning, animation, etc.

    • chuotfx has written: January 1, 2022 at 10:03 am Reply

      Thank you! 👍

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Role-playing vs. Persona-based Prompting
  • [Swift 6.2] Raw Identifiers – Đặt tên hàm có dấu cách, tại sao không?
  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?

You may also like:

  • Hello Testing iOS
    testing
  • Clean Architecture trong iOS
    feature_bg_3

Archives

  • May 2025 (2)
  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • September 2024 (1)
  • July 2024 (1)
  • June 2024 (1)
  • May 2024 (4)
  • April 2024 (2)
  • March 2024 (5)
  • January 2024 (4)
  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (44)
  • Code (11)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (102)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (87)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2025 Fx Studio - All rights reserved.