8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[iOS13]UILabelを長押ししてコピーしたい

Posted at

概要

UITextFieldはフォーカスを当てて長押しすると、コピーやカットなどのメニューが表示されますよね。
しかしUILabelはこの機能がついてないのでつけたいな、と。
検索してみるとすぐに実装方法がヒット!
その通りにやってみた。
........メニューが全然表示されない泣
3時間半くらい苦戦してようやく解決法を見つけたので備忘録としてまとめます。
またUIMenuControllerを調べていて、iOS13から新しくDeprecatedになっていた要素があったので紹介します。

環境

  • iOS 13.0
  • Xcode 11.0
  • Swift5

実装

① カスタムクラスの作成

UILabel長押しでメニューを表示させるには、まずUILabelを継承したカスタムクラスを作成します。

CopyableLabel.swift
import UIKit

class CopyableLabel: UILabel
{
    // これからここに書いていく
}

② 長押しできるように設定

ラベルを長押しして特定のイベントを発動できるように、
タップ検出のプロパティ isUserInteractionEnabled を有効化、
addGestureRecognizerでジェスチャーを追加します。
今回は長押ししたいのでUILongPressGestureRecognizerを使用します。

CopyableLabel.swift
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にすることです。
これをしないとメニューを表示できません。

CopyableLabel.swift
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でコピーアクションの許可をします。
もし自分でカスタマイズしたアクションを表示したい場合はここで制御可能です。

CopyableLabel.swift
class CopyableLabel: UILabel
{
    // 省略

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool
    {
        // コピーアクションだけ許可
        return action == #selector(UIResponderStandardEditActions.copy)
    }
}

⑤ ラベルのテキストコピー

メニューに表示された[コピー]を押したときに実行したい処理を書きます。
今回は単純にラベルのテキストをコピーしたいだけなので次のような書き方で終了です。

CopyableLabel.swift
class CopyableLabel: UILabel
{
    // 省略

    override func copy(_ sender: Any?)
    {
        UIPasteboard.general.string = text
    }
}

出来上がり

iOS13以降のメニュー開き方

冒頭にも書いたように、iOS13.0からメニューを開くメソッドがDeprecatedになってました。
先ほど③でsetTargetRectsetMenuVisibleでメニューの表示位置、いまだ開け!という指示をしたわけですが、
showMenuの1つでその2つを賄えるようになりました。
うん、簡単。ありがたい!

CopyableLabel.swift
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)
            }
        }
    }
}

参考

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?