CALayer trong iOS

Có lẽ các bạn đã quen thuộc với các views trong iOS, tuy nhiên phía dưới mỗi view là gì thì có thể nhiều người chưa rõ. Đó chính là layer - một trong những phần cốt lõi của framework Core Animation.

1. Low => High level Framework

Việc làm sao để ứng dụng trở nên mượt mà chắc hẳn là một nhiệm vụ rất quan trọng để thu hút người dùng. Trên iOS, sẽ có tối đa 60 khung hình xuất hiện trong 1 giây. Và để đặt được tốc độ này, chúng ta cần truy cập trực tiếp vào GPU bằng cách sử dụng framework OpenGL. Nó sẽ cung cấp cho bạn quyền truy cập ở mức thấp nhất vào phần cứng đồ họa nên tốc độ nhanh nhất là điều hiển nhiên. Tuy nhiên, với những nhiệm vụ đơn giản thì chúng ta cũng cần phải viết rất nhiều code.

Để cải thiện vấn đề này, Core Graphics được tạo ra để giảm đi 1 chút code nhưng level so với phần cứng lại cao hơn 1 chút. Để tiếp tục đơn giản hơn nữa, framework Core Animation được tạo ra để cung cấp CALayer - cho phép truy cập vào các lớp đồ họa.

Nhưng rồi Apple lại nhận ra rằng nhiều chức năng của Core Animation không phải là luôn cần thiết với các ứng dụng nên cuối cùng thì UIKit được viết ra để cung cấp quyền truy cập cao nhất vào đồ họa trên iOS.

Với các tầng này bạn sẽ lựa chọn xem ứng dụng của bạn phù hợp với level nào để sử dụng framework vừa có những chức năng cần thiết mà lại giảm thiểu được code.

2. Các chức năng chính của CALayer

Như mình đã nói ở trên, nằm dưới mỗi view sẽ là 1 layer. Chúng ta có thể truy cập đến layer này đơn giản bằng cách gọi trực tiếp thuộc tính layer của view đó. Ví dụ: myView.layer

Rounded Corner

Thuộc tính cornerRadius của CALayer giúp chúng ra bo tròn góc.

myView.layer.cornerRadius = 10

Đây là kết quả:

hoặc có thể tạo ra view hình tròn từ một view hình vuông bằng cách

myView.layer.cornerRadius = myView.frame.size.width / 2

Shadow effect

Shadow được sử dụng rất nhiều, đặc biệt là trong Material design. Nó giúp giao diện tạo được chiều sâu khi người dùng sử dụng.

        myView.layer.shadowOffset = CGSize(width: 5, height: 5)
        myView.layer.shadowOpacity = 0.7
        myView.layer.shadowRadius = 5

Trong đó shadowOffset là thuộc tính set vị trí của shadow, (width: 5, height: 5) tức là nằm về bên phải và dưới của layer mỗi phía 5 pixels. shadowOpacity, shadowRadius là 2 thuộc tính liên quan đến độ mờ ảo của shadow.

Border

Chúng ta có thể thêm viền cho layer

        myView.layer.borderColor = UIColor.black.cgColor
        myView.layer.borderWidth = 2

Images

Chúng ta hoàn toàn có thể set image cho view mà không cần dùng đến UIImageView bằng cách truy cập vào layer

        myView.layer.contents = UIImage(named: "image.jpg")?.cgImage
        myView.layer.masksToBounds = true

Chúng ta có thể thấy shadow của layer đã biến mất. Đó là do thuộc tính masksToBounds, khi được set bằng true thì phần thừa ra của các sublayer so với ranh giới sẽ bị loại bỏ. Ranh giới của layer như hình trên là 1 đường tròn do đã set cornerRadius ở trên. Thuộc tính này khá giống với clipToBounds của UIView khi nó loại bỏ các phần thừa của các subviews ngoài ranh giới. Để hiểu rõ hơn bạn có thể set false cho masksToBounds

Lúc này bạn sẽ nhìn thấy rõ ranh giới của layer là đường tròn và phần bị thừa ra bị không bị loại bỏ.

Bài toán đặt ra

Bây giờ mình muốn ảnh vẫn phải là hình tròn và vẫn có shadow.

Hãy thử suy nghĩ một chút 🤔

Mình chưa tìm được cách khác ngoài 1 cách hơi lầy 🤣 đó là cho 1 cái view bên dưới rồi add shadow cho nó còn view ở trên lúc đầu sẽ chỉ hiển thị ảnh.

        myView.layer.cornerRadius = myView.frame.size.width / 2
        myView.layer.borderColor = UIColor.black.cgColor
        myView.layer.borderWidth = 2
        myView.layer.contents = UIImage(named: "image.jpg")?.cgImage
        myView.layer.masksToBounds = true
        
        let tempView = UIView(frame: myView.frame)
        tempView.backgroundColor = .black
        view.addSubview(tempView)
        view.sendSubviewToBack(tempView)
        tempView.layer.cornerRadius = tempView.frame.size.width / 2
        tempView.layer.shadowOffset = CGSize(width: 5, height: 5)
        tempView.layer.shadowOpacity = 0.7
        tempView.layer.shadowRadius = 5

Bạn nào có cách nào hay hơn có thể comment bên dưới giúp mình 👏

3. Kết luận

Hi vọng với chút kiến thức mình chia sẻ sẽ giúp các bạn trong quá trình phát triển ứng dụng.

Bài viết lấy ý tưởng từ nguồn: https://www.appcoda.com/calayer-introduction/