通用集团网站模板,怎样建设学校网站,土巴兔装修口碑怎样,高要网站建设抖音首页的头部滑动标签(通常称为Segmented Control或Tab Bar)是一个常见的UI组件#xff0c;可以通过以下几种方式实现#xff1a;
1. 使用UISegmentedControl
最简单的实现方式是使用系统自带的UISegmentedControl#xff1a;
let segmentedCo…抖音首页的头部滑动标签(通常称为Segmented Control或Tab Bar)是一个常见的UI组件可以通过以下几种方式实现
1. 使用UISegmentedControl
最简单的实现方式是使用系统自带的UISegmentedControl
let segmentedControl UISegmentedControl(items: [推荐, 关注, 同城])
segmentedControl.selectedSegmentIndex 0
segmentedControl.addTarget(self, action: #selector(segmentChanged(_:)), for: .valueChanged)
navigationItem.titleView segmentedControl objc func segmentChanged(_ sender: UISegmentedControl) {// 处理标签切换 print(Selected segment: \(sender.selectedSegmentIndex))
}2. 自定义实现更接近抖音效果
抖音的效果通常是水平滚动的标签栏可以这样实现
import UIKit class TikTokTabBar: UIView {private let scrollView UIScrollView()private var buttons: [UIButton] []private let indicator UIView()private var currentIndex: Int 0 var titles: [String] [] {didSet {setupButtons()}}var onTabSelected: ((Int) - Void)?override init(frame: CGRect) {super.init(frame: frame)setupUI()}required init?(coder: NSCoder) {super.init(coder: coder)setupUI()}private func setupUI() {scrollView.showsHorizontalScrollIndicator false addSubview(scrollView)indicator.backgroundColor .red scrollView.addSubview(indicator)}override func layoutSubviews() {super.layoutSubviews()scrollView.frame bounds var x: CGFloat 0 let buttonHeight bounds.height - 4 let padding: CGFloat 20 for (index, button) in buttons.enumerated() {let width button.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: buttonHeight)).width padding * 2 button.frame CGRect(x: x, y: 0, width: width, height: buttonHeight)x width if index currentIndex {indicator.frame CGRect(x: button.frame.minX padding, y: buttonHeight, width: button.frame.width - padding * 2, height: 3)}}scrollView.contentSize CGSize(width: x, height: bounds.height)}private func setupButtons() {buttons.forEach { $0.removeFromSuperview() }buttons.removeAll()for (index, title) in titles.enumerated() {let button UIButton(type: .custom)button.setTitle(title, for: .normal)button.setTitleColor(index 0 ? .white : .lightGray, for: .normal)button.titleLabel?.font UIFont.systemFont(ofSize: 16, weight: .medium)button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)button.tag index scrollView.addSubview(button)buttons.append(button)}}objc private func buttonTapped(_ sender: UIButton) {selectTab(at: sender.tag, animated: true)onTabSelected?(sender.tag)}func selectTab(at index: Int, animated: Bool) {guard index 0 index buttons.count else { return }let button buttons[index]currentIndex index UIView.animate(withDuration: animated ? 0.25 : 0) {self.buttons.forEach {$0.setTitleColor($0.tag index ? .white : .lightGray, for: .normal)}self.indicator.frame CGRect(x: button.frame.minX 20, y: button.frame.height, width: button.frame.width - 40, height: 3)// 确保选中的标签可见 let visibleRect CGRect(x: button.frame.minX - 30, y: 0, width: button.frame.width 60, height: self.scrollView.frame.height)self.scrollView.scrollRectToVisible(visibleRect, animated: animated)}}
}3. 结合PageViewController实现完整效果
要实现抖音首页的完整效果滑动标签同时控制页面切换可以结合UIPageViewController
class TikTokHomeViewController: UIViewController {private let tabBar TikTokTabBar()private var pageViewController: UIPageViewController!private var viewControllers: [UIViewController] []override func viewDidLoad() {super.viewDidLoad()// 设置标签栏 tabBar.titles [推荐, 关注, 同城]tabBar.onTabSelected { [weak self] index in self?.selectPage(at: index, animated: true)}navigationItem.titleView tabBar // 设置页面控制器 pageViewController UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)pageViewController.delegate self pageViewController.dataSource self // 添加子控制器 viewControllers [RecommendationViewController(),FollowingViewController(),NearbyViewController()]addChild(pageViewController)view.addSubview(pageViewController.view)pageViewController.didMove(toParent: self)pageViewController.setViewControllers([viewControllers[0]], direction: .forward, animated: false)}private func selectPage(at index: Int, animated: Bool) {guard index 0 index viewControllers.count else { return }let direction: UIPageViewController.NavigationDirection index tabBar.currentIndex ? .forward : .reverse pageViewController.setViewControllers([viewControllers[index]], direction: direction, animated: animated)tabBar.selectTab(at: index, animated: animated)}
}extension TikTokHomeViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) - UIViewController? {guard let index viewControllers.firstIndex(of: viewController), index 0 else { return nil }return viewControllers[index - 1]}func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) - UIViewController? {guard let index viewControllers.firstIndex(of: viewController), index viewControllers.count - 1 else { return nil }return viewControllers[index 1]}func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {if completed, let currentVC pageViewController.viewControllers?.first, let index viewControllers.firstIndex(of: currentVC) {tabBar.selectTab(at: index, animated: true)}}
}高级优化
1. 动画效果可以添加更流畅的滑动动画和指示器动画 2. 字体缩放选中的标签可以放大字体未选中的缩小 3. 预加载预加载相邻的页面以提高响应速度 4. 性能优化对于大量标签实现重用机制
下面是优化的具体实现
1. 平滑滑动动画与指示器效果优化
实现思路
监听UIScrollView的滚动偏移量根据偏移量动态计算指示器位置和宽度实现标签颜色渐变效果
代码实现
// 在TikTokTabBar类中添加以下方法
private var lastContentOffset: CGFloat 0 func scrollViewDidScroll(_ scrollView: UIScrollView) {guard scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating else { return }let offsetX scrollView.contentOffset.x let scrollViewWidth scrollView.bounds.width let progress (offsetX / scrollViewWidth) - CGFloat(currentIndex)// 防止快速滑动时progress超出范围 let clampedProgress max(-1, min(1, progress))updateTabAppearance(progress: clampedProgress)updateIndicatorPosition(progress: clampedProgress)lastContentOffset offsetX
}private func updateTabAppearance(progress: CGFloat) {let absProgress abs(progress)for (index, button) in buttons.enumerated() {// 当前标签和下一个标签 if index currentIndex || index currentIndex (progress 0 ? 1 : -1) {let isCurrent index currentIndex let targetIndex isCurrent ? (progress 0 ? currentIndex 1 : currentIndex - 1) : currentIndex guard targetIndex 0 targetIndex buttons.count else { continue }let targetButton buttons[targetIndex]// 颜色渐变 let currentColor UIColor.white let targetColor UIColor.lightGray let color isCurrent ? currentColor.interpolate(to: targetColor, progress: absProgress) : targetColor.interpolate(to: currentColor, progress: absProgress)button.setTitleColor(color, for: .normal)// 字体缩放 let minScale: CGFloat 0.9 let maxScale: CGFloat 1.1 let scale isCurrent ? maxScale - (maxScale - minScale) * absProgress : minScale (maxScale - minScale) * absProgress button.transform CGAffineTransform(scaleX: scale, y: scale)} else {// 其他标签保持默认状态 button.setTitleColor(.lightGray, for: .normal)button.transform CGAffineTransform(scaleX: 0.9, y: 0.9)}}
}private func updateIndicatorPosition(progress: CGFloat) {guard currentIndex 0 currentIndex buttons.count else { return }let currentButton buttons[currentIndex]var nextIndex currentIndex (progress 0 ? 1 : -1)nextIndex max(0, min(buttons.count - 1, nextIndex))let nextButton buttons[nextIndex]let absProgress abs(progress)// 计算指示器位置和宽度 let currentFrame currentButton.frame let nextFrame nextButton.frame let originX currentFrame.minX (nextFrame.minX - currentFrame.minX) * absProgress let width currentFrame.width (nextFrame.width - currentFrame.width) * absProgress indicator.frame CGRect(x: originX 20,y: currentFrame.height,width: width - 40,height: 3 )
}// UIColor扩展用于颜色插值
extension UIColor {func interpolate(to color: UIColor, progress: CGFloat) - UIColor {var fromRed: CGFloat 0, fromGreen: CGFloat 0, fromBlue: CGFloat 0, fromAlpha: CGFloat 0 var toRed: CGFloat 0, toGreen: CGFloat 0, toBlue: CGFloat 0, toAlpha: CGFloat 0 self.getRed(fromRed, green: fromGreen, blue: fromBlue, alpha: fromAlpha)color.getRed(toRed, green: toGreen, blue: toBlue, alpha: toAlpha)let red fromRed (toRed - fromRed) * progress let green fromGreen (toGreen - fromGreen) * progress let blue fromBlue (toBlue - fromBlue) * progress let alpha fromAlpha (toAlpha - fromAlpha) * progress return UIColor(red: red, green: green, blue: blue, alpha: alpha)}
}2. 字体缩放效果优化
实现思路
根据滑动进度动态调整标签字体大小当前选中标签放大相邻标签适当缩小其他标签保持最小尺寸
代码实现 上面的updateTabAppearance方法已经包含了字体缩放逻辑这里补充字体缩放的具体参数
// 在updateTabAppearance方法中添加以下参数
let minScale: CGFloat 0.9 // 最小缩放比例
let maxScale: CGFloat 1.1 // 最大缩放比例
let scale isCurrent ? maxScale - (maxScale - minScale) * absProgress : minScale (maxScale - minScale) * absProgress button.transform CGAffineTransform(scaleX: scale, y: scale)3. 页面预加载机制
实现思路
预加载当前页面相邻的页面使用UIPageViewController的缓存机制监听滑动方向提前准备内容
代码实现
// 在TikTokHomeViewController中添加预加载逻辑
private var pendingIndex: Int?
private var direction: UIPageViewController.NavigationDirection .forward func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {if let pendingVC pendingViewControllers.first,let index viewControllers.firstIndex(of: pendingVC) {pendingIndex index }
}func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {if completed, let pendingIndex pendingIndex {currentIndex pendingIndex tabBar.selectTab(at: currentIndex, animated: true)// 预加载相邻页面 preloadAdjacentPages()}pendingIndex nil
}private func preloadAdjacentPages() {// 预加载前一个页面 if currentIndex 0 {let previousIndex currentIndex - 1 if let previousVC pageViewController.dataSource?.pageViewController(pageViewController,viewControllerBefore: viewControllers[currentIndex]) {// 确保视图已加载 _ previousVC.view }}// 预加载后一个页面 if currentIndex viewControllers.count - 1 {let nextIndex currentIndex 1 if let nextVC pageViewController.dataSource?.pageViewController(pageViewController,viewControllerAfter: viewControllers[currentIndex]) {// 确保视图已加载 _ nextVC.view }}
}// 修改selectPage方法以支持方向判断
private func selectPage(at index: Int, animated: Bool) {guard index 0 index viewControllers.count else { return }direction index currentIndex ? .forward : .reverse pageViewController.setViewControllers([viewControllers[index]], direction: direction, animated: animated) { [weak self] _ in self?.preloadAdjacentPages()}currentIndex index tabBar.selectTab(at: index, animated: animated)
}4. 性能优化与标签重用
实现思路
对于大量标签实现重用机制只保留可视区域附近的标签动态加载和卸载标签
代码实现
// 在TikTokTabBar中添加重用逻辑
private let reusableQueue NSMutableSet()
private var visibleButtons
private var allTitles func setTitles(_ titles: [String]) {allTitles titles updateVisibleButtons()
}private func updateVisibleButtons() {// 计算当前可见范围 let visibleRange calculateVisibleRange()// 移除不再可见的按钮 for (index, button) in visibleButtons {if !visibleRange.contains(index) {button.removeFromSuperview()reusableQueue.add(button)visibleButtons.removeValue(forKey: index)}}// 添加新可见的按钮 for index in visibleRange {if visibleButtons[index] nil {let button dequeueReusableButton()configureButton(button, at: index)scrollView.addSubview(button)visibleButtons[index] button }}// 更新布局 setNeedsLayout()
}private func calculateVisibleRange() - ClosedRangeInt {let contentOffsetX scrollView.contentOffset.x let visibleWidth scrollView.bounds.width // 计算第一个和最后一个可见的索引 var startIndex 0 var endIndex allTitles.count - 1 // 这里可以添加更精确的计算逻辑 // 例如根据按钮宽度和偏移量计算 // 扩展可见范围预加载左右各2个 startIndex max(0, startIndex - 2)endIndex min(allTitles.count - 1, endIndex 2)return startIndex...endIndex
}private func dequeueReusableButton() - UIButton {if let button reusableQueue.anyObject() as? UIButton {reusableQueue.remove(button)return button }return UIButton(type: .custom)
}private func configureButton(_ button: UIButton, at index: Int) {button.setTitle(allTitles[index], for: .normal)button.setTitleColor(index currentIndex ? .white : .lightGray, for: .normal)button.titleLabel?.font UIFont.systemFont(ofSize: 16, weight: .medium)button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)button.tag index
}// 在scrollViewDidScroll中调用updateVisibleButtons
func scrollViewDidScroll(_ scrollView: UIScrollView) {updateVisibleButtons()// 其他滚动逻辑...
}5. 综合优化与细节处理
5.1 弹性效果限制
// 在TikTokTabBar中
scrollView.bounces false
scrollView.alwaysBounceHorizontal false 5.2 点击动画效果
objc private func buttonTapped(_ sender: UIButton) {// 点击动画 UIView.animate(withDuration: 0.1, animations: {sender.transform CGAffineTransform(scaleX: 0.95, y: 0.95)}) { _ in UIView.animate(withDuration: 0.1) {sender.transform .identity }}selectTab(at: sender.tag, animated: true)onTabSelected?(sender.tag)
}5.3 性能优化提示
// 在TikTokTabBar的初始化中
layer.shouldRasterize true
layer.rasterizationScale UIScreen.main.scale 5.4 内存管理优化
// 在视图控制器中
deinit {scrollView.delegate nil
}总结
通过以上高级优化实现你可以获得一个接近抖音效果的滑动标签栏具有以下特点
平滑的滑动动画和指示器过渡效果动态字体缩放和颜色渐变高效的页面预加载机制优化的性能与内存管理标签重用机制支持大量标签
这些优化可以显著提升用户体验使滑动更加流畅响应更加迅速同时保持良好的内存使用效率。