質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.10%

PageViewController をスワイプしかけて辞めると、次にスワイプした時にインデックスが2つカウントアップしてしまう事への回避方法

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,485

taro_nii_chan

score 197

 やりたい事

UIPageViewController の子クラス PageViewController を作成し、extension の中で右にスワイプした時と左にスワイプさせたときで次のページもしくは前のページを表示させるようにしたいです。ページは要素数3つだけの配列にし、ループさせることでページがたくさんあるように見せようとしています。

 困っていること

毎回綺麗にスワイプをすればほぼ思い通りに動くのですが、ページをめくりかけて「やっぱや〜めた」をすると、次にスワイプした時に2ページ分進んでしまいます。
理由が全く分からないわけではないのですが、「めくりかけてやっぱやめた」に対して page だけでなく index も変わらないようにするにはどうしたらいいのかが分からなくて困っています。回避する方法がありましたらご教授願います。

後、「ほぼ」と言いましたが、先へスワイプする時に一旦戻って進む事がある、もしくはその逆があります(print 文で確認)。

//
//  PageViewController.swift
//  ColorSample
//

import UIKit

let count: Int = 1 << (6 * 3)

class PageViewController: UIPageViewController {

    var eachViewControllers: [EachViewController] = []
    var red: [Int] = []
    var green: [Int] = []
    var blue: [Int] = []
    var rgba: [UIColor] = []
    var index: Int = 1
    var lastPage: Int = 1

