Comment désactiver la copie, couper, sélectionner, sélectionner tout dans UITextView

110

La fonctionnalité UITextViewCopier, Couper, Sélectionner, Tout sélectionner de s s'affiche par défaut lorsque j'appuie sur l'écran. Mais, dans mon projet, le UITextFieldn'est en lecture seule. Je n'ai pas besoin de cette fonctionnalité. Veuillez me dire comment désactiver cette fonctionnalité.

Aishwarya
la source
3
place [UIMenuController sharedMenuController] .menuVisible = NO; in - (BOOL) canPerformAction: action (SEL) withSender: méthode de l'expéditeur (id).
ravoorinandan

Réponses:

108

Le moyen le plus simple de désactiver les opérations de la table de montage consiste à créer une sous-classe de UITextViewcelle qui remplace la canPerformAction:withSender:méthode à renvoyer NOpour les actions que vous ne souhaitez pas autoriser:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    if (action == @selector(paste:))
        return NO;
    return [super canPerformAction:action withSender:sender];
}

Voir également UIResponder

rpetrich
la source
1
@rpetrichm, j'ai utilisé votre solution, copier / coller / couper / sélectionner / sélectionner Toutes les options sont désactivées mais il y a encore à venir Remplacer ... | BIU | Définir les options. Je veux désactiver ce menu complet.
Ameet Dhas
3
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; }- pour bloquer toutes les options
Islam Q.
C'est génial, mais je ne sais pas comment faire cela pour UISearchBar - comme il y a aussi un champ de texte à l'intérieur, j'ai pensé que je pourrais écraser la méthode d'une sous-classe d'UISearchBar mais cela ne fonctionne malheureusement pas. Des idées pour ça?
borchero
j'ai de nombreux contrôles. comment autoriser le copier-coller sur un seul contrôle?
Gaucho
Cela ne fonctionne pas pour UITextView, même si j'ai vu cette solution suggérée à plusieurs endroits sur SO. Quelqu'un a-t-il compris comment faire ça?
Pigpocket
67

Sous-classe UITextView et écraser canBecomeFirstResponder:

- (BOOL)canBecomeFirstResponder {
    return NO;
}

Notez que cela ne s'applique qu'aux UITextViews non modifiables! Je ne l'ai pas testé sur des modifiables ...

iCoder
la source
Je pense que return NO;sur la - (BOOL)canPerformAction:(SEL)action withSender:(id)senderméthode est une meilleure option.
Islam Q.
29

Si vous souhaitez désactiver le couper / copier / coller sur l' ensemble UITextView de votre application, vous pouvez utiliser une catégorie avec:

@implementation UITextView (DisableCopyPaste)

- (BOOL)canBecomeFirstResponder
{
    return NO;
}

@end

Il enregistre un sous-classement ... :-)

Damien Debin
la source
3
vous pouvez également placer ceci uniquement dans votre fichier /, où vous avez besoin de ce comportement.
markus_p
4
Cela ne fonctionne que comme un effet secondaire et empêche le UITextViewde se comporter comme prévu lorsque, par exemple, il est modifiable et est touché. C'est beaucoup à remplacer canPerformAction:withSender:; c'est à cela que sert le protocole.
jdc
16
Vous ne pouvez pas remplacer en toute sécurité une méthode à l'aide d'une catégorie. Il s'agit d'un comportement indéfini. Vous devez effectuer une sous-classe pour remplacer une méthode en toute sécurité.
Rob Napier
2
Remarque: cela s'appliquera à tous les UITextViews de votre application. Pas idéal la plupart du temps.
bbrame
2
Notez également qu'Apple déconseille ceci : "Si le nom d'une méthode déclarée dans une catégorie est le même qu'une méthode de la classe d'origine, ou une méthode d'une autre catégorie sur la même classe (ou même une superclasse), le comportement est indéfini quant à l'implémentation de méthode utilisée au moment de l'exécution ... [et] peut poser des problèmes lors de l'utilisation de catégories pour ajouter des méthodes aux classes Cocoa ou Cocoa Touch standard. "
Rob
29

C'était la meilleure solution de travail pour moi:

UIView *overlay = [[UIView alloc] init];  
[overlay setFrame:CGRectMake(0, 0, myTextView.contentSize.width, myTextView.contentSize.height)];  
[myTextView addSubview:overlay];  
[overlay release];

