Như bạn đã biết, mọi thứ bạn thấy trong một ứng dụng iOS đều là view, như button, table, slider, ... thậm chí là view cha chứa nhiều view nhỏ hơn. Nhưng điều bạn có thể không biết là mỗi view trong iOS được hỗ trợ bởi một lớp gọi là layer - cụ thể là một CALayer.

Trong hướng dẫn này, chúng ta sẽ tìm hiểu CALayer là gì và các ví dụ về việc sử dụng CALayers cho các hiệu ứng như vẽ, hình dạng, độ bóng, ...

CALayer liên quan đến UIView như thế nào?

UIView chăm sóc nhiều thứ bao gồm layout và xử lý các sự kiện chạm, tuy nhiên nó không trực tiếp chăm sóc hình dạng hay animation. UIKit uỷ thác nhiệm vụ đó cho người anh em của nó là CoreAnimation, UIView trên thực tế chỉ là một trình bao bọc CALayer, khi bạn đặt giới hạn cho UIView (gồm toạ độ kích thước) thì UIView chỉ đơn giản là đem giới hạn đó đặt vào CALayer hỗ trợ nó. Nếu bạn gọi layout IfNeed trên UIView, cuộc gọi sẽ được chuyển tiếp đến CALayer gốc. Mỗi UIView có một CALayer gốc, có thể chứa các layer con.

CALayer vs UIView

Bắt đầu  

Để hiểu nhanh và rõ ràng về layer hoạt động như thế nào, mọi người hãy cùng code nhé.

Bạn hãy tạo một project và thêm vào đó một UView đặt tên là viewForLayer, sau đó thay thế nội dung file ViewController.swift bằng đoạn mã sau: 

import UIKit

class ViewController: UIViewController {
  
  @IBOutlet weak var viewForLayer: UIView!
  
  var layer: CALayer {
    return viewForLayer.layer
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    setUpLayer()
  }
  
  func setUpLayer() {
    layer.backgroundColor = UIColor.blue.cgColor
    layer.borderWidth = 100.0
    layer.borderColor = UIColor.red.cgColor
    layer.shadowOpacity = 0.7
    layer.shadowRadius = 10.0
  }

}

Như đã đề cập trước đó, mọi view trong iOS đều có một layer được liên kết với nó và bạn có thể truy xuất bằng .layer. Ở đây, mình tạo một thuộc tính gọi là layer để truy cập vào lớp viewForLayer.

Hàm setUpLayer() để set một số thuộc tính cho layer như: đổ bóng, màu xanh cho background, màu đỏ cho viền, kích thước viền, ...

Giờ thì bạn hãy build và run trong iOS Simulator để thấy thay đổi mà bạn vừa viết trong hàm setUpLayer:

CALayerPlayground-1

Thuộc tính cơ bản của CALayer 

CALayer có một số thuộc tính cho phép bạn tuỳ chỉnh, như nãy giờ chúng ta làm:

- Thay đổi màu nền từ mặc định không có màu thành màu xanh

- Đặt cho nó một đường viền bằng cách thay đổi độ rộng đường viền từ 0 thành 100

- Thay đổi màu viền từ đen mặc định thành đỏ

- Cuối cùng, set cho nó một bóng bằng cách thay đổi độ mờ của bóng từ 0 thành 0,7, và thay đổi bán kính của bóng từ 3 đến 10

Hãy thêm đoạn mã sau vào cuối hàm setUpLayer()

layer.contents = UIImage(named: "star")?.cgImage
layer.contentsGravity = kCAGravityCenter

Thuộc tính contents trên một CALayer cho phép bạn set nội dung của layer thành một image

CALayerPlayground-2

Bạn thấy ảnh ngôi sao được căn giữa - điều này là do bạn đặt thuộc tính contensGravity thành kCAGravityCenter. Bạn cũng có thể thay đổi bằng cách chuyển từ giữa thành lên trên, xuống dưới, sang trái, sang phải.