    override func viewDidLoad() {
        super.viewDidLoad()

        self.dataSource = self

        // 1677万色の色の配列(要素数:count個)を作る
        for i in 0..<count {
            red.append(Int(arc4random_uniform(256)))
            green.append(Int(arc4random_uniform(256)))
            blue.append(Int(arc4random_uniform(256)))
            rgba.append(UIColor(red: CGFloat(red[i]) / 255.0, green: CGFloat(green[i]) / 255.0, blue: CGFloat(blue[i]) / 255.0, alpha: CGFloat(1.0)))
        }

        // EachViewController を3つだけ作る
        for i in 0..<3 {
            let eachViewController = EachViewController()
            eachViewController.setIndexTo(i)
            eachViewController.setBackgroundRgba(rgba[i])
            eachViewControllers.append(eachViewController)
        }

        // 1番目の EachViewController をセットする
        setViewControllers([eachViewControllers[1]], direction: .reverse, animated: false, completion: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

extension PageViewController : UIPageViewControllerDataSource {

    // 次のページを表示する時に呼ばれる
    func pageViewController(_ pageViewController:
        UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

        // ページを取得する
        let page = eachViewControllers.index(of: viewController as! EachViewController)!

        // インデックスをセットする
        index = (index + 1) % count

        // 前後ののページの値をセットする
        let nextPage = (page + 1) % 3
        let prevPage = (page + 2) % 3

        // デバッグ
        print("index: \(index), lastPage: \(lastPage) , page: >> [\(prevPage) -> \(page) -> \(nextPage)]")

        lastPage = nextPage

        // 新しく表示するページに変数をセットする
        eachViewControllers[lastPage].setBackgroundRgba(rgba[index])
        eachViewControllers[lastPage].setIndexTo(index)

        // 要らなくなったページをリリースする処理をここに入れる


        // 新しいページを返す
        return eachViewControllers[lastPage]
    }

    // 前のページを表示する時に呼ばれる
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController:UIViewController) -> UIViewController? {

        // ページを取得する
        let page = eachViewControllers.index(of: viewController as! EachViewController)!

        // 前後ののページの値をセットする
        let nextPage = (page + 1) % 3
        let prevPage = (page + 2) % 3

        // インデックスをセットする
        index = (index - 1 + count) % count

        // デバッグ
        print("index: \(index), lastPage: \(lastPage) , page: << [\(prevPage) -> \(page) -> \(nextPage)]")

        lastPage = prevPage

        // 新しく表示するページに変数をセットする
        eachViewControllers[lastPage].setBackgroundRgba(rgba[index])
        eachViewControllers[lastPage].setIndexTo(index)

        // 要らなくなったページをリリースする処理をここに入れる


        // 新しいページを返す
        return eachViewControllers[lastPage]
    }
}

 補足

言葉が足りませんでした。

        eachViewControllers[lastPage].setBackgroundRgba(rgba[lastPage])
        eachViewControllers[lastPage].setIndexTo(lastPage)

だと3ページ分しか表示できません。

やりたいのは

let count: Int = 1 << (6 * 3)

と定義したとても大きい数(count)だけのページを表示させたいのです。
(その、何番目なのかを index で持たせようとしています)

でも、
EachViewController クラスのインスタンスを count 個作るとメモリを喰って大変ですよね。
なので、添字が 0, 1, 2 の3つのインスタンスを

        for i in 0..<3 {
            let eachViewController = EachViewController()
            eachViewController.setIndexTo(i)
            eachViewController.setBackgroundRgba(rgba[i])
            eachViewControllers.append(eachViewController)
        }


の様に作り、
その背景色(rgba)及びページ番号(index)だけを書き換えて使いまわすことで大きい数のページがあるかのように見せたいという事です。

最初に eachViewControllers[0] eachViewControllers[1] eachViewControllers[2]を作って真ん中の eachViewControllers[1] を表示させておき、
1ページ進んだら[0][1][2]、から  [1][2][0] へ(添字だけで書いてます)、さらに進んだら[2][0][1] と進めていくことで使いまわそうという考えです(戻る時も同様)。

どうしても言葉でうまく説明できないのがもどかしいのですが、少しでも伝わるでしょうか?

 8/27 (14:03) 追記

コメントにも書きましたが、
pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
は使わずに、意図している動きが出来ているように見えています。

//  PageViewController.swift
extension PageViewController : UIPageViewControllerDataSource {

    // 次のページを表示する時に呼ばれる
    func pageViewController(_ pageViewController:
        UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

        // ここにスワイプする前のページが入ってくるみたい(前回、スワイプ完了でも途中でやめても)
        let eachViewController = pageViewController.viewControllers?.first as! EachViewController

        // インデックスをインクリメントする
        index = (eachViewController.index + 1) % count

        // ページを取得する
        let page = eachViewControllers.index(of: viewController as! EachViewController)!

        // 次のページの値をセットする
        let nextPage = (page + 1) % 3

        // 新しく表示するページに変数をセットする
        eachViewControllers[nextPage].setBackgroundRgba(rgba[index])
        eachViewControllers[nextPage].setRgb(red: red[index], green: green[index], blue: blue[index])
        eachViewControllers[nextPage].setIndexTo(index)


        // 新しいページを返す
        return eachViewControllers[nextPage]
    }
}

で上手く行ってそうです。

let eachViewController = pageViewController.viewControllers?.first as! EachViewController
index = (eachViewController.index + 1) % count


がスワイプを途中でやめたときにインクリメントされない(経験則ですが)ようなので、
これを使ったのが良かったのかなと思っています。

勘違い等あるかも知れないので、閉めるのはまだにしておこうと思います。

気付いていない点、勘違いしてる点、
そもそもコードの書き方が汚い等ありましたら教えて下さい。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

どうしてこんなことをしようとしているのかわかりませんが、
ページ切り替え時に、新しく表示するEachViewControllerの背景色を
そのEachViewControllerが入っている配列番号に合わせてrgba配列から取り出したいなら、
そもそもindexという変数は不要で、

        eachViewControllers[lastPage].setBackgroundRgba(rgba[lastPage])
        eachViewControllers[lastPage].setIndexTo(lastPage)

のように、lastPageの番号の色を取り出したらいいと思うし、そもそも背景色の設定は
EachViewControllerの作成時に

        for i in 0..<3 {
            let eachViewController = EachViewController()
            eachViewController.setIndexTo(i)
            eachViewController.setBackgroundRgba(rgba[i])
            eachViewControllers.append(eachViewController)
        }


という形で配列番号に対応するする背景色を設定済みなので、
ページ切り替え時に背景色を設定し直す必要すらないように思います。


(8/24 21:40追記)

補足ありがとうございます。
やりたいことがわかりました。

そういうことであれば、
https://developer.apple.com/documentation/uikit/uipageviewcontrollerdelegate/1614090-pageviewcontroller
で説明されている、
UIPageViewControllerDelegatepageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)メソッドを使うとうまくいくと思います。

現在実装しているUIPageViewControllerDataSourceのメソッドは、
次に表示するビューコントローラーを教えてくれと言っているだけであり、
それが呼ばれたからと言って必ずページ遷移するわけではありません。

上記で紹介したメソッドを使えば、画面遷移のアニメーションが完了したタイミングで呼ばれ、
次の画面に遷移した時は、transitionCompletedパラメータがtrue、
次に遷移するのをやめて戻した時は、transitionCompletedパラメータがfalse
で呼ばれます。
これを使うとうまくいくと思います。

また、
http://qiita.com/eKushida/items/d4fe95576a86f9e1d9fb
の「ページ番号の取得」の例にあるように、このタイミングでviewControllers.firstを取得すれば、
アニメーションが完了して現在表示されているビューコントローラーを得ることができますので、
そのビューコントローラーのプロパティにページ番号を入れておけば、
現在表示されているビューコントローラーのページ番号を知ることもできます。
このページ番号に相当しているのが、今回の質問ではindexなのだと思います。


(8/28 2:10追記)

確かに、EachViewControllerにindexを入れておけば、それで十分だと思います。

またStripeさんがおっしゃっている通り、それを使えばPageViewControllerの
indexプロパティは不要になるので、削除してしまえばよいと思います。

さらに、EachViewControllerを使い回す場合は2個で十分という話もありますが、
ページ切り替えに速度を求めないなら、使い回しでさえ不要で、ページ切り替え毎に
EachViewControllerを新しく作ればいいという話もあります。

(サンプル)

extension PageViewController : UIPageViewControllerDataSource {

