Created
September 2, 2024 14:17
-
-
Save vurgunmert/69cb6e61935fe5b0b62fb76b3e006e9c to your computer and use it in GitHub Desktop.
UIKit UIView Card Box Storybook tvOS MedienMonster
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIKitCardBoxStorybook.swift | |
// TemplateAppTvOS | |
// | |
// Created by Mert Vurgun on 2.09.2024. | |
// | |
import Foundation | |
import UIKit | |
class UIKitCardBoxStorybook: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setupUI() | |
setInitialFocus() | |
} | |
// MARK: - Setup UI | |
private func setupUI() { | |
view.backgroundColor = .systemGray | |
// Create a main grid-like stack view | |
let mainStackView = UIStackView() | |
mainStackView.axis = .vertical | |
mainStackView.alignment = .fill | |
mainStackView.distribution = .fillEqually | |
mainStackView.spacing = 20 | |
mainStackView.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(mainStackView) | |
NSLayoutConstraint.activate([ | |
mainStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), | |
mainStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), | |
mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), | |
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) | |
]) | |
// Add rows of boxes | |
let boxStyles: [BoxStyle] = [.appleTVStyle, .redbullTVStyle, .darkWithRoundedCorners, | |
.lightWithShadows, .blueShadowed, .borderedAndElevated, | |
.imageBackgroundWithOverlay, .neonGlow, .minimalist] | |
for rowIndex in 0..<3 { | |
let rowStackView = UIStackView() | |
rowStackView.axis = .horizontal | |
rowStackView.alignment = .fill | |
rowStackView.distribution = .fillEqually | |
rowStackView.spacing = 20 | |
mainStackView.addArrangedSubview(rowStackView) | |
for colIndex in 0..<3 { | |
let styleIndex = rowIndex * 3 + colIndex | |
rowStackView.addArrangedSubview(createBoxView(style: boxStyles[styleIndex])) | |
} | |
} | |
} | |
// MARK: - Set Initial Focus | |
private func setInitialFocus() { | |
guard let firstFocusableView = view.subviews.first(where: { $0.canBecomeFocused }) else { return } | |
setNeedsFocusUpdate() | |
updateFocusIfNeeded() | |
firstFocusableView.becomeFirstResponder() | |
} | |
override var preferredFocusEnvironments: [UIFocusEnvironment] { | |
return [view.subviews.first(where: { $0.canBecomeFocused })].compactMap { $0 } | |
} | |
// MARK: - Box Styles | |
private enum BoxStyle { | |
case appleTVStyle | |
case redbullTVStyle | |
case darkWithRoundedCorners | |
case lightWithShadows | |
case blueShadowed | |
case borderedAndElevated | |
case imageBackgroundWithOverlay | |
case neonGlow | |
case minimalist | |
} | |
// MARK: - Create Box View | |
private func createBoxView(style: BoxStyle) -> UIView { | |
let boxView = FocusableBoxView() | |
switch style { | |
case .appleTVStyle: | |
configureAppleTVStyle(boxView) | |
case .redbullTVStyle: | |
configureRedbullTVStyle(boxView) | |
case .darkWithRoundedCorners: | |
configureDarkWithRoundedCorners(boxView) | |
case .lightWithShadows: | |
configureLightWithShadows(boxView) | |
case .blueShadowed: | |
configureSportCardBlueShadow(boxView) | |
case .borderedAndElevated: | |
configureBorderedAndElevated(boxView) | |
case .imageBackgroundWithOverlay: | |
configureImageBackgroundWithOverlay(boxView) | |
case .neonGlow: | |
configureNeonGlow(boxView) | |
case .minimalist: | |
configureMinimalist(boxView) | |
} | |
return boxView | |
} | |
// MARK: - Custom App Style Configurations | |
private func configureAppleTVStyle(_ boxView: UIView) { | |
boxView.backgroundColor = .darkGray | |
boxView.layer.cornerRadius = 20 | |
boxView.layer.shadowColor = UIColor.black.cgColor | |
boxView.layer.shadowOpacity = 0.7 | |
boxView.layer.shadowOffset = CGSize(width: 0, height: 10) | |
boxView.layer.shadowRadius = 15 | |
addLabel(to: boxView, text: "Apple TV Style") | |
} | |
private func configureRedbullTVStyle(_ boxView: UIView) { | |
boxView.backgroundColor = .systemRed | |
boxView.layer.cornerRadius = 15 | |
boxView.layer.borderColor = UIColor.white.cgColor | |
boxView.layer.borderWidth = 3 | |
addLabel(to: boxView, text: "Redbull TV Style", textColor: .white) | |
} | |
private func configureDarkWithRoundedCorners(_ boxView: UIView) { | |
boxView.backgroundColor = .black | |
boxView.layer.cornerRadius = 10 | |
boxView.layer.borderWidth = 2 | |
boxView.layer.borderColor = UIColor.systemGray.cgColor | |
addLabel(to: boxView, text: "Dark w/ Rounded Corners", textColor: .lightGray) | |
} | |
private func configureLightWithShadows(_ boxView: UIView) { | |
boxView.backgroundColor = .white | |
boxView.layer.shadowColor = UIColor.black.cgColor | |
boxView.layer.shadowOpacity = 0.4 | |
boxView.layer.shadowOffset = CGSize(width: 0, height: 5) | |
boxView.layer.shadowRadius = 10 | |
boxView.layer.cornerRadius = 8 | |
addLabel(to: boxView, text: "Light w/ Shadows", textColor: .black) | |
} | |
private func configureSportCardBlueShadow(_ boxView: UIView) { | |
// Configure the image view | |
let imageView = UIImageView() | |
imageView.image = UIImage(named: "imgSportPlaceHolder") | |
imageView.contentMode = .scaleAspectFill | |
imageView.clipsToBounds = true | |
imageView.layer.cornerRadius = 12 | |
imageView.translatesAutoresizingMaskIntoConstraints = false | |
boxView.addSubview(imageView) | |
NSLayoutConstraint.activate([ | |
imageView.leadingAnchor.constraint(equalTo: boxView.leadingAnchor), | |
imageView.trailingAnchor.constraint(equalTo: boxView.trailingAnchor), | |
imageView.topAnchor.constraint(equalTo: boxView.topAnchor), | |
imageView.bottomAnchor.constraint(equalTo: boxView.bottomAnchor) | |
]) | |
// Configure the overlay view | |
let overlayView = UIView() | |
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.4) | |
overlayView.layer.cornerRadius = 12 | |
overlayView.translatesAutoresizingMaskIntoConstraints = false | |
boxView.addSubview(overlayView) | |
NSLayoutConstraint.activate([ | |
overlayView.leadingAnchor.constraint(equalTo: boxView.leadingAnchor), | |
overlayView.trailingAnchor.constraint(equalTo: boxView.trailingAnchor), | |
overlayView.topAnchor.constraint(equalTo: boxView.topAnchor), | |
overlayView.bottomAnchor.constraint(equalTo: boxView.bottomAnchor) | |
]) | |
// Configure the blue shadow | |
boxView.layer.shadowColor = UIColor.blue.cgColor | |
boxView.layer.shadowOpacity = 0.7 | |
boxView.layer.shadowOffset = CGSize(width: 0, height: 10) | |
boxView.layer.shadowRadius = 15 | |
boxView.layer.cornerRadius = 12 | |
} | |
private func configureBorderedAndElevated(_ boxView: UIView) { | |
boxView.backgroundColor = .systemYellow | |
boxView.layer.borderColor = UIColor.systemRed.cgColor | |
boxView.layer.borderWidth = 2 | |
boxView.layer.shadowColor = UIColor.black.cgColor | |
boxView.layer.shadowOpacity = 0.5 | |
boxView.layer.shadowOffset = CGSize(width: 0, height: 4) | |
boxView.layer.shadowRadius = 6 | |
boxView.layer.cornerRadius = 10 | |
addLabel(to: boxView, text: "Bordered & Elevated", textColor: .black) | |
} | |
private func configureImageBackgroundWithOverlay(_ boxView: UIView) { | |
let imageView = UIImageView() | |
imageView.image = UIImage(named: "imgMoviePlaceHolder") | |
imageView.contentMode = .scaleAspectFill | |
imageView.clipsToBounds = true | |
imageView.layer.cornerRadius = 12 | |
imageView.translatesAutoresizingMaskIntoConstraints = false | |
boxView.addSubview(imageView) | |
NSLayoutConstraint.activate([ | |
imageView.leadingAnchor.constraint(equalTo: boxView.leadingAnchor), | |
imageView.trailingAnchor.constraint(equalTo: boxView.trailingAnchor), | |
imageView.topAnchor.constraint(equalTo: boxView.topAnchor), | |
imageView.bottomAnchor.constraint(equalTo: boxView.bottomAnchor) | |
]) | |
let overlayView = UIView() | |
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.4) | |
overlayView.layer.cornerRadius = 12 | |
overlayView.translatesAutoresizingMaskIntoConstraints = false | |
boxView.addSubview(overlayView) | |
NSLayoutConstraint.activate([ | |
overlayView.leadingAnchor.constraint(equalTo: boxView.leadingAnchor), | |
overlayView.trailingAnchor.constraint(equalTo: boxView.trailingAnchor), | |
overlayView.topAnchor.constraint(equalTo: boxView.topAnchor), | |
overlayView.bottomAnchor.constraint(equalTo: boxView.bottomAnchor) | |
]) | |
} | |
private func configureNeonGlow(_ boxView: UIView) { | |
boxView.backgroundColor = .black | |
boxView.layer.cornerRadius = 10 | |
boxView.layer.shadowColor = UIColor.systemGreen.cgColor | |
boxView.layer.shadowOpacity = 0.8 | |
boxView.layer.shadowOffset = CGSize(width: 0, height: 0) | |
boxView.layer.shadowRadius = 10 | |
addLabel(to: boxView, text: "Neon Glow", textColor: .systemGreen) | |
} | |
private func configureMinimalist(_ boxView: UIView) { | |
boxView.backgroundColor = .white | |
boxView.layer.cornerRadius = 5 | |
boxView.layer.borderWidth = 1 | |
boxView.layer.borderColor = UIColor.lightGray.cgColor | |
addLabel(to: boxView, text: "Minimalist", textColor: .darkGray) | |
} | |
// MARK: - Helper to Add Label | |
// MARK: - Helper to Add Label | |
private func addLabel(to boxView: UIView, text: String, textColor: UIColor = .white) { | |
let label = UILabel() | |
label.text = text | |
label.textAlignment = .center | |
label.textColor = textColor | |
label.font = UIFont.boldSystemFont(ofSize: 16) | |
label.translatesAutoresizingMaskIntoConstraints = false | |
boxView.addSubview(label) | |
NSLayoutConstraint.activate([ | |
label.centerXAnchor.constraint(equalTo: boxView.centerXAnchor), | |
label.centerYAnchor.constraint(equalTo: boxView.centerYAnchor) | |
]) | |
} | |
} | |
// Custom UIView to handle focusable boxes | |
class FocusableBoxView: UIView { | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setup() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
setup() | |
} | |
private func setup() { | |
isUserInteractionEnabled = true | |
} | |
override var canBecomeFocused: Bool { | |
return true | |
} | |
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { | |
if context.nextFocusedView == self { | |
layer.borderWidth = 5 | |
layer.borderColor = UIColor.orange.cgColor | |
} else { | |
layer.borderWidth = 0 | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment