I am currently trying to prevent all UIResponderStandardEditActions like copy, paste, delete from showing up when the UITextfield is empty. I would only like to show them if the user has types a message. I have tried 2 solutions and currently don't work, I'm not sure if its to do with iOS 12 or. I have tried overriding the canPerformAction method both in a UITextfield extension and using a custom class later assigned to the UITextfield in the Storyboard but no luck. Is there another way to do this. Here is what I have tried.
extension UITextField {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if self.text!.isEmpty {
return false
}
return action == #selector(UIResponderStandardEditActions.paste(_:))
}
}
class CustomTextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste(_:)) || action == #selector(UIResponderStandardEditActions.copy(_:)) || action == #selector(UIResponderStandardEditActions.delete(_:)) {
return false
}
return true
}
}
Just override with your subclass and use this class instead of UITextField. By the way, this will disable copy paste,cut for whole conditions so that you need to add some hasText: Bool or related condition to the switch case.
#IBDesignable
class ActionsDisabledUITextField: UITextField {
#IBInspectable var isPasteEnabled: Bool = false
#IBInspectable var isSelectEnabled: Bool = false
#IBInspectable var isSelectAllEnabled: Bool = false
#IBInspectable var isCopyEnabled: Bool = false
#IBInspectable var isCutEnabled: Bool = false
#IBInspectable var isDeleteEnabled: Bool = false
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(UIResponderStandardEditActions.paste(_:)) where !isPasteEnabled,
#selector(UIResponderStandardEditActions.select(_:)) where !isSelectEnabled,
#selector(UIResponderStandardEditActions.selectAll(_:)) where !isSelectAllEnabled,
#selector(UIResponderStandardEditActions.copy(_:)) where !isCopyEnabled,
#selector(UIResponderStandardEditActions.cut(_:)) where !isCutEnabled,
#selector(UIResponderStandardEditActions.delete(_:)) where !isDeleteEnabled:
return false
default:
//return true : this is not correct
return super.canPerformAction(action, withSender: sender)
}
}
}
Related
Here's what my textView looks like right now. It is a textview inside a scrollview.
I am trying to replace the usual UIMenuController menu items with Save and Delete but not getting there. Can someone help me out?
Here's my code:
import UIKit
class DetailViewController: UIViewController, UIGestureRecognizerDelegate, {
var selectedStory : URL!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var textSlider: UISlider! {
didSet {
configureSlider()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let storyText = try? String(contentsOf: selectedStory)
textView.text = storyText
textView.isUserInteractionEnabled = true
let longPressGR = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
longPressGR.minimumPressDuration = 0.3 //
textView.addGestureRecognizer(longPressGR)
}
// MARK: - UIGestureRecognizer
#objc func longPressHandler(sender: UILongPressGestureRecognizer) {
guard sender.state == .began,
let senderView = sender.view,
let superView = sender.view?.superview
else { return }
senderView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(senderView.frame, in: superView)
UIMenuController.shared.setMenuVisible(true, animated: true)
}
override var canBecomeFirstResponder: Bool {
return true
}
}
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
extension UIScrollView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
I'm getting 2 issues:
When I tap the screen, only the Share is showing up and the Copy is not.
The Share button shows up randomly near the center, not on the text that is selected, like so.
First of all, remove UITextView that is inside UIScrollView because UIScrollView itself is the parent class of UITextView. It will place the UIMenuController at appropriate frame.
Remove longPressGR and longPressHandler methods.
Replace this method,
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action.description == "copy:" || action.description == "_share:" {
return true
} else {
return false
}
}
}
You will get following output.
I show a UIMenuController in a uiviewconroller in this way:
in my class:
override open func canBecomeFirstResponder() -> Bool {
return true
}
open override func canPerformAction(_ action: Selector, withSender sender: Any) -> Bool {
//here I check for my custom action, else return false
return false
}
then to show I use:
//Make this as first responder
self.becomeFirstResponder()
///Build menu
let menu = UIMenuController.shared
///Set item and anchor point, and showit
menu.menuItems = itemsToAdd
menu.setTargetRect(CGRect(x: 0, y: 5, width: bubbleNode.view.bounds.size.width, height: bubbleNode.view.bounds.size.height), in: bubbleImageNode.view)
menu.setMenuVisible(true, animated: true)
the problem is that in a device I show my custom items, but also: "Spell, speak, speck sentence, ecc..." how can I disable it?
override canPerformAction and handle it for each specific action. It working perfectly for me.
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
var canPerform = super.canPerformAction(action, withSender: sender)
if (action == "your action to restrict") {
canPerform = false
}
return canPerform
}
Ok, the problem is "Accessibility" option "speak selection" in my device, if I disabled it, I see only the custom items, but in other app i see only custom items with this option enabled!
class TextViewWithCopyAction: UITextView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(copy(_:)){
return true
} else {
return false
}
}
}
to use it:
let textView: TextViewWithCopyAction = {
let textView = TextViewWithCopyAction()
textView.backgroundColor = .white
return textView
}()
If you see speak option in UIMenuController that is from device go to:
Setting->Accessibility->Speech->Speech
Select and disable it. Automatically Speak option will get removed from menu.
I am trying to show the ios copy button on a view and handle the click on a custom function. I tried to display the button with this code but nothing is appearing.
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.setTargetRect(paragraphTableViewCell.bounds, in: paragraphTableViewCell)
menu.setMenuVisible(true, animated: true)
}
EDIT:
I got this errors
You need to call canBecomeFirstResponder In your class.
And override canPerformAction then add your appropriate option as UIMenuItem
func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any) -> Bool {
if action == #selector(self.cut) {
return false
}
else if action == #selector(self.copy) {
return true
}
else if action == #selector(self.paste) {
return false
}
else if action == #selector(self.select) || action == #selector(self.selectAll) {
return true
}
else {
return super.canPerformAction(action, withSender: sender)
}
}
override func copy(_ sender: Any?) {
}
Finally you should pass UIView object
menu.setTargetRect(paragraphTableViewCell.bounds, in: paragraphTableViewCell.contentView)
Because its required
- (void)setTargetRect:(CGRect)targetRect inView:(UIView *)targetView;
I am trying to override the default UIMenuController so that only my custom item "Define..." appears when the user selects text in its text view. I haven't had much luck with the approaches I've found online thus far.
To be more specific, I have subclassed a UIViewController and used canPerformAction() to exclude all actions except my define method.
override func becomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
let canPerform: Bool
if action == #selector(defineWord){
canPerform = true
}
else {
canPerform = false
}
print("action = \(action), canPerform = \(canPerform)")
return canPerform
}
In the view controller's viewDidLoad(), I've included the following:
let shared = UIMenuController.shared
let menuItemDefine = UIMenuItem(title: "Define...", action: #selector(self.defineWord))
shared.menuItems = [menuItemDefine]
Whenever I select text in the view, the console goes through each possible action that might appear in the UIMenuController and says they can't be performed, with the exception of my custom action:
action = cut:, canPerform = false
action = select:, canPerform = false
(and so on, until...)
action = defineWord, canPerform = true
But the resulting edit menu contains "Copy", "Look Up", "Share", and "Define...". These don't appear in the console, which makes me think that a different approach is called for.
Note that I've also tried subclassing UITextView and using the above code as appropriate, but the result is the same.
Any ideas where I'm going wrong?
This might help everyone who is asking this question that how to remove "Copy", "Select All" etc.. standard menu items or UIResponderStandardEditActions that are still visible when you have already returned false in canPerformAction:.
It is related to responder chain. As canPerformAction: is called for every responder, for some of those it may be returning true in canPerformAction: as a default value.
Thus to check where it is failing I found it by overriding this canPerformAction: for every element I used in my controller
For example in my view controller I had a webview and the mistake I was doing was that I was overriding the canPerformAction: in the delegate methods i.e I was doing something like below
extension viewcontroller: UIWebViewDelegate{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
But the point is that you have to do it for element and not as the delegate method.
extension UIView {
func dropRoundCorners() {
self.layer.cornerRadius = 10.0;
self.clipsToBounds = true;
}
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIImageView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIScrollView{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
extension UISlider{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
extension UIWebView{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
I hope this is useful to anyone whose is stuck with this issue.
Following are links that might help you with details:
UIResponder reference
very important read the discussion here regarding responder
some what related
May be it is too late for the answer, but it can be helpful for other users.
So, my solution is: I created custom UITextView and redefined the following methods:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
//Here you can check for all action what you need
return (action == #selector(yourCustomAction)) ? YES : NO;
}
I'm facing problem when string in UILabel is displayed with delay in inputAccessoryView on UIViewController. I have attached gif demonstrating this problem. After pushing SecondViewController to navigation stack inputAccessoryView is missing text for short time. But I want text to be shown right away after opening screen.
Implementation demonstrating this problem is extremely simple.
class SecondViewController: UIViewController {
#IBOutlet var accessoryView: UIView!
override var inputAccessoryView: UIView {
return accessoryView
}
override func canBecomeFirstResponder() -> Bool {
return true
}
}
Does any one have solution for this problem?
I have come up with the solution which works on both iOS 8 and 9. Also it address retain cycle issue presented in iOS 9 which prevent view controller from being deallocated when use inputaccessoryview. Check github project for more details.
With lots of experimentation I have found quite hacky solution but works like a charm. Just subclass your implemantation accessory view from AccessoryView listed below.
class AccessoryView: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override func awakeFromNib() {
super.awakeFromNib()
disableShowingKeyboard()
hideCursor()
}
}
extension AccessoryView {
private func disableShowingKeyboard() {
inputView = UIView()
}
private func hideCursor() {
tintColor = UIColor.clear
}
override func accessibilityActivate() -> Bool {
return false
}
override var isEditing: Bool {
return false
}
override func caretRect(for position: UITextPosition) -> CGRect {
return .zero
}
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
return []
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponder.copy(_:)) || action == #selector(UIResponder.selectAll(_:)) || action == #selector(UIResponder.paste(_:)){
return false
}
return super.canPerformAction(action, withSender: sender)
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
}
extension AccessoryView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for view in subviews {
let _point = self.convert(point, to: view)
if !view.isHidden && view.isUserInteractionEnabled && view.alpha > 0.01 && view.point(inside: _point, with: event) {
if let _view = view.hitTest(_point, with: event){
return _view
}
}
}
return super.hitTest(point, with: event)
}
}