NavigationBarのBarButtonItemにテキスト量に応じたサイズで吹き出しメッセージを表示する。

##はじめに

アプリ起動時にボタンの機能を簡単に紹介できる吹き出しが欲しいと思い、
NavigationBarのボタンからポップ表示できる吹き出しメッセージを作成。



完成形
![スクリーンショット 2021-01-16 15.37.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918084/534ce892-874a-bf92-8150-bff5bd4f71f4.png)


##準備
![スクリーンショット 2021-01-16 17.07.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918084/2136e70e-4dc2-3c86-9f5f-c0fc1a5ca3fc.png)

MainStoryBoardでNavigationControllerを追加します。
追加は、ViewControllerを選択して「Editor」→「Enbed In」→「Navigation Controller」

##コード

吹き出しviewの三角矢印部は後から追加されるため、完成したviewは指定したサイズより大きくなる。
今回のように上側矢印の場合はviewのzeroポイントに中身のパーツを配置すると
矢印上にパーツが配置されることになる。
それを防ぐために吹き出しのViewControllerのセーフエリアを取得してパーツを配置。




```ViewContoller.swift
import UIKit

class ViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    
    
    //ボタンの作成
    let actionButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionTapped(sender:)))
    
    //ナビゲーションバーに右ボタンを追加（２つ以上追加する場合）
    navigationItem.setRightBarButtonItems([editButtonItem,actionButton], animated: true)
    
    //一つだけの場合
    //navigationItem.rightBarButtonItem = actionButton
    
  }
  
  override func viewDidAppear(_ animated: Bool) {
    super .viewDidAppear(animated)
    
    //pop表示メソッドを実行
    showPopGuide()
    
  }
  
  //ガイド用吹き出しのPop表示メソッド
  func showPopGuide() {

    //吹き出しを表示するターゲットを指定
    if let target = navigationItem.rightBarButtonItems?[1] {
      
      
      let popVC = PopoverViewController()
      popVC.modalPresentationStyle = .popover
      //pop表示するViewのサイズを指定（今回は、文字量に応じて更新される）
      popVC.preferredContentSize = CGSize(width: 100, height: 100)
      //矢印を表示したいbarButtonの指定
      popVC.popoverPresentationController?.barButtonItem = target
      //矢印の方向を制限する
      popVC.popoverPresentationController?.permittedArrowDirections = .any
      //デリゲートの設定
      popVC.popoverPresentationController?.delegate = self
      
      popVC.text = "メッセージをポップ表示します。\nViewのサイズはテキストの量で\n自動調整されます。"
      
      //吹き出しを表示
      present(popVC, animated: true, completion: nil)
      
    }
  }
  
  
  //actionButtonをタップしたときに実行されるメソッド
  @objc func actionTapped(sender:UIBarButtonItem){
    
    print("ActionTapped")
  }
}


// MARK: - UIPopoverPresentationControllerDelegate

extension ViewController: UIPopoverPresentationControllerDelegate {
  
  // noneを返すことで、iPhoneでもpopover表示ができるようになる
  func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
    return .none
  }
  
  // Popoverの外をタップしたら閉じるべきかどうかを指定
  func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
    //trueを返すと、viewの外をタップした時に閉じる
    return true
  }
}


// MARK: - PopoverViewController （ポップ表示するViewController）

//ポップ用のViewController
class PopoverViewController: UIViewController{
  
  //表示するTextView
  let guideTextView = UITextView()
  
  var text:String = ""
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .cyan
    
    //textViewの設定
    guideTextView.isEditable = false
    guideTextView.isSelectable = false
    guideTextView.backgroundColor = .clear
    guideTextView.text = text
    view.addSubview(guideTextView)
    
    //このViewController自体がタップされたことを受け取れるように設定
    view.isUserInteractionEnabled = true
    //このViewControllerがタップされたときに実行するメソッドの指定（VCを閉じるのに使用）
    view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewTapped(sender:))))
    
    
  }
  
  
  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    //iOS13以降矢印のエリアを除外するにはSafeAreaを使うようにする
    //safeAreaが確定して使用できるようになるのはviewWillLayoutSubviews()以降
    let safeArea = view.safeAreaInsets
    
    //textViewの大きさを設定
    guideTextView.frame.size = preferredContentSize
    guideTextView.frame.origin = CGPoint(x:safeArea.left , y: safeArea.top)

  }
  
  
  //textViewの内容にサイズを合わせる(ViewControllerのプロパティをoverrideして書き換える)
  override var preferredContentSize: CGSize {
    get {
      if presentingViewController != nil {
        return guideTextView.sizeThatFits(presentingViewController!.view.bounds.size)
      } else {
        return super.preferredContentSize
      }
    }set {
      super.preferredContentSize = newValue
    }
  }
  
  
  //このView自体をタップした時に呼ばれるメソッド
  @objc func viewTapped(sender:UITapGestureRecognizer){
    
    print("viewtapped")
    //view自体を閉じる
    dismiss(animated: true, completion: nil)
  }
}