技術ブログ

プログラミング、IT関連の記事中心

セミモーダルビューを追加する方法【Swift】

■はじめに

本記事では、「FloatingPanel」のライブラリを使用させて頂き、セミモーダルビューを表示する方法をまとめています。
ライブラリのインストールにはCocoaPodsを使用するため、環境ができていない方は「CocoaPodsのインストール【iOS】」にて環境構築をしてください。

■手順

ソースコードは、こちらにアップしていますので、とりあえず動かしてみたい方はクローンして確認してください。

以下のように、下からニュッと出てきて、ドラッグして上げたり下げたりできるViewのことをセミモーダルビューと言うようです。

本手順では、以下のセミモーダルビューの作成方法をまとめています。

以下をPodfileに追加して、インストールする

pod 'FloatingPanel'

セミモーダルビューに表示するUIViewControllerを作成します。
ここでは「SemiModalViewController」として作成しています。

import UIKit

class SemiModalViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // とりあえず背景色のみ設定
        self.view.backgroundColor = UIColor.orange
    }
}

セミモーダルビューのサイズや背景の透明度などの定義をカスタマイズするために「FloatingPanelLayout」を継承したカスタムクラスを作成します。
ここでは「CustomFloatingPanelLayout」として作成しています。

import UIKit
import FloatingPanel

class CustomFloatingPanelLayout: FloatingPanelLayout {
    
    // ここで、最初に表示するパネルの高さを設定する
    var initialPosition: FloatingPanelPosition {
        return .half
    }

    /// セミモーダルビューの高さを設定する処理
    /// - Parameter position: position description
    func insetFor(position: FloatingPanelPosition) -> CGFloat? {
        switch position {
            case .full:
                return 56.0
            case .half:
                return 262.0
            case .tip:
                return 100.0
            case .hidden:
                return 0
        }
    }

    /// セミモーダルビュー表示中の背景の透明度(0だと通常表示で、1だと真っ暗)
    /// - Parameter position: position description
    func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
        return 0.0
    }
}

セミモーダルビューを表示するために、ViewControllerを作成していきます。
ViewControllerに「FloatingPanel」をインポートする。

import FloatingPanel

セミモーダルビューのController変数をフィールドに定義する。

var floatingPanelController: FloatingPanelController!

セミモーダルビューの初期化を行い、角を丸くする。
また、「SemiModalViewController」をセミモーダルビューで表示されるように設定する。

// セミモーダルビューの初期化
floatingPanelController = FloatingPanelController()
// 角を丸くする
floatingPanelController.surfaceView.cornerRadius = 24.0
// 「floatingPanelDidEndDragging」などが呼ばれるようにDelegateの設定
floatingPanelController.delegate = self

// セミモーダルビューとなるViewControllerをインスタンス化し、floatingPanelControllerに設定する
let semiModalViewController = SemiModalViewController()
floatingPanelController.set(contentViewController: semiModalViewController)

上記で、Delegateを追加したので、「FloatingPanelControllerDelegate」を継承に追加する。

class ViewController: UIViewController, FloatingPanelControllerDelegate {

セミモーダルビューの移動後に実行される関数「floatingPanelDidEndDragging」を定義し、「tip」の場合にはセミモーダルビューを閉じるように定義する。

/// セミモーダルビューの移動後に実行される処理(各種サイズに応じて処理を追加する
/// - Parameters:
///   - vc: vc description
///   - velocity: velocity description
///   - targetPosition: targetPosition description
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
    // セミモーダルビューの各表示パターンの高さに応じて処理を実行する
    switch targetPosition {
        case .tip:
            print("tip")
            // tipだった場合には、セミモーダルビューを閉じる
            floatingPanelController.removePanelFromParent(animated: true)
        case .half:
            print("half")
        case .full:
            print("full")
        case .hidden:
            print("hidden")
    }
}

セミモーダルビューのレイアウトをカスタムクラスから適応するように以下の関数を定義する。

/// セミモーダルビューのカスタムクラスの適応
/// - Parameters:
///   - vc: vc description
///   - newCollection: newCollection description
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
    return CustomFloatingPanelLayout()
}

これで、セミモーダルビューの定義はできたので、ボタン押下時などに以下の処理を実行し、セミモーダルビューを表示してみてください。

floatingPanelController.addPanel(toParent: self, belowView: nil, animated: true)

以下にViewControllerのサンプルを記載します。(セミモーダルビューの表示処理を含む)

import UIKit
import FloatingPanel

class ViewController: UIViewController, FloatingPanelControllerDelegate {
    
    // セミモーダルビューの変数
    var floatingPanelController: FloatingPanelController!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(type: .system)
        button.addTarget(self, action: #selector(btnAction(_:)), for: .touchUpInside)
        button.setTitle("セミモーダルビュー表示", for: .normal)
        button.sizeToFit()
        button.center = self.view.center
        self.view.addSubview(button)
        
        // セミモーダルビューの初期化
        floatingPanelController = FloatingPanelController()
        // 角を丸くする
        floatingPanelController.surfaceView.cornerRadius = 24.0
        // 「floatingPanelDidEndDragging」などが呼ばれるようにDelegateの設定
        floatingPanelController.delegate = self
        
        // セミモーダルビューとなるViewControllerをインスタンス化し、floatingPanelControllerに設定する
        let semiModalViewController = SemiModalViewController()
        floatingPanelController.set(contentViewController: semiModalViewController)
    }
    
    /// 「表示」ボタン押下時処理
    /// - Parameter sender: sender description
    @objc func btnAction(_ sender: Any) {
        // セミモーダルビューを表示する
        floatingPanelController.addPanel(toParent: self, belowView: nil, animated: true)
    }
    
    
    /// セミモーダルビューの移動後に実行される処理(各種サイズに応じて処理を追加する
    /// - Parameters:
    ///   - vc: vc description
    ///   - velocity: velocity description
    ///   - targetPosition: targetPosition description
    func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
        // セミモーダルビューの各表示パターンの高さに応じて処理を実行する
        switch targetPosition {
            case .tip:
                print("tip")
                // tipだった場合には、セミモーダルビューを閉じる
                floatingPanelController.removePanelFromParent(animated: true)
            case .half:
                print("half")
            case .full:
                print("full")
            case .hidden:
                print("hidden")
        }
    }
    
    /// セミモーダルビューのカスタムクラスの適応
    /// - Parameters:
    ///   - vc: vc description
    ///   - newCollection: newCollection description
    func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
        return CustomFloatingPanelLayout()
    }
}