Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 278 - ポップオーバー(popover)の考察

2017年5月1日:イメージを改訂
2017年5月10日:内容をまとめました

Segueで新しいMVC、ポップオーバーを作る説明に入ります。

Main.storyboard上にあるHappinessViewControllerの横に新しいUIViewControllerを設置(オブジェクト・ライブラリからドラッグ)します。このポップオーバーはsmiliness値の履歴を並べるだけです。いままでのように新しいUIViewControllerを継承したTextViewController.swiftを作成してから変更します

TextViewController上にText Viewもオブジェクト・ライブラからドラッグ&ドロップして配置します。HappinessViewControllerにNavigationControllerバーを設置してBar Button Itemもオブジェクト・ライブラからドラッグ&ドロップしてから「History」に改名します。ジェネリックなSegueを作るためにこのボタンからTextViewControllerに「Ctrl + ドラッグ」します。次のポップアップウインドウが出現します。

f:id:yataiblue:20170501201641j:plain

Present Ad Popover」を選べばストーリーボード上にSegueができます。

そしてSegueで重要なプロパティは2つ、identifierdestinationViewControllerです。

ここでSegueを選んでアトリビュート・インスペクタを選び、ユニーク値のidentifierを設定します。

ここでは「Show Diagnostic History」とします。

f:id:yataiblue:20170501202027j:plain

基本的にポップオーバーも普通の画面遷移の実装と同じようにできます。ストーリーボードで「Ctrl + ドラッグ」すればコードを書かなくてもセグエを作成できます。

override func prepare(for segue:UIStoryboardSegue, 
                         sender:Any!) {        
    if let identifier = segue.identifier {
        switch identifier {
            case History.SegueIdentifier:
            if let tvc = segue.destination
                             as? TextViewController {
                tvc.text = "\(diagnosticHistory)"
            default:
                break
            }
      }
}

Segueを使ってViewControllerを生成させるためにprepare(segue: sender:)をオーバーライドして実装するのが基本です。普通の画面遷移と同じ手順でTextViewController上のtextを表示させます。

このコードの中で、History.SegueIdentifierとdiagnosticHistoryにはちょっとしたプログラミングテクニックが使用されています。ユーザー定義の定数をまとめたり、NSUserDefaultsを使って履歴をまとめているのですが説明を省略します*1

これをiPadのシュミレーターで走らせると次のようになります。

f:id:yataiblue:20150416165237j:plain

なんとなく上手くいっているように見えます。しかし、これをiPhoneシュミレーターで走らせると

f:id:yataiblue:20150416165455j:plain

iPhoneでは完全に画面を占拠するModalタイプのSegueになってしまいます。

ここでサイズを変更するために少し工夫が必要になります。

ViewControllerにコードされたPopoverはiPhoneでモーダル(Modal)、画面全面を覆い尽くすViewを表示してしまうため、画面外をタップしてキャンセルすることができません。

そこでiPhoneの小さな画面で小さなポップオーバー画面を表示させるために工夫が必要になります。それは、「UIPopoverPresentationControllerDelegateプロトコールのメソッドを使用するということです。

私の理解した範囲で解説すれば、UIPopoverPresentationControllerクラスを使うために外部で利用(デリゲーション)するためのプロトコールUIPopoverPresentationControllerDelegateが用意されています。すると、UIPopoverPresentationControllerクラスの内容を知らなくても利用できるということになります。

ということで、上のコードの中枢部分にあたる次のコード

if let tvc = 
     segue.destinationViewController as? TextViewController {
  tvc.text = "\(diagnosticHistory)"
}

に次のようにコードを入れていきます。

if let tvc = segue.destination as? TextViewController {

    if let ppc = tvc.popoverPresentationController {
        ppc.delegate = self
    }
    tvc.text = "\(diagnosticHistory)"
}

UIPopoverPresentationControllerクラスのプロパティにdelegateがあり、ここに自分自身を持たせるということです。これでUIPopoverPresentationControllerを操作することができるのですが、プロトコールとデリゲーションの関係で説明した>ステップ5になります。

iOSフレームワークの利用は、プロトコールが基本というのはなんとなく理解できているので、「Swiftで遊ぼう! - 260 - プロトコールとデリゲーション ProtocolsとDelegation」の手順で実装します。

クラス宣言のところでプロトコールを組み込むのがステップ4です。

class DiagnosedHappinessViewController: HappinessViewController, 
                        UIPopoverPresentationControllerDelegate {

ということは具体的に操作するためのステップ6が必要です。

func adaptivePresentationStyle(for 
                      controller: UIPresentationController, 
                       traitCollection: UITraitCollection)
                                 -> UIModalPresentationStyle {
        return .none
}

これでランをさせると次のようになります。

f:id:yataiblue:20150417142344j:plain

それにしてもポップオーバーのサイズが大きいのでこの調整をしていきます。

本来ならデリゲート(デリゲートされた先*2の具体的な実装となる次のコードも基本的に全然わからないので調べないといけません。
}
このデリゲーションメソッドの戻り値のUIModalPresentationStyleですが、どうもStyleというキーワードが含まれているものはenum型というのがお決まりのようですね。ちょっと調べると、

enum UIModalPresentationStyle : Int {
    case fullScreen
    case pageSheet
    case formSheet
    case currentContext
    case custom
    case overFullScreen
    case overCurrentContext
    case popover
    case none
}

色々あるけど、.none以外はすべて画面をすべて覆い尽くします。

またポップオーバー画面のサイズをコンテンツに合わせる方法は、preferredContentSizeを使うので、TextViewControllerの中でオーバーライドします。

override var preferredContentSize: CGSize {
    get {
        if textView != nil && presentingViewController != nil {
            return textView.sizeThatFits(
            presentingViewController!.view.bounds.size)
        } else {
            return super.preferredContentSize
        }
    }
    set { super.preferredContentSize = newValue }
}

ViewControllerが持っているプロパティの書き換え(overriding)で変更すればいいんです。なんとなく分かるような分からないような(^_^;) それでもこのようなコーディングに慣れていく必要がありますね。

これでラン(Comd + R)させると

f:id:yataiblue:20150418094528j:plain

これでサイズが調整されました。

考察になっていないのですが、先に進むためLecture7を終わりにします。

*1:既に一部説明をしていますが詳しい解説の要望があれえば今後加えます。

*2:ここではDiagnosedHappinessViewControllerです