Deaktivieren von Kopieren, Ausschneiden, Auswählen und Alles auswählen in UITextView


110

Die UITextViewFunktionen Kopieren, Ausschneiden, Auswählen, Alle auswählen werden standardmäßig angezeigt, wenn ich auf den Bildschirm drücke. Aber in meinem Projekt UITextFieldist das nur schreibgeschützt. Ich benötige diese Funktionalität nicht. Bitte sagen Sie mir, wie ich diese Funktion deaktivieren kann.


3
place [UIMenuController sharedMenuController] .menuVisible = NO; in - (BOOL) canPerformAction: (SEL) Aktion withSender: (id) Absendermethode.
Ravoorinandan

Antworten:


108

Der einfachste Weg, Pasteboard-Vorgänge zu deaktivieren, besteht darin, eine Unterklasse zu erstellen UITextView, die die canPerformAction:withSender:Methode überschreibt, die NOfür Aktionen zurückgegeben wird, die Sie nicht zulassen möchten:

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

Siehe auch UIResponder


1
@rpetrichm, ich habe Ihre Lösung verwendet. Kopieren / Einfügen / Ausschneiden / Auswählen / AuswählenAlle Optionen sind deaktiviert, aber es kommt noch Ersetzen ... | BIU | Optionen definieren. Ich möchte das komplette Menü deaktivieren.
Ameet Dhas

3
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; }- um alle Optionen zu blockieren
Islam Q.

Das ist großartig, aber ich weiß nicht, wie ich das für UISearchBar machen soll - da es auch ein Textfeld gibt, dachte ich, ich könnte die Methode der Unterklasse einer UISearchBar überschreiben, aber das funktioniert leider nicht. Irgendwelche Ideen dafür?
Borchero

Ich habe viele Kontrollen. Wie kann man Kopieren und Einfügen nur auf einem Steuerelement zulassen?
Gaucho

Dies funktioniert nicht für UITextView, obwohl ich diese Lösung an mehreren Stellen auf SO vorgeschlagen habe. Hat jemand herausgefunden, wie das geht?
Pigpocket

67

Unterklasse UITextView und Überschreiben von canBecomeFirstResponder:

- (BOOL)canBecomeFirstResponder {
    return NO;
}

Beachten Sie, dass dies nur für nicht bearbeitbare UITextViews gilt! Habe es nicht an bearbeitbaren getestet ...


Ich denke, return NO;auf - (BOOL)canPerformAction:(SEL)action withSender:(id)senderMethode ist eine bessere Option.
Islam Q.

29

Wenn Sie das Ausschneiden / Kopieren / Einfügen in Ihrer gesamten UITextView Anwendung deaktivieren möchten, können Sie eine Kategorie verwenden mit:

@implementation UITextView (DisableCopyPaste)

- (BOOL)canBecomeFirstResponder
{
    return NO;
}

@end

Es spart eine Unterklasse ... :-)


3
Sie können dies auch nur in Ihre /, -Datei einfügen, in der Sie dieses Verhalten benötigen.
markus_p

4
Dies wirkt nur als Nebeneffekt und verhindert, dass sich das UITextViewGerät wie erwartet verhält, wenn es beispielsweise bearbeitet werden kann und eine Berührung erhält. Es ist viel zu überschreiben canPerformAction:withSender:; Dafür ist das Protokoll da.
JDC

16
Sie können eine Methode mit einer Kategorie nicht sicher überschreiben. Dies ist undefiniertes Verhalten. Sie müssen eine Unterklasse erstellen, um eine Methode sicher zu überschreiben.
Rob Napier

2
Hinweis: Dies gilt für alle UITextViews in Ihrer Anwendung. Meistens nicht ideal.
Brame

2
Beachten Sie auch, dass Apple davon abrät : "Wenn der Name einer in einer Kategorie deklarierten Methode mit einer Methode in der ursprünglichen Klasse oder einer Methode in einer anderen Kategorie in derselben Klasse (oder sogar einer Oberklasse) identisch ist, ist das Verhalten so undefiniert, welche Methodenimplementierung zur Laufzeit verwendet wird ... [und] können Probleme verursachen, wenn Kategorien zum Hinzufügen von Methoden zu Standardklassen von Cocoa oder Cocoa Touch verwendet werden. "
Rob

