カテゴリー: iOS

[Swift]ループできるページングビューをUIScrollViewで作ってみた

はじめに

こんにちは、nukkyです。

スライドショーやウォークスルーなどで何かしらのページングビューを使用するかとは思いますが、お手軽に使えるUIScrollViewだとループができないので、今回は自作でループができるように作ってみました!

前提条件

Xcode 9.x
iOS 11 Simulator
Swift 4.0

ページングビューとは

スクロールと違い任意の位置で止まるようなビューではなく、1ページ単位で切り替わるビューのことです、
今回作成する画面の完成イメージはこのようになります。

 

実装

Storyboardの準備

まずはStoryboardにUIScrollViewを用意します。

そうしたら、UIScrollViewの「indicators」と「Scrolling」を画像のように設定してください。

今回はページングなのでindicatorの表示はなくしています、それと忘れずに「Paging Enabled」にはチェックを入れてください。

それではこのUIScrollViewをViewControllerにリレーションしてください。

@IBOutlet weak var scrollView: UIScrollView!

 

コードの実装

まずは今回使用する変数を宣言しておきます。

// ページビューのラベルのタグ
static let LABEL_TAG = 100
// ページビューの背景色
private let colorArray: [UIColor] = [.red,.green,.blue,.yellow,.purple]
// 現在表示されているページ
private var page: Int = 0
// ScrollViewをスクロールする前の位置
private var startPoint: CGPoint!
// 表示するページビューの配列
private var pageViewArray: [UIView] = []

viewDidLoadでscrollViewのdelegateを設定します。

override func viewDidLoad() {
    super.viewDidLoad()
    scrollView.delegate = self
}

scrollViewに各ページのビューを生成します、オートレイアウトが適用されているviewDidAppear後が望ましいです、今回はviewDidAppear内で生成を行っています。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // scrollViewの表示サイズ
    let size = CGSize(width: scrollView.frame.size.width, height: scrollView.frame.size.height)
    // 5ページ分のcontentSize
    let contentRect = CGRect(x: 0, y: 0, width: size.width * CGFloat(5), height: size.height)
    let contentView = UIView(frame: contentRect)
    
    for i in 0..<5 {
        let pageView = UIView(frame: CGRect(x: size.width * CGFloat(i), y: 0, width: size.width, height: size.height))
        pageView.backgroundColor = colorArray[i]
        let label = UILabel()
        label.text = "View_\(i)"
        label.backgroundColor = .white
        label.sizeToFit()
        label.center = pageView.center
        label.frame.origin.x = 10
        // あとで使いたいのでtagを設定
        label.tag = ViewController.LABEL_TAG
        pageView.addSubview(label)
        contentView.addSubview(pageView)
        // あとで再描画をできるように保持
        pageViewArray.append(pageView)
    }
    // scrollViewに5ページ分のViewとサイズを設定する
    scrollView.addSubview(contentView)
    scrollView.contentSize = contentView.frame.size
    // 5つの真ん中のViewを初期位置に変更
    scrollView.contentOffset = CGPoint(x: ((size.width * 2)), y: 0)
    startPoint = scrollView.contentOffset;
    // 最初に表示するページ
    page = 0
    // 各ページの再描画
    setPageView()
}

// 各ページの再描画
func setPageView() {
    for i in 0..<pageViewArray.count {
        // 5つあるビューの真ん中が現在選択されているページになるようにする
        let index = getPageIndex(page: page + (i - 2))
        pageViewArray[i].backgroundColor = colorArray[index]
        // tagからラベルを取得しtextを再設定
        let label: UILabel = pageViewArray[i].viewWithTag(ViewController.LABEL_TAG) as! UILabel
        label.text = "View_\(index)"
        label.sizeToFit()
    }
}

// ページがpageViewArray.count以上や0以下になった時に適切な値を返す
func getPageIndex(page: Int) -> Int {
    var index = page
    if index < 0 {
        index = (pageViewArray.count + page)
    } else if index >= pageViewArray.count {
        index = page - pageViewArray.count
    }
    return index
}

ループの肝となるUIScrollViewDelegateを追加します。

extension ViewController: UIScrollViewDelegate {
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        // 左右のスワイプを判断するのでスクロール開始時に設定
        self.startPoint = scrollView.contentOffset;
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){
        if startPoint.x > scrollView.contentOffset.x {//左スワイプ
            pageChange(num: -1)
        } else if startPoint.x < scrollView.contentOffset.x {//右スワイプ
            pageChange(num: 1)
        }
        // scrollViewのスクロール位置を真ん中のビューに戻す
        let point = CGPoint(x: ((scrollView.frame.size.width * 2)), y: 0)
        scrollView.setContentOffset(point, animated: false)
        // pageが切り替わったのでビューを再描画
        setPageView()
    }
    
    // ページがループできるように適切な値をセットする
    private func pageChange(num:Int) {
        if page + num < 0 {
            page = pageViewArray.count - 1
        } else if page + num >= pageViewArray.count {
            page = 0
        } else {
            page = page + num
        }
    }
}

 

仕組み

駆け足でコードを貼ってしまいましたが、実際どのようにしてループさせているかというと以下のような流れです。

  1. まず5つ分のページのビューを用意します。
  2. このビューをUIScrollViewにaddSubViewします。
  3. 真ん中のビューが表示されるように位置を調整します。
  4. 真ん中のビューが初期のページになるように「1」のビューを更新します。
  5. UIScrollViewDelegateでスクロール終了を取得します。
  6. スクロール終了したら真ん中のビューが表示されるように位置を戻します。
  7. 「1」で用意したビューを更新して現在のページが真ん中に表示されるようにします。

「1」で用意した時のページの並びが[0,1,2,3,4]とした時に「4」のタイミングで0ページ目を初期位置にしたい場合ページの並びは[3,4,0,1,2]になります。

上記の状態で左スワイプをおこない「5」のタイミングで1ページ目が表示された場合、「7」のタイミングでページの並びを[4,0,1,2,3]に変更しビューの更新を行い、常に真ん中のビューに現在のページをセットすることで、ページングしているように見せつつループして表示することができます。

 

さいごに

UIScrollViewでのページングのループ処理どうでしょうか、今回は5つでサンプルを書きましたがViewの数は5つのままでページの要素を増やしていけば、メモリはView5つ分のままでまだまだページを増やすことも可能です!結構便利にできているのではないかと思うので同じようなものが欲しい方の力になれれば嬉しいです!

RE:ENGINESブログ「iOS記事」まとめページはこちらから

nukky

シェア
執筆者:
nukky
タグ: Swift

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前