在 UILabel 上显示 iPhone 剪切复制粘贴菜单

Posted

技术标签:

【中文标题】在 UILabel 上显示 iPhone 剪切复制粘贴菜单【英文标题】:Show iPhone cut copy paste menu on UILabel 【发布时间】:2010-11-17 18:53:38 【问题描述】:

    我们能否像 UITextField 一样为 UILabel 启用剪切复制粘贴菜单?

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

【问题讨论】:

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

【讨论】:

固定目标矩形,gist.github.com/baryon/bd2d3e7fe1fa57d19dae1d88f7662c32【参考方案2】:

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

@implementation UILabel (Clipboard)

- (BOOL) canBecomeFirstResponder

    return YES;


@end

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

【讨论】:

我在 zoul 的 repo 中添加了一些额外的功能。 github.com/zhbrass/UILabel-Clipboard【参考方案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。

【讨论】:

以上是关于在 UILabel 上显示 iPhone 剪切复制粘贴菜单的主要内容,如果未能解决你的问题,请参考以下文章

UILabel.attributedText 不显示在 iPhone 模拟器上(但适用于 iPad 模拟器)

如何在iphone中突出显示UILabel的单词

UILabel.attributedText 不显示在 iPhone 4 + iOS 7.0.3

HTML5 完美解决javascript中iphone手机和android手机复制文本到剪切板问题

在 iOS 上本地化剪切|复制|粘贴菜单

iPhone 4S 与 iPhone 3GS 中的 UILabel