29

Dies war die beste funktionierende Lösung für mich:

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

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


3
nett, keine Unterklasse hier!
Martin Reichl

7
Ich liebe den Ansatz von Klebeband und Pressdraht hier. Ich wünschte, ich könnte dich wieder +1 :) Stattdessen ist hier ein goldener Stern: i.imgur.com/EXLFt1Z.jpg
jdc

21

@ rpetrich Antwort hat bei mir funktioniert. Ich poste den erweiterten Code für den Fall, dass jemand Zeit spart.

In meinem Fall möchte ich überhaupt kein Popup, aber ich möchte, dass das UITextField Ersthelfer werden kann.

Leider wird das Lupen-Popup immer noch angezeigt, wenn Sie auf das Textfeld tippen und es gedrückt halten.

@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)
    }

}

1
Bitte posten Sie auch hierfür eine schnelle Version. Danke.
Salman Khakwani

@pinch: tnx. Hält noch das Wasser in iOS 12.1.3.
YvesLeBorg

20

Wenn Sie UITextView nicht zum Scrollen benötigen, besteht die einfachste Lösung ohne Unterklassifizierung darin, die Benutzerinteraktion für die Textansicht einfach zu deaktivieren:

textField.userInteractionEnabled = NO;

2
Dadurch entfällt das Tippen auf Links usw., wenn dies in der Textansicht angegeben ist. Es sollte beachtet werden, dass dies keine gute Lösung ist, um das Auswählen / Kopieren / Einfügen auszublenden, aber auch ein gewisses Maß an Interaktion aktiviert zu lassen.
Barfoon

3
Nun, ich hätte gedacht, dass das offensichtlich wäre.
Luke Redpath

Ich verwende Textfelder als Beschriftungen für Bilder in einem Spiel und möchte nicht, dass die Lupe angezeigt wird, und es gibt keinen Grund, den Text zu kopieren. Das funktioniert gut für mich. Ich verwende gestalteten Text in einer UIWebView und die gleiche Zeile funktioniert auch dort.
JScarry

15

Am einfachsten ist es, eine Unterklasse von UITextView zu erstellen, die die canPerformAction überschreibt: 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;
}

Dies ist die beste Lösung, um Text eingeben zu können, aber nichts anderes, und ermöglicht es, dass das Tippen auf das Textfeld nicht mehr "abgefangen" wird. Dies macht, was das OP wollte, und mehr.
hlfcoding

13

Wenn ich in canPerformAction unter iOS 7 NEIN zurückgebe, werden viele Fehler wie folgt angezeigt:

<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.

Meine Lösung lautet wie folgt:

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

Der Trick besteht darin, den Menü-Controller im nächsten Zyklus in der Hauptwarteschlange auszublenden (unmittelbar nachdem er angezeigt wird).


wirklich nett. Das einzige Problem ist, dass ich 2 Textansichten und ein Textfeld habe und das Kopieren und Einfügen nur in den textView-Feldern vermeiden möchte. Wie identifiziere ich, wer die canPerformAction aufgerufen hat? Die Absendervariable ist der UIMenuController
Gaucho

1
Es funktioniert in der Unterklasse von UITextField (oder UITextView). Wenn Sie die von Ihnen erstellte Unterklasse nicht verwenden, hat dies keine Auswirkungen. ZB habe ich ein TextFieldWithoutCopyPaste erstellt und dieses verwendet, bei dem ich keine Kopier- und Einfügefunktion haben wollte.
Adam Wallner

OK, Sie haben Recht. In meinem Fall benötigt die textView jedoch eine Unterklasse, um die canPerformAction zu verwenden, und das textField benötigt eine Unterklasse, um die textFieldDidBeginEditing zu verwenden, um das Fenster zu animieren, wenn die Tastatur angezeigt wird. Die Animation verschiebt das Fenster mit der Tastatur. Ich benutze diese Methode, um zu vermeiden, dass die Tastatur das Textfeld abdeckt.
Gaucho

