MoveLogというiOSアプリで、複数の位置情報が保存されている場合に、保存したデータ毎の移動時間と距離を表示出来るようにしました。移動履歴だとピンをタップした時にしか距離と時間を表示する事が出来なかったのですが、セミモーダルビューを表示する事で一覧として見れるようにしました。
この表示方法が半モーダルビューとかハーフモーダルビューとかどれが正式名称なのかが未だに分かっていないのですが、この記事ではセミモーダルビューとします。個人的にはひょっこりビューとよんでいます。今回はFloatingPanelを利用して実装をしましたので、利用方法を備忘録として残します。
FloatingPanelについて
セミモーダルビューはSwiftの標準ライブラリとしては用意されておりません。そのため、画面からアニメーションの処理を全て自前で設定する必要となりますが、本業でアプリを作っていない個人開発者にはとても荷が重いです。そのため、 FloatingPanel というOSSのライブラリを利用する事にしました。
利用方法
README にインストール手順が記載されています。
GitHub - SCENEE/FloatingPanel: A clean and easy-to-use floating panel UI component for iOS
私は Cocoa Pods を利用しているので、Podfile
に下記の設定を追加しました。
pod 'FloatingPanel'
ViewController
に FloatingPanelController
をセミモーダルとして追加します。今回はCustomFloatingPanelControllerを作る事にしました。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let fpc = CustomFloatingPanelController() fpc.addPanel(toParent: self) } }
下記のような CustomFloatingPanelController
を作成します。surfaceView.cornerRadius
を設定すればセミモーダルビューを角丸にする事が出来ます。set(contentViewController: xxxx)
で実際に描画するViewControllerを指定します。
import UIKit import FloatingPanel class CustomFloatingPanelController: FloatingPanelController { init() { super.init() self.delegate = self self.isRemovalInteractionEnabled = true self.surfaceView.cornerRadius = 24.0 // セミモーダルビュー上に表示するContent を設定する let content = UIViewController() content.view.backgroundColor = .cyan self.set(contentViewController: content) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
セミモーダルビューのレイアウトについては、FloatingPanelLayout
で設定する事が出来ます。今回はCustomFloatingPanelLayoutを作成しています。セミモーダルビューの表示は .full, .half, .tip, .hidden
の4パターンの表示サイズが設定出来ます。
class CustomFloatingPanelLayout: FloatingPanelLayout { // セミモーダルビューの表示の際の初期ポジション public var initialPosition: FloatingPanelPosition { return .tip } // 対応するポジションの一覧 public var supportedPositions: Set<FloatingPanelPosition> { return [.full, .half, .tip, .hidden] } // ポジション毎のサイズ設定 public func insetFor(position: FloatingPanelPosition) -> CGFloat? { switch position { case .full: return 50.0 // A top inset from safe area case .half: return 300.0 // A bottom inset from the safe area case .tip: return 100.0 // A bottom inset from the safe area default: return nil // Or `case .hidden: return nil` } } }
FloatingPanelControllerDelegate
で セミモーダルビューのイベントを検知する事が出来ます。もし、セミモーダルビュー上にTableViewを利用する場合は、floatingPanelDidEndDragging
のイベントが起きた時に Viewのサイズを可変にするようにしていないと、セルが最後まで表示されないといった事が発生するかと思います。FloatingPanelControllerに track
設定があるのでそれを使えばよいのかも?試してないですが…。
extension CustomFloatingPanelController: FloatingPanelControllerDelegate { func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { return CustomFloatingPanelLayout() } func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) { //targetPositionに応じて Viewのサイズを変更する } }
最終的にはこんな感じとなります。比較的簡単にセミモーダルビューが実現出来ていいですね。
おわりに
MoveLogのようなマップを使うアプリは画面のほとんどを地図で埋め尽くす必要があるので、どういう風に画面表示をしようかとわりと悩みます。セミモーダルビューも独自実装となると作れる気がしませんでしたが、FloatingPanel を利用する事で簡単に実装する事が出来てよかったです。コード関連の記事はあまり書く事ないので、どこまで書くべきなのか難しいですね。苦手意識しかない