17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[iOS]子ViewControllerを動的に入れ替えるメモ

Posted at

参考

Switching Child View Controllers in iOS with Auto Layout

英語がわかるなら、上記のURLがすべて。

概要

下図の黄色のビューを、動的に入れ替える話。

スクリーンショット 2018-03-04 14.15.09.png

コード

ViewController.swift
import UIKit

class ViewController: UIViewController {
    enum CenterViewType {
        case First
        case Second
        case Third
        
        func id() -> String {
            switch self {
            case .First:
                return "First"
            case .Second:
                return "Second"
            case .Third:
                return "Third"
            }
        }
    }
    
    @IBAction func firstButtonTapped(_ sender: Any) {
        self.changeCenterView(.First)
    }
    
    @IBAction func secondButtonTapped(_ sender: Any) {
        self.changeCenterView(.Second)
    }

    @IBAction func thirdButtonTapped(_ sender: Any) {
        self.changeCenterView(.Third)
    }

    @IBOutlet weak var centerView: UIView!
    
    var currentViewController: UIViewController?

    func addSubview(_ subView:UIView, toView parentView:UIView) {
        parentView.addSubview(subView)
        
        var viewBindingsDict = [String: AnyObject]()
        viewBindingsDict["subView"] = subView
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "V:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
    }
    
    func changeCenterView(_ type: CenterViewType) {
        let identifier = type.id()
        let newViewController = self.storyboard?.instantiateViewController(withIdentifier: identifier)
        newViewController!.view.translatesAutoresizingMaskIntoConstraints = false
        self.cycleFromViewController(self.currentViewController!, toViewController: newViewController!)
        self.currentViewController = newViewController
    }
    
    func cycleFromViewController(_ oldViewController: UIViewController, toViewController newViewController: UIViewController) {
        oldViewController.willMove(toParentViewController: nil)
        self.addChildViewController(newViewController)
        self.addSubview(newViewController.view, toView:self.centerView!)
        // TODO: Set the starting state of your constraints here
        newViewController.view.layoutIfNeeded()
        
        // TODO: Set the ending state of your constraints here
        
        UIView.animate(withDuration: 0.5,
            animations: {
                // only need to call layoutIfNeeded here
                newViewController.view.layoutIfNeeded()
            },
            completion: { finished in
                oldViewController.view.removeFromSuperview()
                oldViewController.removeFromParentViewController()
                newViewController.didMove(toParentViewController: self)
            }
        )
    }
    
    override func viewDidLoad() {
        self.currentViewController = self.storyboard?.instantiateViewController(withIdentifier: CenterViewType.First.id())
        let view = self.currentViewController!.view!
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addChildViewController(self.currentViewController!)
        self.addSubview(self.currentViewController!.view, toView: self.centerView)

        super.viewDidLoad()
    }

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

コードの解説

"First","Second","Third"と表示されたボタンを押すと

    @IBAction func firstButtonTapped(_ sender: Any) {
        self.changeCenterView(.First)
    }
    
    @IBAction func secondButtonTapped(_ sender: Any) {
        self.changeCenterView(.Second)
    }

    @IBAction func thirdButtonTapped(_ sender: Any) {
        self.changeCenterView(.Third)
    }

それぞれ、上記のアクションが呼び出され、どれを選んでも changeCenterView() メソッドが呼び出される。

changeCenterView() では、

    func changeCenterView(_ type: CenterViewType) {
        let identifier = type.id()
        let newViewController = self.storyboard?.instantiateViewController(withIdentifier: identifier)
        newViewController!.view.translatesAutoresizingMaskIntoConstraints = false
        self.cycleFromViewController(self.currentViewController!, toViewController: newViewController!)
        self.currentViewController = newViewController
    }

storyboard id (identifier) から該当する子ViewControllerを生成し、 cycleFromViewController() メソッドを呼び出す。

    func cycleFromViewController(_ oldViewController: UIViewController, toViewController newViewController: UIViewController) {
        oldViewController.willMove(toParentViewController: nil)
        self.addChildViewController(newViewController)
        self.addSubview(newViewController.view, toView:self.centerView!)
        // TODO: Set the starting state of your constraints here
        newViewController.view.layoutIfNeeded()
        
        // TODO: Set the ending state of your constraints here
        
        UIView.animate(withDuration: 0.5,
            animations: {
                // only need to call layoutIfNeeded here
                newViewController.view.layoutIfNeeded()
            },
            completion: { finished in
                oldViewController.view.removeFromSuperview()
                oldViewController.removeFromParentViewController()
                newViewController.didMove(toParentViewController: self)
            }
        )
    }

cycleFromViewController() では古いViewControllerを新しいViewControllerと取り替える。
重要なのが、 addSubview(toView:) メソッド。

    func addSubview(_ subView:UIView, toView parentView:UIView) {
        parentView.addSubview(subView)
        
        var viewBindingsDict = [String: AnyObject]()
        viewBindingsDict["subView"] = subView
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "V:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
    }

addSubview(toView:) では、AutoLayoutを適切に設定してやることにより、子ViewControllerが元の黄色いView内にぴったり収まるようにしている(自力で子ViewControllerを入れ替えようとしてハマるのが、この部分)。

GitHubレポジトリ

出来上がったプロジェクトが以下。

https://github.com/JunSuzukiJapan/SwitchChildViewControllers

17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?