You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scroll View内嵌Table View:滚动时展开Table View并隐藏上方视图

嘿,这个交互确实是iOS App里超常见的体验——像微信的顶部搜索栏、微博的导航栏,都是这么做的!我来给你拆解下具体的实现思路和代码示例,分UIKit(Swift/OC)和SwiftUI两种场景来说~

核心原理

核心就是监听TableView的滚动偏移量,对比上次的偏移值判断滚动方向,然后通过平滑动画调整顶部视图的位置:

  • 向下滚动时,让顶部视图向上滑出屏幕(隐藏)
  • 向上滚动时,让顶部视图重新滑入屏幕(显示)

因为TableView继承自UIScrollView,所以我们可以利用UIScrollViewDelegate的回调来实现这个逻辑。

UIKit 实现步骤(Swift)

假设你已经在Storyboard/XIB里搭建好UI:顶部有个topView,下方是tableView,并且给topView添加了顶部约束(比如和父视图/SafeArea顶部的约束,记为topViewTopConstraint),topView的高度固定为60。

1. 定义状态变量

在ViewController里添加几个变量记录滚动状态和视图参数:

import UIKit

class ViewController: UIViewController {
    // 关联Storyboard里的控件
    @IBOutlet weak var topView: UIView!
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var topViewTopConstraint: NSLayoutConstraint!
    
    // 记录上次的滚动偏移量
    private var lastContentOffset: CGFloat = 0
    // 顶部视图的固定高度
    private let topViewHeight: CGFloat = 60
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置TableView的代理
        tableView.delegate = self
        // 模拟一些列表数据
        tableView.dataSource = self
    }
}

2. 实现滚动监听逻辑

遵守UIScrollViewDelegate,实现scrollViewDidScroll方法:

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 先判断列表内容是否足够滚动,避免内容过短误触发
        guard scrollView.contentSize.height > scrollView.bounds.height else {
            topViewTopConstraint.constant = 0
            return
        }
        
        let currentOffset = scrollView.contentOffset.y
        let offsetDiff = currentOffset - lastContentOffset
        
        // 根据滚动方向调整顶部视图的约束
        if offsetDiff > 0 {
            // 向下滚动:隐藏topView,约束值最小为 -topViewHeight(完全滑出屏幕)
            topViewTopConstraint.constant = max(topViewTopConstraint.constant - offsetDiff, -topViewHeight)
        } else {
            // 向上滚动:显示topView,约束值恢复到0(完全显示)
            topViewTopConstraint.constant = min(topViewTopConstraint.constant - offsetDiff, 0)
        }
        
        // 更新上次偏移量
        lastContentOffset = currentOffset
        
        // 触发平滑动画
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded()
        }
    }
}

// 补充TableView数据源(示例)
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = "第 \(indexPath.row + 1) 行"
        return cell
    }
}
UIKit 实现步骤(OC)

如果用Objective-C开发,逻辑是一样的:

1. 定义状态变量

#import "ViewController.h"

@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UIView *topView;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topViewTopConstraint;

@property (nonatomic, assign) CGFloat lastContentOffset;
@property (nonatomic, assign) CGFloat topViewHeight;
@end

2. 实现滚动监听

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.topViewHeight = 60;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 判断内容是否足够滚动
    if (scrollView.contentSize.height <= scrollView.bounds.height) {
        self.topViewTopConstraint.constant = 0;
        return;
    }
    
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat offsetDiff = currentOffset - self.lastContentOffset;
    
    if (offsetDiff > 0) {
        // 向下滚动
        self.topViewTopConstraint.constant = MAX(self.topViewTopConstraint.constant - offsetDiff, -self.topViewHeight);
    } else {
        // 向上滚动
        self.topViewTopConstraint.constant = MIN(self.topViewTopConstraint.constant - offsetDiff, 0);
    }
    
    self.lastContentOffset = currentOffset;
    
    // 平滑动画
    [UIView animateWithDuration:0.3 animations:^{
        [self.view layoutIfNeeded];
    }];
}

// 数据源实现(示例)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 50;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    cell.textLabel.text = [NSString stringWithFormat:@"第 %ld 行", indexPath.row + 1];
    return cell;
}

@end
SwiftUI 实现思路

如果用SwiftUI开发,可以通过监听ScrollView的滚动偏移来控制顶部视图的位置:

import SwiftUI

struct ScrollHideTopView: View {
    // 记录滚动偏移量
    @State private var scrollOffset: CGFloat = 0
    // 顶部视图高度
    let topViewHeight: CGFloat = 60
    
    var body: some View {
        ScrollView {
            VStack(spacing: 0) {
                // 顶部视图:根据滚动偏移调整位置
                Color.blue
                    .frame(height: topViewHeight)
                    .offset(y: scrollOffset > 0 ? -min(scrollOffset, topViewHeight) : 0)
                    .animation(.easeInOut(duration: 0.3), value: scrollOffset)
                
                // 列表内容
                ForEach(0..<50) { index in
                    Text("第 \(index + 1) 行")
                        .frame(height: 50)
                        .border(.gray)
                }
            }
        }
        // 监听滚动偏移
        .background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .global).origin.y)
            }
        )
        .onPreferenceChange(ScrollOffsetKey.self) { offset in
            scrollOffset = -offset // 转换坐标系,让偏移值符合直觉
        }
    }
}

// 自定义PreferenceKey获取滚动偏移
struct ScrollOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}

struct ScrollHideTopView_Previews: PreviewProvider {
    static var previews: some View {
        ScrollHideTopView()
    }
}
优化小技巧
  • 添加滚动阈值:比如判断abs(offsetDiff) > 5才触发动画,避免轻微滚动就频繁调整视图位置
  • 性能优化:如果顶部视图有复杂UI,可以设置topView.layer.shouldRasterize = true,减少动画卡顿
  • 边界处理:确保列表滚动到顶部时,顶部视图一定完全显示;滚动到底部时保持隐藏状态

内容的提问来源于stack exchange,提问作者Darius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:08:51