depuis: https://stackoverflow.com/a/5704584/1293949

Saraiva Alcides
la source
3
gentil, pas de sous-classement ici!
Martin Reichl
7
J'adore l'approche du ruban adhésif et du fil de pressage ici. J'aimerais pouvoir encore vous attribuer
jdc
22

La réponse @rpetrich a fonctionné pour moi. Je poste le code développé au cas où cela permettrait à quelqu'un de gagner du temps.

Dans mon cas, je ne veux aucune fenêtre contextuelle, mais je veux que UITextField puisse devenir le premier répondeur.

Malheureusement, vous obtenez toujours la fenêtre contextuelle de la loupe lorsque vous maintenez le doigt sur le champ de texte.

@interface NoSelectTextField : UITextField

@end

@implementation NoSelectTextField

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    if (action == @selector(paste:) ||
        action == @selector(cut:) ||
        action == @selector(copy:) ||
        action == @selector(select:) ||
        action == @selector(selectAll:) ||
        action == @selector(delete:) ||
        action == @selector(makeTextWritingDirectionLeftToRight:) ||
        action == @selector(makeTextWritingDirectionRightToLeft:) ||
        action == @selector(toggleBoldface:) ||
        action == @selector(toggleItalics:) ||
        action == @selector(toggleUnderline:)
        ) {
            return NO;
    }
    return [super canPerformAction:action withSender:sender];
}

@end

Swift 4

class NoSelectTextField: UITextField {

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(paste(_:)) ||
            action == #selector(cut(_:)) ||
            action == #selector(copy(_:)) ||
            action == #selector(select(_:)) ||
            action == #selector(selectAll(_:)) ||
            action == #selector(delete(_:)) ||
            action == #selector(makeTextWritingDirectionLeftToRight(_:)) ||
            action == #selector(makeTextWritingDirectionRightToLeft(_:)) ||
            action == #selector(toggleBoldface(_:)) ||
            action == #selector(toggleItalics(_:)) ||
            action == #selector(toggleUnderline(_:)) {
            return false
        }
        return super.canPerformAction(action, withSender: sender)
    }

}
Devin Pitcher
la source
1
Veuillez publier une version rapide pour cela également. Merci.
Salman Khakwani
@pinch: tnx. Tient toujours l'eau dans iOS 12.1.3.
YvesLeBorg
20

Si vous n'avez pas besoin de UITextView pour faire défiler, la solution la plus simple qui n'implique pas de sous-classification consiste simplement à désactiver l'interaction de l'utilisateur pour la vue texte:

textField.userInteractionEnabled = NO;
Luke Redpath
la source
2
Cela enlève de taper sur les liens, etc. si c'est ce qui est dans la vue de texte. Il faut noter que ce n'est pas une bonne solution pour vouloir masquer la sélection / copier / coller, mais aussi garder un certain niveau d'interaction activé.
barfoon
3
Eh bien, j'aurais pensé que ce serait évident.
Luke Redpath
J'utilise des champs de texte comme étiquettes pour les images dans un jeu et je ne veux pas que la loupe apparaisse et il n'y a aucune raison de copier le texte. Cela fonctionne très bien pour moi. J'utilise du texte stylé dans un UIWebView et la même ligne y fonctionne également.
JScarry
15

Le moyen le plus simple consiste à créer une sous-classe de UITextView qui remplace le canPerformAction: withSender:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender    
{    
     [UIMenuController sharedMenuController].menuVisible = NO;  //do not display the menu
     [self resignFirstResponder];                      //do not allow the user to selected anything
     return NO;
}
haiLong
la source
C'est la meilleure solution pour pouvoir taper du texte mais rien d'autre, et permet aux tapotements sur le champ de texte de ne plus «intercepter». Cela fait ce que le PO voulait, et plus encore.
hlfcoding
13

Lorsque je retourne NON dans canPerformAction sur iOS 7, j'obtiens beaucoup d'erreurs comme celle-ci:

<Error>: CGContextSetFillColorWithColor: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.

Ma solution est la suivante:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
    }];
    return [super canPerformAction:action withSender:sender];
}

L'astuce consiste à masquer le contrôleur de menu dans le cycle suivant sur la file d'attente principale (juste après son affichage).

