iOSにNSPopupButtonが無いので…(その2)

色々悩み結局寄り道・・・

NSPopupButton が駄目なら UIPopoverController があるじゃ無い。

というか存在初めて知りましたが。

以下のサイトほぼまるコピになってしまった・・・。ありがとうございます。

[iOS, Swift2] アクションボタンの作り方 : 永遠日誌

上記サイトを参考にしつつ作ったクラスをざっくりと説明すると、

UIViewController (& UIPopoverPresentationControllerDelegate) → UIStackView → UIButton

でボタンを並べてメニューがわりに使います。

以下自前コード。

import UIKit
// Optional extension only for highlight when button is selected.
extension UIColor {
    func createImageFromColor()->UIImage {
        let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(self.cgColor)
        context?.fill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }
}

// Main
class PopoverMenuController: UIViewController, UIPopoverPresentationControllerDelegate {
    private var stackView:UIStackView! = nil
    var viewSize: CGSize = CGSize()
    var axis: UILayoutConstraintAxis = .vertical    
    var isVisible: Bool {
        get {
            return self.view.window != nil
        }
    }
    override var preferredContentSize: CGSize {
        get {
            return CGSize(width:self.viewSize.width, height:self.viewSize.height)
        }
        set {
            print("Cannot set preferredContentSize of this view controller.")
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        self.stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: self.viewSize.width, height: self.viewSize.height))
        self.stackView.backgroundColor = UIColor.white
        self.stackView.axis = self.axis
        self.stackView.distribution = .fillEqually
        self.view.addSubview(self.stackView)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
        return .none
    }
    func prepare(at sender: Any?) {
        self.modalPresentationStyle = .popover
        if let popover = self.popoverPresentationController {
            popover.permittedArrowDirections = .down
            if let view = sender as? UIView {
                popover.sourceView = view
                popover.sourceRect = view.bounds
            } else if let recognizer = sender as? UIGestureRecognizer {
                if let view = recognizer.view {
                    popover.sourceView = view
                    popover.sourceRect = view.bounds
                }
            }
            popover.delegate = self
        }
    }
    func addItem(withTitle: String)->UIButton {
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
        button.setTitle(withTitle, for: .normal)
        button.setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 1.0), for: .normal)
        button.addTarget(self, action: #selector(dismissPopover), for: .touchUpInside)
        // 2 lines below are optional to highlight
        button.setTitleColor(UIColor.white, for: .highlighted)
        button.setBackgroundImage(UIColor(red: 0, green: 122 / 255, blue: 1, alpha: 1).createImageFromColor() , for: .highlighted)
        self.stackView.addArrangedSubview(button)
        return button
    }
    func dismissPopover() {
        self.dismiss(animated: false)
    }
}

完成品はこちら。

ボタンが押されたタイミングで画面を閉じるべく追加の addTarget をボタンに紐付け。もっと他に良いやり方がある気はする。

UIPopoverController が閉じられる時に以下の Warning が表示される。

This will cause the effect to appear broken until opacity returns to 1.

StackOverflowとか見ると、気にすんな、ってコメントがあった。

が、気になる・・・。

せめてボタンを押した時くらい抑制しようと、閉じる用に追加でした自前 dismissPopover() を呼んで、中で animation: false 指定。

ポップアップ外の時の Warning は抑制の仕方がよくわからない。どのイベントでハンドルすれば良いか特定できず。(override func dismiss()では駄目だった)

呼び側のコード

let menu = PopoverMenuController()
menu.prepare(at: sender)
menu.viewSize = CGSize(width: 200, height: 100)
self.present(menu, animated: true, completion: {
    var button = UIButton()
    button = menu.addItem(withTitle: "foo")
    button.addTarget(self, action: #selector(self.action1), for: .touchUpInside)
    button = menu.addItem(withTitle: "bar")
    button.addTarget(self, action: #selector(self.action2), for: .touchUpInside)
    button = menu.addItem(withTitle: "baz")
    button.addTarget(self, action: #selector(self.action3), for: .touchUpInside)
})

上記の呼び側にボタンが押された後の処理用途に、別途 func action1(), func action2(), func action3() は要定義。

フォローする