10

Dies ist der einfachste Weg, um das gesamte Menü Auswählen / Kopieren / Einfügen in einer UITextView zu deaktivieren

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{    
    [UIMenuController sharedMenuController].menuVisible = NO;
    return NO;    
}

Funktioniert auch für UITextField. Danke für das Teilen.
Gaurav Srivastava

1
Ich verwende den obigen Code, aber ich erhalte ein Menü zum Deaktivieren, das mir bitte hilft.
Bittoo

4

Seit iOS 7 gibt es eine Eigenschaft in UITextView:

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

Dies verhindert, dass eine Ansicht eine Textauswahl zulässt. Funktioniert super für mich.


4

Wenn Sie die Tastatur durch eine (z. UIPickerB. inputViewmit einer Symbolleiste als inputAccesotyView) ersetzen möchten , kann diese Problemumgehung hilfreich sein ...

  • Implementieren textFieldShouldBeginEditing:
  • innen setzen textField.userInteractionEnabled = NO;
  • Wenn Sie das schließen UIPickerViewmöchten, setzen Sie es auf YES.

Auf diese Weise können Sie auf tippen UITextFieldund die Optionen anzeigen, aus denen Sie auswählen UIPickerViewkönnen. Zu diesem Zeitpunkt UITextFieldwürden Sie tatsächlich nicht auf ein Berührungsereignis reagieren (dies schließt Berühren und Halten zum Ausschneiden, Kopieren und Einfügen ein). Sie müssen jedoch daran denken, es beim Schließen wieder auf YES zu setzen, können UIPickerViewjedoch nicht mehr auf Ihr zugreifen UIPickerView.

Der einzige Moment, in dem dies fehlschlägt, ist, wenn der Benutzer zunächst auf die Taste tippt und sie gedrückt hält UITextView. Dann wird zum ersten Mal das Ausschneiden, Kopieren und Einfügen angezeigt. Aus diesem Grund sollten Sie Ihre Eingaben immer validieren. Dies ist die einfachste, die ich mir vorstellen kann. Die andere Möglichkeit bestand darin, einen UILabelschreibgeschützten Text zu verwenden, aber Sie vermissen eine Menge großartiger Funktionen von UITextView.


4

Unterklasse UITextView - Swift 4.0

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

Wie seltsam? es zeigt immer noch die Option "Einfügen", aber nicht, wenn gedrückt wird ...
iOS Flow

4

Wenn Sie das Popup für deaktivieren möchten, UITextFieldversuchen Sie diese UITextFieldDelegateMethode zum Umschalten isUserInteractionEnabled.

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

3

Dies kann einfach im Storyboard (Xcode 6) erfolgen. Deaktivieren Sie einfach "Bearbeiten" und "Auswählbar" im Attributinspektor. Sie können weiterhin durch die Textansicht scrollen.Geben Sie hier die Bildbeschreibung ein


3

Das hat bei mir funktioniert. Stellen Sie sicher, dass Sie resignFirstResponder in der Textansicht aufrufen

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

2

Ich bot eine Arbeits Antwort hier zu deaktivieren Textauswahl + Lupe, keeping aktiviert clikable Links Hoffnung , die hilft:

Nachdem ich lange versucht hatte, konnte ich die Textauswahl stoppen, vergrößern und die Datenerkennung beibehalten (Links anklickbar usw.), indem ich addGestureRecognizer in einer UITextView-Unterklasse überschrieb, sodass nur UILongPressGestureRecognizer das Ende der Berührung verzögerte:

UIUnselectableTextView.m

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

2

Für Swift 3 wurde Folgendes geändert:

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

2

Sie können dies in Ihrem Storyboard beheben, indem Sie die folgenden Kontrollkästchen deaktivieren:

Geben Sie hier die Bildbeschreibung ein

Oder Sie können programmgesteuert wie folgt einstellen:

textView.selectable = false
textView.editable = false