Adam Wallner
la source
vraiment sympa. le seul problème est que j'ai 2 textviews et un textField et je veux éviter le copier-coller uniquement sur les champs textView. comment identifier qui a appelé le canPerformAction? la variable de l'expéditeur est le UIMenuController
Gaucho
1
Cela fonctionne sur la sous-classe de UITextField (ou UITextView). Si vous n'utilisez pas la sous-classe que vous avez créée, cela n'aura aucun effet. Par exemple, j'ai créé un TextFieldWithoutCopyPaste et l'ai utilisé là où je ne voulais pas avoir de fonctionnalité de copier-coller.
Adam Wallner
ok, tu as raison. Mais dans mon cas, le textView a besoin d'une sous-classe pour utiliser canPerformAction et le textField a besoin d'une sous-classe pour utiliser textFieldDidBeginEditing afin d'animer la fenêtre lorsque le clavier est affiché. l'animation déplace la fenêtre avec le clavier. J'utilise cette méthode pour éviter que le clavier recouvre le textField.
Gaucho
10

C'est le moyen le plus simple de désactiver tout le menu Sélectionner / Copier / Coller dans un UITextView

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{    
    [UIMenuController sharedMenuController].menuVisible = NO;
    return NO;    
}
GL777
la source
Fonctionne également pour UITextField. Merci d'avoir partagé.
Gaurav Srivastava
1
J'utilise le code ci-dessus mais je reçois toujours le menu comment désactiver cela, veuillez m'aider.
Bittoo
4

Depuis iOS 7, il existe une propriété sur UITextView:

 @property(nonatomic,getter=isSelectable) BOOL selectable;

Cela empêche une vue d'autoriser les sélections de texte. Fonctionne très bien pour moi.

Epaga
la source
4

Si vous cherchez à remplacer le clavier par un, disons, UIPickercomme le inputView(avec bien sûr une barre d'outils comme un inputAccesotyView), alors cette solution de contournement pourrait vous aider ...

  • Mettre en place textFieldShouldBeginEditing:
  • à l'intérieur textField.userInteractionEnabled = NO;
  • Ensuite, lorsque vous êtes sur le point de fermer le UIPickerView, réglez-le sur OUI.

En faisant cela, vous pourrez appuyer sur UITextFieldet afficher les options à choisir UIPickerView, à ce UITextFieldstade, vous ne réagiriez en effet à aucun événement tactile (cela inclut le toucher et maintenir pour couper, copier et coller). Cependant, vous devez vous rappeler de le remettre sur OUI lorsque vous fermez votre, UIPickerViewmais vous ne pourrez plus y accéder UIPickerView.

Le seul moment où cela échoue est lorsque l'utilisateur commence par appuyer et maintenir le UITextView, alors vous verrez couper et coller à nouveau pour la première fois. C'est pourquoi vous devez toujours valider vos entrées. C'est le plus simple auquel je puisse penser. L'autre option était d'utiliser un UILabelpour le texte en lecture seule, mais vous manquez de nombreuses fonctionnalités intéressantes UITextView.

rn3sto
la source
4

Sous-classe UITextView - swift 4.0

     override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return false
    }
Manee ios
la source
Comment bizarre? il affiche toujours l'option "Coller" mais ne le faites pas si vous appuyez dessus ...
Flux iOS
4

Si vous souhaitez désactiver la fenêtre contextuelle, UITextFieldessayez cette UITextFieldDelegateméthode pour basculer isUserInteractionEnabled.

func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
    textField.isUserInteractionEnabled = false
    return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
    textField.isUserInteractionEnabled = true
    return true
}
Jayesh Thanki
la source
3

Cela peut être fait facilement dans le storyboard (Xcode 6). Décochez simplement Modifiable et sélectionnable dans l'inspecteur d'attributs. Vous pouvez toujours faire défiler la vue texte.entrez la description de l'image ici

Hari Kunwar
la source
3

Cela a fonctionné pour moi. Assurez-vous que vous appelez resignFirstResponder sur le textView

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
  [self.textView resignFirstResponder];
  return NO;
}
verma
la source
2

J'ai fourni une réponse de travail ici pour désactiver la sélection de texte + loupe, en gardant les liens clikables activés J'espère que cela aide:

