【问题标题】:Show iPhone cut copy paste menu on UILabel在 UILabel 上显示 iPhone 剪切复制粘贴菜单
【发布时间】:2010-11-17 18:53:38
【问题描述】:
  1. 我们能否像 UITextField 一样为 UILabel 启用剪切复制粘贴菜单?

  2. 如果没有,我需要将我的UILabel 转换为UITextField,如何启用剪切复制粘贴菜单并且不允许修改内容?

【问题讨论】:

  • 选项 2 运气好吗?我目前正在尝试连接一个 UILabel 子类来支持一个非常简单的复制菜单选项,这不是一个特别直接的过程。
  • @BillyGray 十年后,现在这很容易 - 向下滚动到我用当前方法输入的最新答案。

标签: ios swift iphone xcode cocoa-touch


【解决方案1】:

对于 Swift 你必须实现这个类:

import UIKit

class CopyableLabel: UILabel {

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.sharedInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.sharedInit()
    }

    func sharedInit() {
        self.isUserInteractionEnabled = true
        let gesture = UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu))
        self.addGestureRecognizer(gesture)
    }

    @objc func showMenu(_ recognizer: UILongPressGestureRecognizer) {
        self.becomeFirstResponder()
    
        let menu = UIMenuController.shared
    
        let locationOfTouchInLabel = recognizer.location(in: self)

        if !menu.isMenuVisible {
            var rect = bounds
            rect.origin = locationOfTouchInLabel
            rect.size = CGSize(width: 1, height: 1)
        
            menu.showMenu(from: self, rect: rect)
        }
    }

    override func copy(_ sender: Any?) {
        let board = UIPasteboard.general
    
        board.string = text
    
        let menu = UIMenuController.shared
    
        menu.setMenuVisible(false, animated: true)
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return action == #selector(UIResponderStandardEditActions.copy)
    }
}

在您的故事板中,只需将 UILabel 子类化为 CopyableLabel

【讨论】:

【解决方案2】:

我在UILabel 上获得了复制和粘贴菜单,我只需要返回YES 以获得canBecomeFirstResponder,然后当所述标签出现在屏幕上时调用[label becomeFirstResponder]。至于从canBecomeFirstResponder返回YES,你可以创建一个自定义的子类或者使用类别修补UILabel

@implementation UILabel (Clipboard)

- (BOOL) canBecomeFirstResponder
{
    return YES;
}

@end

类别解决方案感觉有点老套,但如果您知道自己在做什么,它可能比子类化更容易。我还发布了一个sample project on GitHub,它展示了如何在UILabel 上显示一个简单的粘贴板菜单。

【讨论】:

【解决方案3】:

由于@zoul 的回答,sample project on github 是要走的路。在撰写本文时,该项目实际上并未在剪贴板(粘贴板)上放置任何内容。方法如下:

将此方法的@zoul 实现更改为:

- (void) copy:(id)sender {
    UIPasteboard *pboard = [UIPasteboard generalPasteboard];
    pboard.string = self.text;  
}

