この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。きんくまです。
今回は前後が少し見えているバナー(カルーセルUI)を作りたい。です。
つくったもの
仕様
- 横スワイプでページをきりかえできる
- バナーの左右に、前のバナーと後ろのバナーが少し見える
- 画面下のページコントロールがバナーの位置に連動
- ページコントロールでもバナーをきりかえ
- 端末のサイズに応じて、比率で幅や高さを適用させる
- バナーをタップするイベントを取得して、どのバナーがタップされたかわかる
- バナーをスクロールして先頭に戻すことが可能
ソースコード
GitHubにアップしました
cm-tsmaeda / BannerViewSample
カスタマイズ
BannerScrollViewControllerのこのあたりは、デザイン上の値を設定します。
そうすると、その値と端末の横幅から比率を計算してレイアウトします。
/// バナーエリアの全体の幅
let baseComponentWidth: CGFloat = 406
/// デザインでのパネルの幅
let basePanelWidth: CGFloat = 355
/// デザインでのパネルの高さ
let basePanelImageHeight: CGFloat = 238
/// デザインでのテキスト部分の高さ
let basePanelTextContainerHeight: CGFloat = 30
/// デザインでのパネルの横マージン(先頭の左マージン)
lazy var basePanelHorizontalMargin: CGFloat = (baseComponentWidth - basePanelWidth) / 2
/// デザインでのパネル間のマージン
let basePanelGap: CGFloat = 8
実装のポイント
ページング
UIScrollViewの isPagingEnabled
をtrueにすると、ページングが可能になります。
ただし、ScrollViewのframeに合わせて表示されるので、前後はそのままだと表示されません。
なので、 clipsToBounds
をfalseにすることで、見えていなかった部分が見えるようになります。
しかしここで問題がおきます。
frameの外にある部分は当たり判定(hitTest)が機能しないのです。
で、それを解決しているのが以下の部分です。
BannerScrollViewController.swift
// frameの外はタッチイベントがきかない。その回避策
// https://stackoverflow.com/a/36641652
view.addGestureRecognizer(scrollView.panGestureRecognizer)
現在のページindexをもとめる
ScrollViewのcontentOffsetに応じて、現在のページindexを計算します。indexが変わったら自作のdelegateにイベントを発行します。
BannerScrollViewController.swift
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentIndex = pageIndex
let newIndex = calcPageIndex(contentOffsetX: scrollView.contentOffset.x)
if currentIndex != newIndex {
pageIndex = newIndex
delegate?.bannerScrollViewController(self, didChangePageIndex: newIndex)
}
}
/// 現在のページindexを返す
func calcPageIndex(contentOffsetX: CGFloat) -> Int {
// 0.5 - 1.5 の間は1で返す
// 1.5 - 2.5 の間は2で返す....
return Int(round(contentOffsetX / (panelWidth + panelGap)))
}
UIPageControlとの連動
- UIScrollViewでページがきりかわったらUIPageControlをきりかえる
- UIPageControlの値がかわったら、UIScrollViewのページをきりかえる
ということをやります。
ViewController.swift
func updateBannerPageControl() {
bannerPageControl.currentPage = bannerViewController.pageIndex
}
@IBAction func didChangeBannerPageControl() {
bannerViewController.showPage(index: bannerPageControl.currentPage, animated: true)
}
@IBAction func didTapResetButton() {
bannerViewController.showPage(index: 0, animated: true)
}
func bannerScrollViewController(_ scrollViewController: BannerScrollViewController, didChangePageIndex index: Int) {
updateBannerPageControl()
}
まとめ
バナーつくってみました。ではでは。
余談
実は一番ひっかかったところは、xibファイルとUIViewのクラスファイルのひもづけですw
(今回のメイン部分とは全く無関係)
- BannerView.xib
- BannerView.swift(UIViewのサブクラスで、BannerView.xibのClass設定で使う)
- BannerViewController.swift(BannerView.xibのClass設定では使わない)
という3つのファイルがあって、BannerViewをUINibからinitしたら実行時エラーがおきてクラッシュしまくり。
なんでかわからず、とほうにくれていたら、BannerViewをinitするときに、同じ名前のViewControllerがあると暗黙的にそっちにリンクすることを知りました。
そんなことを知らんがなーw
なので、BannerViewController.swiftの名前をBannerScrollViewControllerに変更しました。
気をつけよう!命名規則!!