概要
インスタグラム,Tiktok,Twitterのマイページで見るようなページを実装してOSSとして公開しました。
TopContentPager とは?
上記のような複雑なページを簡単に作ることができるフレームワークです。
上記のようなページの特徴としては以下。
1. 中断にページのタブがあり、上にスクロールすることで上部に固定される
2. 全てのページで共通した縦スクロールが可能なヘッダーが存在する
3. 上にスクロールしてページタブが固定されている状態でページを切り替えると各スクロール量は維持されているが、1つのページで上部まで(ページタブが固定されない部分まで)スクロールした状態でページを切り替えると全てスクロールはトップまで戻ってきている
これらの複雑なロジックを考えることなく、ページの実装を実現できます。
使い方
導入
まずはライブラリをinstallしてください。
Podfileに下記を追加して pod install
をすればinstall完了です。
pod 'TopContentPager'
実装
使うものは
- ContentTableBody
- TopContentView
- TopContentPagerViewController
のみです。
親ViewControllerの作成
まず、大元となる親ViewControllerを実装します。
TopContentPagerViewController
クラスを継承した ParentViewController
を実装します。
ParentViewController
には TopContentPagerDataSource
を設定してください。
DataSourceには下記の二つの関数があります
func topContentPagerViewControllerTopContentView(_ viewController: TopContentPagerViewController) -> TopContentView
func topContentPagerViewControllerViewControllers(_ viewController: TopContentPagerViewController) -> [ContentTableBody]
それぞれ
-
TopContentView
を継承した共通のヘッダー部分になるView -
ContentTableBody
プロトコルの適応した各ページのViewになるViewControllerの配列
を返します。
上記の関数を設定したら、下記の関数でdataSourceをselfに設定してください。
func setupWillLoadDataSource()
これで以下のようになり、最低限の親ViewControllerの実装は完了です。
final class ParentViewController: TopContentPagerViewController {
override func setupWillLoadDataSource() {
super.setupWillLoadDataSource()
dataSource = self
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ParentViewController: TopContentPagerDataSource {
func topContentPagerViewControllerTopContentView(_ viewController: TopContentPagerViewController) -> TopContentView {
// return common HeaderView in all pages.
TopView()
}
func topContentPagerViewControllerViewControllers(_ viewController: TopContentPagerViewController) -> [ContentTableBody] {
// return ViewControllers for each page.
[Page1ViewController(), Page2ViewController(), Page3ViewController()]
}
}
共通のヘッダーViewの作成
次に共通となるヘッダーViewを作成します。
TopContentView
を継承したクラス TopView
を実装します。
final class TopView: TopContentView { }
はい、これだけです。あとはいつも通り普通にViewを作ってください。xibにしてもコードにしてもAutoLayoutを使うことを推奨します。
各ページのViewControllerの作成
最後にそれぞれのページとなるViewControllerを作っていきます。
ContentTableBody
を継承させたViewControllerを実装します。
ContentTableBody
の定義は以下です。
public protocol ContentTableBody: UIViewController {
var pagerItem: PagerItem { get }
var scrollView: UIScrollView! { get }
// 〜省略
}
pagerItem
はページのタブ部分のデザインを設定するものです。
.text
.image
.textAndImage
.custom
があり好きなものを設定してください。
詳細はここにあります。
-> https://github.com/itsukiss/TopContentPager#pageritem
scrollView
にはコンテンツとなるViewを設定してください、基本的には tableView
や collectionView
です。
下記が最低限設定した時の実装です。
class Page1ViewController: UIViewController, ContentTableBody {
var pagerItem: PagerItem = .text(.init(title: "ページ1"))
var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView! {
didSet {
scrollView = tableView
}
}
}
完成
これだけで上記のような共通のヘッダーがあるPagerを作ることができます。
上記の実装では必要最低限の説明しかしていませんので、READMEやサンプルプログラムを見ることをお勧めします。
特にサンプルプログラムはわかりやすく作ってますのでぜひ参考にしてください。
設計
View構造は下記のようになっていて、基本的には下記のような実装をしています。
(READMeにもかいてますが、 ContentTopProtocol
はもはや使われていません。 TopContentView
に置き換えて読んでください)
また、各スクロール時のViewの挙動はGifのアニメーションで表しているので、みていただけるとイメージがわくと思います。
- 横スクロール時に一番最前面のEscapeViewにTopContentViewを貼り付ける。
- 縦スクロール時にはContentTopCellに貼り付けることで縦スクロールのジェスチャも可能に
- ある一定以上までスクロールしたら、TopContentViewをEscapeViewに貼り付けページタブ部分だけをView内にみえるように
- TopContentViewではhitTestをoverrideして判定し、横方向のスクロールを制御しています。
- どこかのページの縦スクロールがTopContentViewまで到達したとき(つまり上部固定のヘッダーが表示された時)に全てのページのcontentOffsetを合わせるように
- これはことばにするとややこしいですが、上部コンテンツが共通の場合上部コンテンツが見えてる場合に横スクロールすると他のページのコンテンツがcontentOffsetを保持していると、TopContentViewに食い込むような形になるからです。
- 下のgifアニメーションの最後の「スクロールがどちらも下の方まで行われていた時を見ればわかります。
1.View構造/TopContentView内の横スクロール制御
2.スクロール時のViewの挙動
ここでは細かく説明しませんが、もし気になる方はコードを読んでみてください。
終わりに
今回はぱっと見て簡単にできそうだけど、やってみたら結構複雑なインスタグラムやtiktokのマイページのようなPagerを作りました。
困っている人の助けになればと思います。
もし何か質問や意見があれば、ガンガンください。