【讨论】:

    【解决方案4】:

    Swift 4 ☻ Xcode 9.2。 通过使用UIMenuController,我们可以做到。

    我创建了 IBDesignable 自定义 UILabel 类,您可以直接在情节提要上分配它

    @IBDesignable
    class TapAndCopyLabel: UILabel {
    
        override func awakeFromNib() {
            super.awakeFromNib()
            //1.Here i am Adding UILongPressGestureRecognizer by which copy popup will Appears
            let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
            self.addGestureRecognizer(gestureRecognizer)
            self.isUserInteractionEnabled = true
        }
    
        // MARK: - 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)
            }
        }
        //2.Returns a Boolean value indicating whether this object can become the first responder
        override var canBecomeFirstResponder: Bool {
            return true
        }
        //3.Here we are enabling copy action
        override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
            return (action == #selector(UIResponderStandardEditActions.copy(_:)))
    
        }
        // MARK: - UIResponderStandardEditActions
        override func copy(_ sender: Any?) {
            //4.copy current Text to the paste board
            UIPasteboard.general.string = text
        }
    }
    

    输出:

    【讨论】:

    • 你应该使用guard recognizer.state == .began else { return } 这样它会在持续时间之后显示菜单控制器,而不是在你抬起手指之后。用户在手指按下时期望得到反馈。
    • 效果很好,但同 Paul 所说的一样,将保护声明中的 .recognized 更改为 .began ,它会在您按下时弹出副本。谢谢!
    【解决方案5】:

    我制作了一个开源 UILabel 子类,它在长按时显示带有“复制”选项的 UIMenuController:

    HTCopyableLabel 在 GitHub 上

    【讨论】:

      【解决方案6】:

      如果有人仍然感兴趣,我已经分叉了 zoul 的示例项目并添加了对 ARC(以及其他一些功能)的支持:

      https://github.com/zhbrass/UILabel-Clipboard

      CopyLabel.h/.m 应该是您要查找的内容

      【讨论】:

        【解决方案7】:

        覆盖UITextField 实例的textFieldShouldBeginEditing 方法,并将其设置为返回NO 以禁用编辑。

        查看UITextFieldDelegate 协议了解更多详情。

        【讨论】:

        • 问题是:如果禁用编辑,复制和粘贴将不起作用。
        【解决方案8】:

        Swift 5.0Xcode 10.2

        直接在您的 ViewController 中将复制选项添加到您的 UILabel。

        //This is your UILabel
        @IBOutlet weak var lbl: UILabel!
        
        //In your viewDidLoad()
        self.lbl.isUserInteractionEnabled = true
        let longPress = UILongPressGestureRecognizer.init(target: self, action: #selector((longPressFunctin(_:))))
        self.lbl.addGestureRecognizer(longPress)
        
        //Write these all functions outside the viewDidLoad()
        @objc func longPressFunctin(_ gestureRecognizer: UILongPressGestureRecognizer) {
            lbl.becomeFirstResponder()
            let menu = UIMenuController.shared
            if !menu.isMenuVisible {
                menu.setTargetRect(CGRect(x: self.lbl.center.x, y: self.lbl.center.y, width: 0.0, height: 0.0), in: view)
                menu.setMenuVisible(true, animated: true)
            }
        }
        
        override func copy(_ sender: Any?) {
            let board = UIPasteboard.general
            board.string = lbl.text
        }
        
        override var canBecomeFirstResponder: Bool {
            return true
        }
        
        override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
            return action == #selector(copy(_:))
        }
        

        【讨论】:

        • 确实有帮助。谢谢
        【解决方案9】:

        Swift 5.3 和 SwiftUI

        为了在 SwiftUI 中实现这一点,我们可以使用 pableiros 创建与 UIViewRepresentable 组合的方法。

        我们需要对 CopyableLabel 类进行两项更新,因为以下方法在 iOS 13 中已弃用。

        .setTargetRect(_,in:)

        .setMenutVisible(_,animated)

        我们可以改用.showMenu(from:rect:) 方法轻松解决此问题。

        这是更新后的CopyableLabel 类。

        class CopyableLabel: UILabel {
        
            override init(frame: CGRect) {
                super.init(frame: frame)
                self.sharedInit()
            }
        
            required init?(coder aDecoder: NSCoder) {
                super.init(coder: aDecoder)
                self.sharedInit()
            }
        
            func sharedInit() {
                self.isUserInteractionEnabled = true
                self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu)))
            }
        
            @objc func showMenu(sender: AnyObject?) {
                self.becomeFirstResponder()
        
                let menu = UIMenuController.shared
        
                if !menu.isMenuVisible {
                    menu.showMenu(from: self, rect: self.bounds) // <-  we update the deprecated methods here
                }
            }
        
            override func copy(_ sender: Any?) {
                let board = UIPasteboard.general
        
                board.string = text
        
                let menu = UIMenuController.shared
        
                menu.showMenu(from: self, rect: self.bounds) // <- we update the deprecated methods here
            }
        
            override var canBecomeFirstResponder: Bool {
                return true
            }
        
            override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
                return action == #selector(UIResponderStandardEditActions.copy)
            }
        }
        

        然后为了让这个类与 SwiftUI 一起工作,我们所要做的就是创建一个简单的UIViewRepresentable

        struct CopyableLabelView: UIViewRepresentable {
        
            let text: String
            private let label = CopyableLabel(frame: .zero)
        
            init(text: String) {
                self.text = text
            }
        
            func makeUIView(context: Context) -> UILabel {
                // Set the text for the label
                label.text = text
        
                // Set the content hugging priority so the UILabel's view is
                // kept tight to the text.
                label.setContentHuggingPriority(.required, for: .horizontal)
                label.setContentHuggingPriority(.required, for: .vertical)
                return label
            }
        
            func updateUIView(_ uiView: UILabel, context: Context) {
                // Handle when the text that is passed changes
                uiView.text = text
            }
        }
          
        

        【讨论】:

          【解决方案10】:

          如果你有多行文本,你应该使用UITextView

          设置委托:

          func textView(_ textView: UITextView,
                        shouldChangeTextIn range: NSRange,
                        replacementText text: String) -> Bool {
              return false
          }
          

          它应该可以神奇地工作:)

          【讨论】:

          • 这并不总是正确的。我有一个带有多行文本的扩展单元格,并且文本视图在文本到达某个点后用于扩展单元格非常糟糕。在那种情况下,uilabels 会好很多
          【解决方案11】:

          2019 ...

          保存任何输入:

          public class SomeComplexCustomView: UIView {
          
              @IBOutlet var oneOfYourLabels: UILabel!
              ... your other labels, boxes, etc
          
              public func makeThatLabelCopyable() {
                  oneOfYourLabels.isUserInteractionEnabled = true
                  addGestureRecognizer(UITapGestureRecognizer(
                    target: self, action: #selector(self.copyMenu(sender:))))
                  addGestureRecognizer(UILongPressGestureRecognizer(
                    target: self, action: #selector(self.copyMenu(sender:))))
          
                  // or use oneOfYourLabels.addGesture... to touch just on that item 
              }
          
              public override var canBecomeFirstResponder: Bool { return true }
          
              @objc func copyMenu(sender: Any?) {
                  becomeFirstResponder()
                  UIMenuController.shared.setTargetRect(bounds, in: self)
                  // or any exact point you want the pointy box pointing to
                  UIMenuController.shared.setMenuVisible(true, animated: true)
              }
          
              override public func copy(_ sender: Any?) {
                  UIPasteboard.general.string = oneOfYourLabels.text
                  // or any exact text you wish
                  UIMenuController.shared.setMenuVisible(false, animated: true)
              }
          
              override public func canPerformAction(
                _ action: Selector, withSender sender: Any?) -> Bool {
                  return (action == #selector(copy(_:)))
              }
          }
          

          就这么简单!


          一个微妙之处:

          改善工程的一个细节:

          注意我们开启了第一响应者:

           public override var canBecomeFirstResponder: Bool { return true }
          

          通常,在带有此类标签的给定屏幕上,您将拥有或不会拥有这样的可复制链接。

          所以你很可能会有类似的东西:

          var linkTurnedOnCurrently: Bool = false
          
          func doShowThatLink( blah ) {
              linkAvailableOnThisScreen = true
              ... the various code above ...
          }
          
          func doShowThatLink( blah ) {
              linkAvailableOnThisScreen = false
              ... perhaps de-color the link, etc ...
          }
          

          因此,实际上不是这样:

           public override var canBecomeFirstResponder: Bool { return true }
          

          一定要这样做:

           public override var canBecomeFirstResponder: Bool {
              if linkTurnedOnCurrently { return true }
              return super.canBecomeFirstResponder
           }
          

          (请注意,它不是类似于“return linkTurnedOnCurrently”。)

          【讨论】:

          • 谢谢@rob!是的,这是一个很棒的公式。这是一个很好的例子,在 SO 上的答案可能已经过时了!这就是今天的方法,而且很简单,谢天谢地。
          • 其实@Rob,请看我在答案末尾添加的详细信息
          【解决方案12】:

          @benvolioT's github project 是非常好的复制示例。对于粘贴,自定义canPerformAction:withSender:。 有关更多信息,请参阅示例 CopyPasteTile

          【讨论】:

            猜你喜欢
            • 2010-12-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-05-13
            • 1970-01-01
            相关资源
            最近更新 更多