Après avoir essayé pendant assez longtemps, j'ai réussi à arrêter la sélection de texte, l'agrandissement et le maintien de la détection des données (liens cliquables, etc.) en remplaçant addGestureRecognizer sur une sous-classe UITextView permettant uniquement à UILongPressGestureRecognizer de retarder la fin du toucher:

UIUnselectableTextView.m

-(void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
    if([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] && gestureRecognizer.delaysTouchesEnded)
    {
        [super addGestureRecognizer:gestureRecognizer];
    }
}
Thibaud David
la source
2

Pour Swift 3, il est changé en:

override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    return false
}
Andrey Gordeev
la source
2

Vous pouvez résoudre ce problème dans votre storyboard en décochant ces cases:

entrez la description de l'image ici

Ou vous pouvez définir par programme comme ceci:

textView.selectable = false
textView.editable = false
Khuong
la source
1

Je l'ai fait. Sur mon, UITextViewj'ai désactivé très facilement l'option couper, copier, sélectionner, etc.

J'ai placé un UIViewau même endroit où j'avais placé le UITextView, mais dessus self.viewet j'ai ajouté une touchDelegateméthode comme suit:

(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    UITouch *scrollTouch=[touches anyObject];
    if(scrollTouch.view.tag==1)
    {
        NSLog(@"viewTouched");
        if(scrollTouch.tapCount==1)
            [textView1 becomeFirstResponder];
        else if(scrollTouch.tapCount==2)
        {
            NSLog(@"double touch");
            return;
        }

    }
}

et cela a fonctionné pour moi. Je vous remercie.

Bill le lézard
la source
1

Rapide

textView.selectable = false // disable text selection (and thus copy/paste/etc)

en relation

textView.editable = false // text cannot be changed but can still be selected and copied
textView.userInteractionEnabled = false // disables all interaction, including scrolling, clicking on links, etc.
Suragch
la source
1

Si vous souhaitez ajouter une option personnalisée à votre UITextView mais désactiver les fonctions existantes, voici comment vous le faites sur Swift 3 :

Pour désactiver la fonction copier, coller, couper, créez une sous-classe et remplacez les éléments suivants:

override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    return false
} 

Sur le ViewController, votre CustomTextView ajoute ce qui suit pour ajouter vos options:

  let selectText = UIMenuItem(title: "Select", action: #selector(ViewController.selected))

    func selected() {

    if let selectedRange = textView.selectedTextRange, let 
     selectedText = textView.text(in: selectedRange) {

     }


    print("User selected text: \(selectedText)")

    }
Sam Bing
la source
1

Cette méthode désactivera complètement le menu Sélectionner, Tout sélectionner, Coller. Si vous obtenez encore une autre action, ajoutez-la simplement à la condition if comme ci-dessous.

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender // This is to disable select / copy / select all / paste menu
    {
        if (action == @selector(copy:) || action == @selector(selectAll:) || action == @selector(select:) || action == @selector(paste:))
            return NO;
        return [super canPerformAction:action withSender:sender];
    }
Sivrish Thangamani
la source
0

Vous pouvez simplement créer une catégorie comme celle-ci:

UITextView + Selectable.h

@interface UITextView (Selectable)

@property (nonatomic, assign, getter = isTextSelectable) bool textSelectable;

@end

UITextView + sélectionnable.m

#import "UITextView+Selectable.h"

#import <objc/runtime.h>

#define TEXT_SELECTABLE_PROPERTY_KEY @"textSelectablePropertyKey"

@implementation UITextView (Selectable)

@dynamic textSelectable;

-(void)setTextSelectable:(bool)textSelectable {
    objc_setAssociatedObject(self, TEXT_SELECTABLE_PROPERTY_KEY, [NSNumber numberWithBool:textSelectable], OBJC_ASSOCIATION_ASSIGN);
}

-(bool)isTextSelectable {
    return [objc_getAssociatedObject(self, TEXT_SELECTABLE_PROPERTY_KEY) boolValue];
}

-(bool)canBecomeFirstResponder {
    return [self isTextSelectable];
}

@end
utilisateur2091319
la source
1
Ce n'est pas une bonne façon de le résoudre. Tout d'abord cela affecte tout, car c'est dans la catégorie. Deuxièmement, l'utilisation d'objets associés pour une tâche plutôt facile peut poser plus de problèmes plus tard avec le débogage (pourquoi cela fonctionne-t-il comme ça lorsque vous oubliez ce que vous avez fait) que de générer des bénéfices. C'est bien de l'éviter autant que possible à mon avis. Rend le code moins facile à trouver, à déboguer et à comprendre par le nouveau programmeur du projet.
Vive le
0

Le sous UITextView-classement et le remplacement - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizersont une autre possibilité de désactiver les actions indésirables.

Utilisez la classe de gestureRecognizer-object pour décider si l'action doit être ajoutée ou non.

RhodanV5500
la source
0

(SWIFT) Si vous voulez juste un champ de texte de base sans aucune des options de menu ou de la loupe, créez une sous-classe de UITextField renvoyant false à gestureRecognizerShouldBegin:

class TextFieldBasic: UITextField {
    override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {

        return false
    }
}

Cela contournera toutes les fonctionnalités tactiles du champ de texte mais vous permettra toujours d'utiliser le clavier contextuel pour ajouter / supprimer des caractères.

Si vous utilisez un storyboard, affectez simplement la classe nouvellement créée au champ de texte ou si vous créez un champ de texte par programme:

var basicTextField = TextFieldBasic()
basic = basicTextField(frame: CGRectMake(10, 100, 100,35))
basic.backgroundColor = UIColor.redColor()
self.view.addSubview(basic)

basic.becomeFirstResponder()
Krivvenz
la source
0
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool 
{
    NSOperationQueue .mainQueue().addOperationWithBlock({ () -> Void in   

        [UIMenuController .sharedMenuController() .setMenuVisible(false, animated: true)]

    })
    return super.canPerformAction(action, withSender: sender)}
Nirav Ghori
la source
0

Swift 3

Pour ce faire, vous devez sous-classer votre UITextView et placer cette méthode.

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

        if (action == #selector(cut(_:))) {
            return false
        }

        if (action == #selector(paste(_:))) {
            return false
        }

        return super.canPerformAction(action, withSender: sender)
    }
