feature/v1
swift 96 lines 2.96 KB
Raw
1 //
2 // TVCategoryCell.swift
3 // JomloCV tvOS Example
4 //
5 // Created by Seto Elkahfi on 2026-01-27.
6 // Copyright © 2026 Seto Elkahfi. All rights reserved.
7 //
8
9 import JomloCollectionView
10 import UIKit
11
12 /// A large category tile for TV navigation grids.
13 /// Features prominent icon + label with focus scaling and color highlight.
14 final class TVCategoryCell: UICollectionViewCell, JomloSelfConfiguringCell {
15
16 private let iconLabel: UILabel = {
17 let label = UILabel()
18 label.font = .systemFont(ofSize: 52)
19 label.textAlignment = .center
20 return label
21 }()
22
23 private let nameLabel: UILabel = {
24 let label = UILabel()
25 label.font = .systemFont(ofSize: 26, weight: .semibold)
26 label.textColor = .white
27 label.textAlignment = .center
28 return label
29 }()
30
31 override init(frame: CGRect) {
32 super.init(frame: frame)
33 setupViews()
34 }
35
36 required init?(coder: NSCoder) {
37 fatalError("init(coder:) not supported")
38 }
39
40 func configure(with item: TVCategoryItem) {
41 iconLabel.text = item.icon
42 nameLabel.text = item.name
43 contentView.backgroundColor = UIColor(hex: item.colorHex) ?? .darkGray
44 }
45
46 // MARK: - Focus Engine
47
48 override var canBecomeFocused: Bool { true }
49
50 override func didUpdateFocus(
51 in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator
52 ) {
53 coordinator.addCoordinatedAnimations(
54 {
55 if self.isFocused {
56 self.transform = CGAffineTransform(scaleX: 1.06, y: 1.06)
57 self.contentView.layer.borderColor = UIColor.white.cgColor
58 self.contentView.layer.borderWidth = 4
59 self.layer.shadowColor = UIColor.black.cgColor
60 self.layer.shadowOpacity = 0.6
61 self.layer.shadowRadius = 12
62 self.layer.shadowOffset = CGSize(width: 0, height: 10)
63 } else {
64 self.transform = .identity
65 self.contentView.layer.borderWidth = 0
66 self.layer.shadowOpacity = 0
67 }
68 }, completion: nil)
69 }
70
71 private func setupViews() {
72 contentView.layer.cornerRadius = 18
73 contentView.clipsToBounds = true
74 clipsToBounds = false
75
76 let stack = UIStackView(arrangedSubviews: [iconLabel, nameLabel])
77 stack.axis = .vertical
78 stack.spacing = 10
79 stack.alignment = .center
80 stack.translatesAutoresizingMaskIntoConstraints = false
81
82 contentView.addSubview(stack)
83 NSLayoutConstraint.activate([
84 stack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
85 stack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
86 ])
87
88 isAccessibilityElement = true
89 accessibilityTraits = .button
90 }
91
92 override var accessibilityLabel: String? {
93 get { nameLabel.text }
94 set {}
95 }
96 }