1

Ich habe es getan. Auf meiner habe UITextViewich die Option Ausschneiden, Kopieren, Auswählen usw. sehr einfach deaktiviert.

Ich platzierte ein UIViewan der gleichen Stelle, an der ich das platziert hatte UITextView, aber auf self.viewund fügte eine touchDelegateMethode wie folgt hinzu:

(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;
        }

    }
}

und es hat bei mir funktioniert. Danke dir.


1

Schnell

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

verbunden

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.

1

Wenn Sie Ihrer UITextView eine benutzerdefinierte Option hinzufügen, aber die vorhandenen Funktionen deaktivieren möchten, gehen Sie wie folgt in Swift 3 vor :

Um die Funktion zum Kopieren, Einfügen und Ausschneiden zu deaktivieren, erstellen Sie eine Unterklasse und überschreiben Sie Folgendes:

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

Auf dem ViewController muss Ihr CustomTextView Folgendes hinzufügen, um Ihre Optionen hinzuzufügen:

  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)")

    }

1

Diese Methode deaktiviert das Menü Auswählen, Alle auswählen, Einfügen vollständig. Wenn Sie noch eine andere Aktion erhalten, fügen Sie diese einfach wie unten zur if-Bedingung hinzu.

- (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];
    }

0

Sie können einfach eine Kategorie wie folgt erstellen:

UITextView + Selectable.h

@interface UITextView (Selectable)

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

@end

UITextView + Selectable.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

1
Dies ist kein guter Weg, um es zu lösen. Erstens betrifft es alles, da es in der Kategorie ist. Zweitens kann die Verwendung zugehöriger Objekte für relativ einfache Aufgaben später beim Debuggen mehr Probleme verursachen (warum funktioniert das so, wenn Sie vergessen, was Sie getan haben), als Gewinn zu erzielen. Meiner Meinung nach ist es gut, dies nach Möglichkeit zu vermeiden. Erleichtert das Auffinden und Debuggen von Code für neue Programmierer im Projekt.
Vive

0

Unterklassen UITextViewund Überschreiben - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizersind eine weitere Möglichkeit, unerwünschte Aktionen zu deaktivieren.

Verwenden Sie die Klasse des gestureRecognizer-Objekts, um zu entscheiden, ob die Aktion hinzugefügt werden soll oder nicht.


0

(SWIFT) Wenn Sie nur ein einfaches Textfeld ohne Menüoptionen oder Lupe möchten, erstellen Sie eine Unterklasse von UITextField, die false an gestureRecognizerShouldBegin zurückgibt:

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

        return false
    }
}

Dadurch werden alle Touch-Funktionen im Textfeld umgangen, Sie können jedoch weiterhin die Popup-Tastatur zum Hinzufügen / Entfernen von Zeichen verwenden.

Wenn Sie ein Storyboard verwenden, weisen Sie dem Textfeld einfach die neu erstellte Klasse zu, oder wenn Sie programmgesteuert ein Textfeld erstellen:

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

basic.becomeFirstResponder()

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)}

0

Swift 3

Dazu müssen Sie Ihre UITextView unterordnen und diese Methode einfügen.

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)
    }

0

UITextView verfügt über zwei Eigenschaften, die genau das tun, was Sie benötigen: isSelectable und isEditable .

Wenn Sie isEditable auf false setzen, vermeiden Sie, dass der Benutzer den Text bearbeitet, und wenn Sie isSelectable auf false setzen, vermeiden Sie, dass der Benutzer Text in Ihrer textView auswählt, sodass das Aktionsmenü nicht angezeigt wird.


0

Den Referenzcode finden Sie als Referenz:

 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
    }

-1

Verwenden Sie func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { retrun bool }anstelle von 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
    }
}

Erstellen Sie eine neue Klasse mit dem Namen StopPasteAction.swift

import UIKit

class StopPasteAction: UITextField {

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

Fügen Sie die Klasse neue Klasse mit Ihrem aktuellen TextField hinzu

Geben Sie hier die Bildbeschreibung ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.