色々悩み結局寄り道・・・
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() は要定義。