Thêm shadow cho text làm UI đẹp, nổi bật, tăng trải nghiệm người dùng (game, app trẻ em, heading...).
Cách: Extension UILabel
Bước 1: Tạo dự án
Xcode → Single View App → Tên: "ShadowText".
Bước 2: Storyboard
Thêm UILabel → Tạo @IBOutlet weak var lblHelloWorld: UILabel!
Bước 3: Extension UILabel
extension UILabel {
func addTextShadow(color: UIColor = .black,
offset: CGSize = CGSize(width: 1, height: 1),
radius: CGFloat = 3.0,
opacity: Float = 1.0) {
self.textColor = color
self.layer.masksToBounds = false // Quan trọng: cho shadow ra ngoài bounds
self.layer.shadowColor = color.cgColor
self.layer.shadowOffset = offset
self.layer.shadowRadius = radius
self.layer.shadowOpacity = opacity
self.layer.shouldRasterize = true // Tối ưu performance
self.layer.rasterizationScale = UIScreen.main.scale
}
}
Bước 4: Gọi trong viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Đỏ, shadow đen offset (1,1), radius 6, opacity 1
lblHelloWorld.addTextShadow(color: .systemRed,
offset: CGSize(width: 2, height: 2),
radius: 6,
opacity: 0.8)
}

Các biến thể Shadow phổ biến
1. Shadow mờ nhẹ (Subtle)
label.addTextShadow(color: .black, offset: CGSize(width: 0, height: 1), radius: 1, opacity: 0.3)
2. Shadow nổi (Neumorphism/Float effect)
label.addTextShadow(color: .white, offset: CGSize(width: -2, height: -2), radius: 4, opacity: 0.5)
label.addTextShadow(color: .black, offset: CGSize(width: 2, height: 2), radius: 4, opacity: 0.3)
// Cần 2 label chồng nhau hoặc dùng attributedString với NSShadow
3. Shadow nhiều màu (Gradient shadow - phức tạp)
Cần dùng CAGradientLayer làm mask cho shadow layer.
Cách 2: NSAttributedString (Cho phần text trong label)
let attributedString = NSMutableAttributedString(string: "Hello World")
let shadow = NSShadow()
shadow.shadowColor = UIColor.black.withAlphaComponent(0.5)
shadow.shadowOffset = CGSize(width: 2, height: 2)
shadow.shadowBlurRadius = 4
attributedString.addAttribute(.shadow, value: shadow, range: NSRange(location: 0, length: 5)) // Chỉ "Hello"
label.attributedText = attributedString
Lưu ý quan trọng
- masksToBounds = false (mặc định true) - BẮT BUỘC để shadow hiển thị ra ngoài bounds
- shouldRasterize = true + rasterizationScale - Tối ưu GPU, tránh re-render liên tục khi scroll
- Shadow color nên dùng
UIColor.black.withAlphaComponent(0.3-0.5)tự nhiên hơn black solid - Performance: Quá nhiều label có shadow + scroll → lag. Cân nhắc rasterize hoặc pre-render
- Dark Mode: Shadow color có thể cần dynamic (light mode: đen, dark mode: trắng/clear)
Dynamic Shadow cho Dark Mode
extension UILabel {
func addDynamicTextShadow(radius: CGFloat = 3, opacity: Float = 0.3, offset: CGSize = .init(width: 1, height: 1)) {
self.layer.masksToBounds = false
self.layer.shadowRadius = radius
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = offset
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
// Dynamic shadow color
self.layer.shadowColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark
? UIColor.white.withAlphaComponent(0.3).cgColor
: UIColor.black.withAlphaComponent(0.3).cgColor
}.cgColor
}
}