    func pageViewController(_ pageViewController:
        UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return nextPage(pageViewController, moveTo:1)
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController:UIViewController) -> UIViewController? {
        return nextPage(pageViewController, moveTo:-1)
    }

    func nextPage(_ pageViewController: UIPageViewController, moveTo:Int) -> EachViewController {
        let currentPage = pageViewController.viewControllers?.first as! EachViewController
        let newIndex = (currentPage.index + moveTo) % count
        let newPage = EachViewController()
        newPage.setBackgroundRgba(rgba[newIndex])
        newPage.setIndexTo(newIndex)
        return newPage
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/27 14:04

    質問に追記いたしました。
    よろしければご覧くださいませ。

    キャンセル

  • 2017/08/28 02:15

    回答追記しました。

    キャンセル

  • 2017/08/28 08:06

    なるほどです。

    実は EachViewController を2つないし3つで実行すると、スワイプを連打すると真っ白なページになることがありました。その場面でちょこっとスワイプしかけて戻ると色のついたページが表示されていました。それが使いまわしなしだと綺麗に表示されます。「ページ切り替えに速度を求めないなら」とのことでしたけど、むしろこっちの方が高速に対応してるように見えました。

    なのでこれで行こうと思います。

    この質問に回答していただいて私としてはとても勉強になりました。
    お二人にベストアンサーをさしあげたい気持ちでいっぱいです。

    ありがとうございました。

    キャンセル

+1

まず、EachViewControllerを使いまわしたいなら、最低2個あれば足りると思います。

次に、EachViewControllerindexプロパティがあるのだから、PageViewControllerindex変数を持つ必要はありません。
EachViewControllerindexと、PageViewControllerindexで整合が取れなくなってしまっているのが、今回の問題点です。
indexは以下のように更新すれば良いです。

eachViewController.index = (eachViewController.index + 1) % count

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/27 16:16

    なるほどです。

    EachViewController は表に出てるのと、隠れてる裏のとの2つで良かったんですね。
    PageViewController が index を持っていなければいけないというのも先入観でした。

    動作確認済みです。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • Swiftに関する質問
  • PageViewController をスワイプしかけて辞めると、次にスワイプした時にインデックスが2つカウントアップしてしまう事への回避方法