JXUserProfileView
Both scroll horizontal and vertical for segment scrollview which have a same header. — 类似半糖、美丽说主页与QQ音乐歌曲列表布局效果,实现不同菜单的左右滑动切换,同时支持类似tableview的顶部工具栏悬停(既可以左右滑动,又可以上下滑动)。
Install / Use
/learn @pujiaxin33/JXUserProfileViewREADME
JXUserProfileView
iOS:一分钟集成主流APP个人资料页(如简书、微博等)
仓库迁移
本仓库已经停止更新,所有代码已经迁移至JXPagingView
Github
仓库地址 如果你喜欢,记得点颗❤️哟
前言
伴随着APP不断迭代更新,各种功能也是越堆越多。对于用户来说,在个人资料页面需要显示的元素也越来越多。如何井然有序的展示且交互简单,变成了一个难事。当然这一点也难不倒产品经理,他们总会想出各种奇淫怪术来折磨我们(程序员)。下面以简书和微博来解读下主流APP是如何处理的。
简书&微博

正如上图所示,简书和微博的布局如出一辙。一个TableHeaderView显示背景图、头像、简介等基础信息。然后有一个SectionHeaderView,承载不同的内容分类指引(动态、文章、更多等),可以悬浮在顶部。然后就是不同分类的数据流的TableView,可以直接滚动。建议大家打开简书和微博,把玩一下。
第一眼看上去,感觉没有什么难度嘛,就是一个主TableView嵌套一个ScrollView,支持左右滚动切换,然后ScrollView放三个TableView。但是仔细思考会有许多难点需要解决,比如主TableView和嵌套TableView的手势冲突,三个子TableView的位置更新等。
如果要自己解决这些问题,且要调试到目标效果,可能要花上你大半天时间了。而且本身个人资料页面的业务逻辑就特别多,如果还要插入这种页面交互逻辑,说实话,当你看到许多零散的代码交织在一起时,你的头会和足球一样大!!!

所以这篇文章不会着重讲解如何解决上面的问题(可以查看源码分析),而是将页面结构封装好,你只需要花一分钟填充你的业务逻辑即可。妈妈再也不用担心我去调试页面交互逻辑了👏
核心原理
让滑动手势给每个ScrollView都可以处理,只是对于mainTableView和listView有不同的逻辑
class JXUserProfileMainTableView: UITableView, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer.isKind(of: UIPanGestureRecognizer.classForCoder()) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.classForCoder())
}
}
- 对于mainTableView的滚动事件处理
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.delegate.mainTableViewDidScroll?(scrollView)
if (self.listItemScrollView != nil && self.listItemScrollView!.contentOffset.y > 0) {
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动
self.mainTableView.contentOffset = CGPoint(x: 0, y: self.delegate.tableHeaderViewHeight(in: self))
}
if (scrollView.contentOffset.y < self.delegate.tableHeaderViewHeight(in: self)) {
//mainTableView已经显示了header,listView的contentOffset需要重置
for index in 0..<self.delegate.numberOfListViews(in: self) {
let listView = self.delegate.userProfileView(self, listViewInRow: index)
listView.scrollView.contentOffset = CGPoint.zero
}
}
}
- 对于listView的滚动事件处理
/// 外部传入的listView,当其内部的scrollView滚动时,需要调用该方法
open func listViewDidScroll(scrollView: UIScrollView) {
self.listItemScrollView = scrollView
if (self.mainTableView.contentOffset.y < self.delegate.tableHeaderViewHeight(in: self)) {
//mainTableView的header还没有消失,让listScrollView一直为0
scrollView.contentOffset = CGPoint.zero;
scrollView.showsVerticalScrollIndicator = false;
} else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPoint(x: 0, y: self.delegate.tableHeaderViewHeight(in: self));
scrollView.showsVerticalScrollIndicator = true;
}
}
实现效果

使用
1.实例化JXUserProfileView
userProfileView = JXUserProfileView(delegate: self)
userProfileView.delegate = self
self.view.addSubview(userProfileView)
2.实现JXUserProfileViewDelegate
@objc protocol JXUserProfileViewDelegate {
///mainTableView的滚动回调,用于实现头图跟随缩放
@objc optional func mainTableViewDidScroll(_ scrollView: UIScrollView)
///tableHeaderView的高度
func tableHeaderViewHeight(in userProfileView: JXUserProfileView) -> CGFloat
///返回tableHeaderView
func tableHeaderView(in userProfileView: JXUserProfileView) -> UIView
///heightForHeaderOfSection,就是分类视图的高度
func heightForHeaderOfSection(in userProfileView: JXUserProfileView) -> CGFloat
///viewForHeaderOfSection,分类视图,我用的是自己封装的JXCategoryView,你也可以选择其他的或者自己写
func viewForHeaderOfSection(in userProfileView: JXUserProfileView) -> UIView
///底部listView的条数
func numberOfListViews(in userProfileView: JXUserProfileView) -> Int
///返回对应index的listView,需要是UIView的子类,且要遵循JXUserProfileListViewDelegate。这里要求返回一个UIView而不是一个UIScrollView,因为listView可能并不只是一个单纯的TableView,还会有其他的元素
func userProfileView(_ userProfileView: JXUserProfileView, listViewInRow row: Int) -> JXUserProfileListViewDelegate & UIView
}
3.让外部listView遵从JXUserProfileListViewDelegate协议
//该协议主要用于mainTableView已经显示了header,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
@objc protocol JXUserProfileListViewDelegate {
var scrollView: UIScrollView { get }
}
4.将外部listView的滚动事件传入userProfileView
func listViewDidScroll(_ scrollView: UIScrollView) {
userProfileView.listViewDidScroll(scrollView: scrollView)
}
不用一分钟,就可以集成完毕👏
Github
仓库地址 如果你喜欢,记得点颗❤️哟
推荐阅读
Related Skills
node-connect
349.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
