{"id":18974683,"url":"https://github.com/pujiaxin33/jxuserprofileview","last_synced_at":"2025-06-11T21:03:16.951Z","repository":{"id":98696982,"uuid":"135392420","full_name":"pujiaxin33/JXUserProfileView","owner":"pujiaxin33","description":"Both scroll horizontal and vertical for segment scrollview which have a same header. — 类似半糖、美丽说主页与QQ音乐歌曲列表布局效果，实现不同菜单的左右滑动切换，同时支持类似tableview的顶部工具栏悬停（既可以左右滑动，又可以上下滑动）。","archived":false,"fork":false,"pushed_at":"2018-08-24T01:56:04.000Z","size":199,"stargazers_count":31,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-16T11:05:26.051Z","etag":null,"topics":["userprofiles"],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pujiaxin33.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-05-30T05:29:56.000Z","updated_at":"2023-11-30T01:50:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"b1d13709-ae8a-4779-ae5e-a8a0146bea87","html_url":"https://github.com/pujiaxin33/JXUserProfileView","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXUserProfileView","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXUserProfileView/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXUserProfileView/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pujiaxin33%2FJXUserProfileView/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pujiaxin33","download_url":"https://codeload.github.com/pujiaxin33/JXUserProfileView/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249740040,"owners_count":21318674,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["userprofiles"],"created_at":"2024-11-08T15:15:48.964Z","updated_at":"2025-04-19T16:40:49.533Z","avatar_url":"https://github.com/pujiaxin33.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JXUserProfileView\niOS：一分钟集成主流APP个人资料页（如简书、微博等）\n\n## 仓库迁移\n\n**本仓库已经停止更新，所有代码已经迁移至[JXPagingView](https://github.com/pujiaxin33/JXPagingView)**\n\n## Github\n[仓库地址](https://github.com/pujiaxin33/JXUserProfileView) 如果你喜欢，记得点颗❤️哟\n## 前言\n  伴随着APP不断迭代更新，各种功能也是越堆越多。对于用户来说，在个人资料页面需要显示的元素也越来越多。如何井然有序的展示且交互简单，变成了一个难事。当然这一点也难不倒产品经理，他们总会想出各种奇淫怪术来折磨我们（程序员）。下面以简书和微博来解读下主流APP是如何处理的。\n\n## 简书\u0026微博\n![简书](https://upload-images.jianshu.io/upload_images/1085173-0da903effc6c05af.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/375)\n\n![微博](https://upload-images.jianshu.io/upload_images/1085173-cb5c095f4ed0e065.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/375)\n\n  正如上图所示，简书和微博的布局如出一辙。一个TableHeaderView显示背景图、头像、简介等基础信息。然后有一个SectionHeaderView，承载不同的内容分类指引（动态、文章、更多等），可以悬浮在顶部。然后就是不同分类的数据流的TableView，可以直接滚动。**建议大家打开简书和微博，把玩一下。**\n\n  第一眼看上去，感觉没有什么难度嘛，就是一个主TableView嵌套一个ScrollView，支持左右滚动切换，然后ScrollView放三个TableView。但是仔细思考会有许多难点需要解决，比如主TableView和嵌套TableView的手势冲突，三个子TableView的位置更新等。\n\n  如果要自己解决这些问题，且要调试到目标效果，可能要花上你大半天时间了。而且本身个人资料页面的业务逻辑就特别多，如果还要插入这种页面交互逻辑，说实话，当你看到许多零散的代码交织在一起时，你的头会和足球一样大！！！\n![足球头](https://upload-images.jianshu.io/upload_images/1085173-aee6817c3954c1d4.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/375)\n\n  所以这篇文章不会着重讲解如何解决上面的问题（可以查看源码分析），而是将页面结构封装好，你只需要花一分钟填充你的业务逻辑即可。妈妈再也不用担心我去调试页面交互逻辑了👏\n\n## 核心原理\n 让滑动手势给每个ScrollView都可以处理，只是对于mainTableView和listView有不同的逻辑\n```\nclass JXUserProfileMainTableView: UITableView, UIGestureRecognizerDelegate {\n    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -\u003e Bool {\n        return gestureRecognizer.isKind(of: UIPanGestureRecognizer.classForCoder()) \u0026\u0026 otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.classForCoder())\n    }\n}\n```\n- 对于mainTableView的滚动事件处理\n```swift\nfunc scrollViewDidScroll(_ scrollView: UIScrollView) {\n        self.delegate.mainTableViewDidScroll?(scrollView)\n\n        if (self.listItemScrollView != nil \u0026\u0026 self.listItemScrollView!.contentOffset.y \u003e 0) {\n            //mainTableView的header已经滚动不见，开始滚动某一个listView，那么固定mainTableView的contentOffset，让其不动\n            self.mainTableView.contentOffset = CGPoint(x: 0, y: self.delegate.tableHeaderViewHeight(in: self))\n        }\n\n        if (scrollView.contentOffset.y \u003c self.delegate.tableHeaderViewHeight(in: self)) {\n            //mainTableView已经显示了header，listView的contentOffset需要重置\n            for index in 0..\u003cself.delegate.numberOfListViews(in: self) {\n                let listView = self.delegate.userProfileView(self, listViewInRow: index)\n                listView.scrollView.contentOffset = CGPoint.zero\n            }\n        }\n    }\n```\n- 对于listView的滚动事件处理\n```swift\n/// 外部传入的listView，当其内部的scrollView滚动时，需要调用该方法\n    open func listViewDidScroll(scrollView: UIScrollView) {\n        self.listItemScrollView = scrollView\n\n        if (self.mainTableView.contentOffset.y \u003c self.delegate.tableHeaderViewHeight(in: self)) {\n            //mainTableView的header还没有消失，让listScrollView一直为0\n            scrollView.contentOffset = CGPoint.zero;\n            scrollView.showsVerticalScrollIndicator = false;\n        } else {\n            //mainTableView的header刚好消失，固定mainTableView的位置，显示listScrollView的滚动条\n            self.mainTableView.contentOffset = CGPoint(x: 0, y: self.delegate.tableHeaderViewHeight(in: self));\n            scrollView.showsVerticalScrollIndicator = true;\n        }\n    }\n```\n\n## 实现效果\n![JXUserProfileViewGif.gif](https://upload-images.jianshu.io/upload_images/1085173-981d275f1b8a4bdb.gif?imageMogr2/auto-orient/strip)\n\n## 使用\n1.实例化`JXUserProfileView`\n```swift\n        userProfileView = JXUserProfileView(delegate: self)\n        userProfileView.delegate = self\n        self.view.addSubview(userProfileView)\n```\n2.实现`JXUserProfileViewDelegate`\n```swift\n@objc protocol JXUserProfileViewDelegate {\n\n    ///mainTableView的滚动回调，用于实现头图跟随缩放\n    @objc optional func mainTableViewDidScroll(_ scrollView: UIScrollView)\n\n    ///tableHeaderView的高度\n    func tableHeaderViewHeight(in userProfileView: JXUserProfileView) -\u003e CGFloat\n\n    ///返回tableHeaderView\n    func tableHeaderView(in userProfileView: JXUserProfileView) -\u003e UIView\n\n    ///heightForHeaderOfSection，就是分类视图的高度\n    func heightForHeaderOfSection(in userProfileView: JXUserProfileView) -\u003e CGFloat\n\n    ///viewForHeaderOfSection，分类视图，我用的是自己封装的JXCategoryView，你也可以选择其他的或者自己写\n    func viewForHeaderOfSection(in userProfileView: JXUserProfileView) -\u003e UIView\n\n    ///底部listView的条数\n    func numberOfListViews(in userProfileView: JXUserProfileView) -\u003e Int\n\n    ///返回对应index的listView，需要是UIView的子类，且要遵循JXUserProfileListViewDelegate。这里要求返回一个UIView而不是一个UIScrollView，因为listView可能并不只是一个单纯的TableView，还会有其他的元素\n    func userProfileView(_ userProfileView: JXUserProfileView, listViewInRow row: Int) -\u003e JXUserProfileListViewDelegate \u0026 UIView\n}\n```\n\n3.让外部listView遵从`JXUserProfileListViewDelegate`协议\n```swift\n//该协议主要用于mainTableView已经显示了header，listView的contentOffset需要重置时，内部需要访问到外部传入进来的listView内的scrollView\n@objc protocol JXUserProfileListViewDelegate {\n    var scrollView: UIScrollView { get }\n}\n```\n\n4.将外部listView的滚动事件传入userProfileView\n```\nfunc listViewDidScroll(_ scrollView: UIScrollView) {\n        userProfileView.listViewDidScroll(scrollView: scrollView)\n    }\n```\n\n  不用一分钟，就可以集成完毕👏\n\n## Github\n[仓库地址](https://github.com/pujiaxin33/JXUserProfileView) 如果你喜欢，记得点颗❤️哟\n\n## 推荐阅读\n[SGPagingView](https://github.com/kingsic/SGPagingView)\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpujiaxin33%2Fjxuserprofileview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpujiaxin33%2Fjxuserprofileview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpujiaxin33%2Fjxuserprofileview/lists"}