Contents
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
- LoadNibName: load 1 file
- Code chay
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
- Tạo 2 đối tương
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
- Khai báo thêm 1 biến class
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
- Tuỳ chỉnh
-
- 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
- Phải kiểu
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 1extention
- Kế thừa lại
UserViewDelegate
- Định nghĩa lại hàm
didTap
- Kế thừa lại
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ínhdelegate
của đối tượng custom view
- Vì ViewController đã kế thừa protocol
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êndelegate
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àmdidTap
và nhận được các giá trị mới thông qua các đối số.
- Gọi function
- 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
Related Posts:
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
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- 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)
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.
Thank you! 👍