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

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

ただいまの
回答率

88.92%

SegmentedControlとスワイプの両方でページを切り替えたい

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 921

samson66

score 32

困っていること
UIPageViewControllerにEmbedしたViewControllerのSegmentedControlでページを切り替えることは出来るのですが、スワイプでページを切り替えようとすると以下のエラーが発生します。

エラー文
Fatal error: Unexpectedly found nil while unwrapping an Optional value
エラー発生場所
イメージ説明
コード全文

import UIKit

class MainViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

  let idList: [String] = ["first", "second", "third"]

  var pageViewController: UIPageViewController!
  var viewControllers: [UIViewController] = []

  @IBOutlet weak var selectTab: UISegmentedControl!

  override func viewDidLoad() {
    super.viewDidLoad()

    selectTab.setTitle("1枚目", forSegmentAt: 0)
    selectTab.setTitle("2枚目", forSegmentAt: 1)
    selectTab.setTitle("3枚目", forSegmentAt: 2)

    for id in idList {
      viewControllers.append((storyboard?.instantiateViewController(withIdentifier: id))!)
    }

    pageViewController = children[0] as? UIPageViewController
    pageViewController.setViewControllers([viewControllers[0]], direction: .forward, animated: true, completion: nil)

    pageViewController.dataSource = self
    pageViewController.delegate = self
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }

  func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
    if (index > 0) {
      print("before")
      return storyboard!.instantiateViewController(withIdentifier: idList[index - 1])
    }
    return nil
  }

  func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
    if (index < idList.count - 1) {
      print("after")
      return storyboard!.instantiateViewController(withIdentifier: idList[index + 1])
    }
    return nil
  }

  func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    let index = idList.firstIndex(of: (pageViewController.viewControllers?.first!.restorationIdentifier)!)
    self.selectTab.selectedSegmentIndex = index!
  }

  @IBAction func selectedTab(_ sender: UISegmentedControl) {
    switch sender.selectedSegmentIndex {
    case 0:
      pageViewController.setViewControllers([viewControllers[0]], direction: .reverse, animated: false, completion: nil)
      break
    case 1:
      pageViewController.setViewControllers([viewControllers[1]], direction: .reverse, animated: false, completion: nil)
      break
    case 2:
      pageViewController.setViewControllers([viewControllers[2]], direction: .forward, animated: false, completion: nil)
      break
    default:
      return
    }
  }
}


StoryBoard
イメージ説明

エラーの原因がわかりません。
どのようにすればスワイプでも動くようになるのでしょうか?m(_ _)m

追記

import UIKit

class PageViewController: UIPageViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
      self.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
      self.dataSource = self
    }

  func getFirst() -> FirstViewController {
    return storyboard!.instantiateViewController(withIdentifier: "first") as! FirstViewController
  }

  func getSecond() -> SecondViewController {
    return storyboard!.instantiateViewController(withIdentifier: "second") as! SecondViewController
  }

  func getThird() -> ThirdViewController {
    return storyboard!.instantiateViewController(withIdentifier: "third") as! ThirdViewController
  }

}

extension PageViewController : UIPageViewControllerDataSource {

  func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

    if viewController.isKind(of: ThirdViewController.self) {
      return getSecond()
    } else if viewController.isKind(of: SecondViewController.self) {
      return getFirst()
    } else {
      return nil
    }
  }

  func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

    if viewController.isKind(of: FirstViewController.self) {
      return getSecond()
    } else if viewController.isKind(of: SecondViewController.self) {
      return getThird()
    } else {
      return nil
    }
  }

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • samson66

    2019/09/22 12:24 編集

    コード自体は自分が一から書いた訳ではなく検索して載っているものを参考にしてきたので薄い知識の状態で動かしていますm(_ _)m
    segmentcontrolとスワイプの両輪で以前教えていただいたTabmanのタブ切り替えのようなものを自前で実装しようとしたのですが、日本語の最新の情報が限られていたのでそこから引用しています。
    restorationIdentifierはこちらのコードを参考にしています。
    https://teratail.com/questions/184661

    キャンセル

  • hayabusabusash

    2019/09/22 22:17

    すみません遅くなりました。
    なるほど、とりあえずrestorationIdentifierがnilになっていてfirstIndex()を強制アンラップして落ちている可能性が高いような気がします。

    なのでPageViewControllerに表示させているViewControllerにrestorationIdentifierではなく、
    identifierみたいなStringの変数を作ってそれで判定するのはどうでしょうか?

    キャンセル

  • samson66

    2019/09/23 11:23

    何度もごめんなさい、
    pageViewController(_: viewControllerBefore:)
    pageViewController(_: viewControllerAfter:)
    の仕組みを
    https://qiita.com/yajamon/items/e1754e7fc847b595c26a
    の図解の部分で何となく機能は理解はできたのですが、
    中身に書かれているコードは人が書いたのを見て何となく動きを把握している状態です。

    中身のコードのどこの部分をString型の変数で置き換えればよいのかがわからないです。
    .restorationIdentifierの部分に変数を置いたところviewControllerが持っていない値だという
    警告文が表示されました。

    //pageViewController(_: viewControllerBefore:)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
    if (index > 0) {
    return storyboard!.instantiateViewController(withIdentifier: idList[index - 1])
    }
    return nil
    }

    キャンセル

回答 1

0

PageViewControllerに表示している
firstsecondthirdのStoryboard identifierを使って生成しているViewControllerがあると思います。
そのViewControllerに適当な変数を作ってしまえばいいと思います。

class FirstViewController: UIViewController {

    let pageIdentifier: String = "first"

    // 省略します
}

この変数をrestorationIdentifierの部分に置き換えてしまえばいいと思います。
ただし、PageViewControllerのデリゲートメソッドの引数のViewControllerをキャストしないとダメです。

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    // UIViewControllerのままだと変数にアクセスできないのでキャストします
    if let firstViewController = viewController as? FirstViewController {
        // 作った変数を使ってindexを取得
        let index = idList.firstIndex(of: firstViewController.pageIdentifier)!
        if (index > 0) {
          print("before")
          return storyboard!.instantiateViewController(withIdentifier: idList[index - 1])
        }
        return nil
    }
    // 以降必要な分だけ分岐
  }

このままだとキャストの分岐が長くなってしまうので、
PageViewControllerに表示するViewControllerに共通のViewControllerを作って、
それを継承させる形にするともう少し短く書けるかもしれません。

// 共通のViewController
class PageChildViewController: UIViewController {

    var pageIdentifier: String = ""
}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る