main
swift 117 lines 4.05 KB
Raw
1 //
2 // TVSeeAllCell.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 /// The "See All" last-item card for tvOS shelves.
13 ///
14 /// Matches the Viaplay design: dark semi-transparent background,
15 /// a prompt question, and a prominent pink pill button — all
16 /// the same height as a poster card so it sits flush in the shelf.
17 ///
18 /// Focus behaviour mirrors `TVPosterCell`: scales up with a shadow lift.
19 final class TVSeeAllCell: UICollectionViewCell, JomloSelfConfiguringCell {
20
21 // MARK: - Views
22
23 private let promptLabel: UILabel = {
24 let label = UILabel()
25 label.text = "Do you want to see more similar titles?"
26 label.font = .systemFont(ofSize: 26, weight: .medium)
27 label.textColor = .white
28 label.textAlignment = .center
29 label.numberOfLines = 0
30 return label
31 }()
32
33 private let pillButton: UIButton = {
34 let button = UIButton(type: .custom)
35 button.setTitle("See All", for: .normal)
36 button.titleLabel?.font = .systemFont(ofSize: 24, weight: .bold)
37 button.setTitleColor(.white, for: .normal)
38 // Viaplay pink/magenta — #E8005A
39 button.backgroundColor = UIColor(red: 0.91, green: 0.0, blue: 0.35, alpha: 1.0)
40 button.layer.cornerRadius = 28
41 button.contentEdgeInsets = UIEdgeInsets(top: 14, left: 36, bottom: 14, right: 36)
42 // Disable interaction — the cell itself handles selection
43 button.isUserInteractionEnabled = false
44 return button
45 }()
46
47 // MARK: - Init
48
49 override init(frame: CGRect) {
50 super.init(frame: frame)
51 setupViews()
52 }
53
54 required init?(coder: NSCoder) {
55 fatalError("init(coder:) not supported")
56 }
57
58 // MARK: - JomloSelfConfiguringCell
59
60 func configure(with item: TVSeeAllItem) {
61 // The layout is static — nothing to configure from the model
62 }
63
64 // MARK: - Focus Engine
65
66 override var canBecomeFocused: Bool { true }
67
68 override func didUpdateFocus(
69 in context: UIFocusUpdateContext,
70 with coordinator: UIFocusAnimationCoordinator
71 ) {
72 coordinator.addCoordinatedAnimations(
73 {
74 if self.isFocused {
75 self.transform = CGAffineTransform(scaleX: 1.08, y: 1.08)
76 self.layer.shadowColor = UIColor.black.cgColor
77 self.layer.shadowOpacity = 0.6
78 self.layer.shadowRadius = 20
79 self.layer.shadowOffset = CGSize(width: 0, height: 16)
80 // Brighten the pill on focus
81 self.pillButton.transform = CGAffineTransform(scaleX: 1.04, y: 1.04)
82 } else {
83 self.transform = .identity
84 self.layer.shadowOpacity = 0
85 self.pillButton.transform = .identity
86 }
87 }, completion: nil)
88 }
89
90 // MARK: - Layout
91
92 private func setupViews() {
93 // Dark frosted-glass look — matches the screenshot
94 contentView.backgroundColor = UIColor(white: 0.12, alpha: 1.0)
95 contentView.layer.cornerRadius = 14
96 contentView.clipsToBounds = true
97 clipsToBounds = false
98
99 let stack = UIStackView(arrangedSubviews: [promptLabel, pillButton])
100 stack.axis = .vertical
101 stack.spacing = 32
102 stack.alignment = .center
103 stack.translatesAutoresizingMaskIntoConstraints = false
104
105 contentView.addSubview(stack)
106 NSLayoutConstraint.activate([
107 stack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
108 stack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
109 stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
110 stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
111 ])
112
113 isAccessibilityElement = true
114 accessibilityTraits = .button
115 accessibilityLabel = "See all. Do you want to see more similar titles?"
116 }
117 }