Thay đổi sự xuất hiện của Layer

Để tương tác với viewForLayer, bạn cần kết nối Action cho nó, chúng ta cần 2 action là tap và pinch:

  @IBAction func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
  }
  
  @IBAction func pinchGestureRecognized(_ sender: UIPinchGestureRecognizer) {

  }

Đầu tiên chúng ta thay đổi trong sự kiện tapGestureRecognized:

@IBAction func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
  layer.shadowOpacity = layer.shadowOpacity == 0.7 ? 0.0 : 0.7
}

Điều nay báo cho layer của viewForLayer chuyển đổi độ mờ của bóng từ 0.7 về 0 sau một cú chạm và ngược lại.

Bây giờ thay đổi sự kiện pinchGestureRecognized:

@IBAction func pinchGestureRecognized(_ sender: UIPinchGestureRecognizer) {
  let offset: CGFloat = sender.scale < 1 ? 5.0 : -5.0
  let oldFrame = layer.frame
  let oldOrigin = oldFrame.origin
  let newOrigin = CGPoint(x: oldOrigin.x + offset, y: oldOrigin.y + offset)
  let newSize = CGSize(width: oldFrame.width + (offset * -2.0), height: oldFrame.height + (offset * -2.0))
  let newFrame = CGRect(origin: newOrigin, size: newSize)
  if newFrame.width >= 100.0 && newFrame.width <= 300.0 {
    layer.borderWidth -= offset
    layer.cornerRadius += (offset / 2.0)
    layer.frame = newFrame
  }
}

Build và run lại ứng dụng của bạn:

CALayerPlayground-3

Wow! Bạn có thể đánh bóng hơn một chút để có được một hình avatar đẹp mắt.

Vẽ bằng CALayer

Để có một hình tròn đỏ như trên bạn có thể dùng nhiều cách, bạn có thể dùng UIImageView, dùng UIView bo tròn, ở đây mình dùng CAShapeLayer để vẽ và nó sẽ nhẹ hơn nhiều, còn có thể tái sử dụng

Mình có một ViewController và add vào đó một view đặt tên là ball, ball này được mình custom ở một class có tên là CircleLayerView:

import UIKit

class ViewController: UIViewController {

    var ball: CircleLayerView!    
    let ball_y_origin: CGFloat = 100.0
    var h: CGFloat!
    let d :CGFloat = 20.0
  
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white
        h = ball_y_origin
        ball = CircleLayerView(center: CGPoint(x: view.bounds.midX, y: h),
                               radius: d,
                               fillColor:  UIColor.red)
        view.addSubview(ball)
    }

}

CircleLayerView:

import UIKit

class CircleLayerView: UIView {
    var circleLayer: CAShapeLayer!
    var radius: CGFloat = 0.0
    
    var fillColor: UIColor!
    
    convenience init(center: CGPoint, radius: CGFloat, fillColor: UIColor) {
        self.init(frame: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2.0, height: radius * 2.0))
        self.radius = radius
        self.fillColor = fillColor
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        if circleLayer == nil {
            circleLayer = CAShapeLayer()
            circleLayer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0,
                                                           width: self.frame.size.width,
                                                           height: self.frame.size.height)).cgPath
            
            circleLayer.fillColor = fillColor.cgColor
            self.layer.addSublayer(circleLayer)
        }
    }

}

CALayer không chỉ có một vài thuộc tính và phương thức để sửa đổi, bạn có thể tham khảo thêm ở các mã nguồn khác trên internet:

Có một vài điều bạn cần biết nữa về CALayer:

  • Layers có thể có sublayers: Giống như các view, view cha có thể có view con, các lớp có thể có các lớp con
  • Thuộc tính của layer là animated: Khi bạn thay đổi thuộc tính của một layer thì nó được animated theo thời gian mặc định và bạn có thể tuỳ chỉnh
  • Layers thì nhẹ: Các layer thì nhẹ hơn so với view và do đó giúp bạn đạt được hiệu suất tốt hơn.