Zaldy
la source
0

UITextView a deux propriétés qui feront ce dont vous avez besoin: isSelectable et isEditable .

En définissant isEditable sur false, vous éviterez à l'utilisateur de modifier le texte et en définissant isSelectable sur false, vous éviterez à l'utilisateur de sélectionner du texte dans votre textView, ce qui empêchera l'affichage du menu d'action.

L. Davì
la source
0

Veuillez trouver l'exemple de code pour référence:

 override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(copy(_:)) || action == #selector(paste(_:)) || action == #selector(UIResponderStandardEditActions.paste(_:)) ||
            action == #selector(replace(_:withText:)) ||
            action == #selector(UIResponderStandardEditActions.cut(_:)) ||
        action == #selector(UIResponderStandardEditActions.select(_:)) ||
        action == #selector(UIResponderStandardEditActions.selectAll(_:)) ||
        action == #selector(UIResponderStandardEditActions.delete(_:)) ||
        action == #selector(UIResponderStandardEditActions.makeTextWritingDirectionLeftToRight(_:)) ||
        action == #selector(UIResponderStandardEditActions.makeTextWritingDirectionRightToLeft(_:)) ||
        action == #selector(UIResponderStandardEditActions.toggleBoldface(_:)) ||
        action == #selector(UIResponderStandardEditActions.toggleItalics(_:)) ||
        action == #selector(UIResponderStandardEditActions.toggleUnderline(_:)) ||
        action == #selector(UIResponderStandardEditActions.increaseSize(_:)) ||
        action == #selector(UIResponderStandardEditActions.decreaseSize(_:))

       {
            return false
        }

        return true
    }
Sameen Ahmad
la source
-1

Utilisez func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { retrun bool }à la place de textFieldShouldBeginEditing.

class ViewController: UIViewController , UITextFieldDelegate {

    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        //Show date picker
        let datePicker = UIDatePicker()
        datePicker.datePickerMode = UIDatePickerMode.date
        textField.tag = 1
        textField.inputView = datePicker
    }

    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        if textField.tag == 1 {
            textField.text = ""
            return false
        }

        return true
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if textField.tag == 1 {
            textField.text = ""
            return false
        }

        return true
    }
}

Créez une nouvelle classe avec le nom StopPasteAction.swift

import UIKit

class StopPasteAction: UITextField {

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return false
    }
}

Ajouter la classe nouvelle classe avec votre TextField actuel

entrez la description de l'image ici

Anson
la source