概要
UITextFieldはフォーカスを当てて長押しすると、コピーやカットなどのメニューが表示されますよね。
しかしUILabelはこの機能がついてないのでつけたいな、と。
検索してみるとすぐに実装方法がヒット!
その通りにやってみた。
........メニューが全然表示されない泣
3時間半くらい苦戦してようやく解決法を見つけたので備忘録としてまとめます。
またUIMenuControllerを調べていて、iOS13から新しくDeprecated
になっていた要素があったので紹介します。
環境
- iOS 13.0
- Xcode 11.0
- Swift5
実装
① カスタムクラスの作成
UILabel長押しでメニューを表示させるには、まずUILabelを継承したカスタムクラスを作成します。
import UIKit
class CopyableLabel: UILabel
{
// これからここに書いていく
}
② 長押しできるように設定
ラベルを長押しして特定のイベントを発動できるように、
タップ検出のプロパティ isUserInteractionEnabled を有効化、
addGestureRecognizerでジェスチャーを追加します。
今回は長押ししたいのでUILongPressGestureRecognizerを使用します。
class CopyableLabel: UILabel
{
override init(frame: CGRect)
{
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
// Storyboardからの設定を無視したい場合は書く
setup()
}
private func setup()
{
isUserInteractionEnabled = true
// ジェスチャーの追加
addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:))))
}
/// 長押ししたら発動
///
/// - Parameter recognizer: UIGestureRecognizer
@objc func handleLongPressGesture(_ recognizer: UIGestureRecognizer)
{
// あとでここに追記する
}
}
③ メニューを開くよう設定
UIGestureRecognizerを引数に受けているので、
ジェスチャーが確認できた状態だけメニューを開くようにguard判定を挟みます。
しなくても大丈夫かもですが←
次にメニューを開く位置を設定します。
はじめメニューが表示されなかった理由はここで、ラベルのboundsをsetTargetRectに設定していましたが
それだといつまで経っても表示されず...
代わりにジェスチャーを受けたViewの位置を指定するとうまくいきました!
同じように苦戦した方いますかね汗
あとはsetMenuVisibleでメニューを表示します。
ここで一番大事なのは、canBecomeFirstResponderをoverrideしてtrue
にすることです。
これをしないとメニューを表示できません。
class CopyableLabel: UILabel
{
// 省略
override var canBecomeFirstResponder: Bool {
return true
}
/// 長押ししたら発動
///
/// - Parameter recognizer: UIGestureRecognizer
@objc func handleLongPressGesture(_ recognizer: UIGestureRecognizer)
{
// 状態判定
guard recognizer.state == .recognized else { return }
if let recognizerView = recognizer.view, let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
let menuController = UIMenuController.shared
menuController.setTargetRect(recognizerView.frame, in: recognizerSuperView)
menuController.setMenuVisible(true, animated: true)
}
}
}
④ メニューに表示する項目の設定
今回はコピー
だけで良いのでcanPerformActionでコピーアクションの許可をします。
もし自分でカスタマイズしたアクションを表示したい場合はここで制御可能です。
class CopyableLabel: UILabel
{
// 省略
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool
{
// コピーアクションだけ許可
return action == #selector(UIResponderStandardEditActions.copy)
}
}
⑤ ラベルのテキストコピー
メニューに表示された[コピー]を押したときに実行したい処理を書きます。
今回は単純にラベルのテキストをコピーしたいだけなので次のような書き方で終了です。
class CopyableLabel: UILabel
{
// 省略
override func copy(_ sender: Any?)
{
UIPasteboard.general.string = text
}
}
出来上がり
iOS13以降のメニュー開き方
冒頭にも書いたように、iOS13.0からメニューを開くメソッドがDeprecatedになってました。
先ほど③でsetTargetRect
とsetMenuVisible
でメニューの表示位置、いまだ開け!という指示をしたわけですが、
showMenuの1つでその2つを賄えるようになりました。
うん、簡単。ありがたい!
class CopyableLabel: UILabel
{
// 省略
/// 長押し検知
///
/// - Parameter recognizer: UIGestureRecognizer
@objc func handleLongPressGesture(_ recognizer: UIGestureRecognizer)
{
guard recognizer.state == .recognized else { return }
if let recognizerView = recognizer.view, let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
let menuController = UIMenuController.shared
if #available(iOS 13.0, *) {
menuController.showMenu(from: recognizerSuperView, rect: recognizerView.frame)
} else {
menuController.setTargetRect(recognizerView.frame, in: recognizerSuperView)
menuController.setMenuVisible(true, animated: true)
}
}
}
}