tvOS:如何通过SwiftUI实现类似.card样式的平移/焦点效果?
在tvOS SwiftUI中实现类似.card的平移/焦点效果
SwiftUI没有直接提供和tvOS原生.card样式完全一致的开箱即用接口,但可以通过两种方式实现接近的平移/视差焦点效果:
1. 自定义ButtonStyle + 几何动画
你可以结合FocusState监听焦点状态,再用GeometryEffect实现自定义的偏移和缩放动画,复刻类似.card的视差感:
// 自定义视差效果的几何 modifier struct ParallaxEffect: GeometryEffect { var offset: CGSize var scale: CGFloat // 支持动画过渡 var animatableData: AnimatablePair<CGSize.AnimatableData, CGFloat> { get { AnimatablePair(offset.animatableData, scale) } set { offset.animatableData = newValue.first scale = newValue.second } } func effectValue(size: CGSize) -> ProjectionTransform { ProjectionTransform(CGAffineTransform(translationX: offset.width, y: offset.height) .scaledBy(x: scale, y: scale)) } } // 自定义按钮样式 struct CardButtonStyle: ButtonStyle { @FocusState private var isFocused: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .focused($isFocused) // 焦点状态下的偏移和缩放参数可按需调整 .modifier(ParallaxEffect( offset: isFocused ? CGSize(width: -12, height: -12) : .zero, scale: isFocused ? 1.08 : 1.0 )) // 增加阴影增强立体感,匹配原生card的视觉效果 .shadow(color: .black.opacity(0.4), radius: isFocused ? 20 : 8, x: isFocused ? 12 : 0, y: isFocused ? 12 : 0) .animation(.interactiveSpring(response: 0.2, dampingFraction: 0.8), value: isFocused) } } // 使用示例 Button("Card Style Button") { // 按钮操作 } .buttonStyle(CardButtonStyle())
这个实现的核心是通过FocusState感知焦点变化,再用ParallaxEffect处理平移和缩放,动画参数可以根据你的视觉需求微调,比如偏移量、缩放比例、动画曲线等。
2. 封装UIKit原生组件(完全匹配系统效果)
如果需要100%还原tvOS原生.card的焦点动效,最稳妥的方式是通过UIViewRepresentable封装UICollectionView——因为原生UICollectionViewCell在焦点变化时自带系统级的平移、阴影和视差效果,无需手动调整参数:
struct NativeCardCollection: UIViewRepresentable { let items: [String] let onSelect: (String) -> Void func makeUIView(context: Context) -> UICollectionView { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 300, height: 200) layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.register(NativeCardCell.self, forCellWithReuseIdentifier: "CardCell") collectionView.delegate = context.coordinator collectionView.dataSource = context.coordinator // 开启原生焦点效果 collectionView.allowsFocus = true return collectionView } func updateUIView(_ uiView: UICollectionView, context: Context) { uiView.reloadData() } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UICollectionViewDelegate, UICollectionViewDataSource { let parent: NativeCardCollection init(_ parent: NativeCardCollection) { self.parent = parent } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { parent.items.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as! NativeCardCell cell.label.text = parent.items[indexPath.item] return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { parent.onSelect(parent.items[indexPath.item]) } } } // 自定义原生cell,启用card样式 class NativeCardCell: UICollectionViewCell { let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupView() { label.frame = contentView.bounds label.textAlignment = .center label.textColor = .white contentView.addSubview(label) // 启用原生card样式 contentView.backgroundColor = .systemBlue self.focusStyle = .card } } // 使用示例 NativeCardCollection(items: ["Item 1", "Item 2", "Item 3"]) { selectedItem in print("Selected: \(selectedItem)") }
这种方式直接复用系统原生的.card焦点行为,动效和系统自带的完全一致,适合对视觉一致性要求高的场景。
内容的提问来源于stack exchange,提问作者Kyle Howarth
相关产品推荐
相关产品推荐

