UITextView disabling text selection - ios

I'm having a hard time getting the UITextView to disable the selecting of the text.
I've tried:
canCancelContentTouches = YES;
I've tried subclassing and overwriting:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
(But that gets called only After the selection)
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
(I don't see that getting fired at all)
- (BOOL)touchesShouldBegin:(NSSet *)touches
withEvent:(UIEvent *)event
inContentView:(UIView *)view;
(I don't see that getting fired either)
What am I missing?

Issue How disable Copy, Cut, Select, Select All in UITextView has a workable solution to this that I've just implemented and verified:
Subclass UITextView and overwrite canBecomeFirstResponder:
- (BOOL)canBecomeFirstResponder {
return NO;
}
Note that this disables links and other tappable text content.

I've found that calling
[textView setUserInteractionEnabled:NO];
works quite well.

UITextView's selectable property:
This property controls the ability of the user to select content and
interact with URLs and text attachments. The default value is YES.

Swift 4, Xcode 10
This solution will
disable highlighting
enable tapping links
allow scrolling
Make sure you set the delegate to YourViewController
yourTextView.delegate = yourViewControllerInstance
Then
extension YourViewController: UITextViewDelegate {
func textViewDidChangeSelection(_ textView: UITextView) {
if #available(iOS 13, *) {
textView.selectedTextRange = nil
} else {
view.endEditing(true)
}
}
}

Swift 4, Xcode 10:
If you want to make it so the user isn't able to select or edit the text.
This makes it so it can not be edited:
textView.isEditable = false
This disables all user interaction:
textView.isUserInteractionEnabled = false
This makes it so that you can't select it. Meaning it will not show the edit or paste options. I think this is what you are looking for.
textView.isSelectable = false

It sounds like what you actually want is a giant UILabel inside a UIScrollView, and not a UITextView.
update: if you are on newer versions of iOS UILabel now has a lines property:
Multiple lines of text in UILabel

If you just want to prevent it from being edited, then set the UITextView's "editable" property to NO/False.
If you're trying to leave it editable but not selectable, that's going to be tricky. You might need to create a hidden textview that the user can type into and then have UITextView observe that hidden textview and populate itself with the textview's text.

To do this first subclass the UITextView
and in the implementation do the following
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
self.selectable = NO;
}
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
self.selectable = YES;
}
this should work fine,

Did you try setting userInteractionEnabled to NO for your UITextView? But you'd lose scrolling too.
If you need scrolling, which is probably why you used a UITextView and not a UILabel, then you need to do more work. You'll probably have to override canPerformAction:withSender: to return NO for actions that you don't want to allow:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
switch (action) {
case #selector(paste:):
case #selector(copy:):
case #selector(cut:):
case #selector(cut:):
case #selector(select:):
case #selector(selectAll:):
return NO;
}
return [super canPerformAction:action withSender:sender];
}
For more, UIResponderStandardEditActions .

You can disable text selection by subclassing UITextView.
The below solution is:
compatible with isScrollEnabled
compatible with loupe/magnifier
but not compatible with links (see here for a solution compatible with links)
/// Class to disallow text selection
/// while keeping support for loupe/magnifier and scrolling
/// https://stackoverflow.com/a/49428248/1033581
class UnselectableTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// prevents selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
// without losing the loupe/magnifier or scrolling
// but we lose taps on links
addSubview(transparentOverlayView)
}
let transparentOverlayView: UIView = {
$0.backgroundColor = .clear
$0.autoresizingMask = [.flexibleHeight, .flexibleWidth]
return $0
}(UIView())
override var contentSize: CGSize {
didSet {
transparentOverlayView.frame = CGRect(origin: .zero, size: contentSize)
}
}
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
}

For swift, there is a property called "isSelectable" and its by default assign to true
you can use it as follow:
textView.isSelectable = false

Related

UITextView with Gesture Recognizer - Conditionally Forward Touch to Parent View

I have a UITextView embedded in a UITableViewCell.
The text view has scrolling disabled, and grows in height with the text in it.
The text view has a link-like section of text that is attributed with a different color and underlined, and I have a tap gesture recognizer attached to the text view that detects whether the user tapped on the "link" portion of the text or not (This is accomplished using the text view's layoutManager and textContainerInset to detect whether the tap falls within the 'link' or not. It's basically a custom hit test function).
I want the table view cell to receive the tap and become selected when the user "misses" the link portion of the text view, but can't figure out how to do it.
The text view has userInteractionEnabled set to true. However, this does not block the touches from reaching the table view cell when there is no gesture recognizer attached.
Conversely, if I set it to false, for some reason cell selection stops altogether, even when tapping outside of the text view's bounds (but the gesture recognizer still works... WHY?).
What I've Tried
I have tried overriding gestureRecognizer(_ :shouldReceive:), but even when I return false, the table view cell does not get selected...
I have also tried implementing gestureRecognizerShouldBegin(_:), but there too, even if I perform my hit test and return false, the cell does not get the tap.
How can I forward the missed taps back to the cell, to highlight it?
After trying Swapnil Luktuke's answer(to the extent that I understood it, at least) to no avail, and every possible combination of:
Implementing the methods of UIGestureRecognizerDelegate,
Overriding UITapGestureRecognizer,
Conditionally calling ignore(_:for:), etc.
(perhaps in my desperation I missed something obvious, but who knows...)
...I gave up and decided to follow the suggestion by #danyapata in the comments to my question, and subclass UITextView.
Partly based on code found on this Medium post, I came up with this UITextView subclass:
import UIKit
/**
Detects taps on subregions of its attributed text that correspond to custom,
named attributes.
- note: If no tap is detected, the behavior is equivalent to a text view with
`isUserInteractionEnabled` set to `false` (i.e., touches "pass through"). The
same behavior doesn't seem to be easily implemented using just stock
`UITextView` and gesture recognizers (hence the need to subclass).
*/
class LinkTextView: UITextView {
private var tapHandlersByName: [String: [(() -> Void)]] = [:]
/**
Adds a custom block to be executed wjhen a tap is detected on a subregion
of the **attributed** text that contains the attribute named accordingly.
*/
public func addTapHandler(_ handler: #escaping(() -> Void), forAttribute attributeName: String) {
var handlers = tapHandlersByName[attributeName] ?? []
handlers.append(handler)
tapHandlersByName[attributeName] = handlers
}
// MARK: - Initialization
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonSetup()
}
private func commonSetup() {
self.delaysContentTouches = false
self.isScrollEnabled = false
self.isEditable = false
self.isUserInteractionEnabled = true
}
// MARK: - UIView
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let attributeName = self.attributeName(at: point), let handlers = tapHandlersByName[attributeName], handlers.count > 0 else {
return nil // Ignore touch
}
return self // Claim touch
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// find attribute name
guard let touch = touches.first, let attributeName = self.attributeName(at: touch.location(in: self)) else {
return
}
// Execute all handlers for that attribute, once:
tapHandlersByName[attributeName]?.forEach({ (handler) in
handler()
})
}
// MARK: - Internal Support
private func attributeName(at point: CGPoint) -> String? {
let location = CGPoint(
x: point.x - self.textContainerInset.left,
y: point.y - self.textContainerInset.top)
let characterIndex = self.layoutManager.characterIndex(
for: location,
in: self.textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
guard characterIndex < self.textStorage.length else {
return nil
}
let firstAttributeName = tapHandlersByName.allKeys.first { (attributeName) -> Bool in
if self.textStorage.attribute(NSAttributedStringKey(rawValue: attributeName), at: characterIndex, effectiveRange: nil) != nil {
return true
}
return false
}
return firstAttributeName
}
}
As ususal, I'll wait a couple of days before accepting my own answer, just in case something better shows up...
Keep all your views active (i.e. user interaction enabled).
Loop through the text view's gestures and disable the ones you do not need.
Loop through the table view's gestureRecognisers array, and make them depend on the text view's custom tap gesture using requireGestureRecognizerToFail.
If its a static table view, you can do this in view did load. For a dynamic table view, do this in 'willDisplayCell' for the text view cell.

How to hide a blue cursor in a textfield and a keyboard?

I need to get an empty textfield when my ViewController Appears. I tried this:
override func viewWillAppear(animated: Bool) {
linkField.placeholder = "test"
linkField.text = ""
}
But when it appears the keyboard pops up and a blue cursor blinks in a textField.
How can I get rid of those ?
EDIT:
I think I didn't explain my question correctly. I don't need to get an empty cursor, I just need to hide it and the keyboard until user touches the textfield to input something.
The textfield cursor color is based on the default tint color. In your case it's blue. I would change the tint color to clear color.
linkField.tintColor = UIColor.clearColor()
For the edited question, if you just want to dismiss the keyboard then
linkField.resignFirstResponder()
will dismiss the keyboard and when you want focus again use
linkField.becomeFirstResponder()
Create subclass of UITextField and override caretRectForPosition
import UIKit
class RemoveBlinkCursor: UITextField {
override init(frame:CGRect)
{
super.init(frame:frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func caretRectForPosition(position: UITextPosition!) -> CGRect {
return CGRectZero
}
}
After your edit answer changed :
For show and hide keyboard and focus you can use resign and become first responders as said by Vig.
Hope this will help to others for removing blinking bar.
try this
self.textField.tintColor = [UIColor clearColor];
Hope it helps.
Swift 3 Xcode 8
Calling view.endEditing(true) has always worked for me

Disabling user input for UITextfield in swift

pretty trivial question, I know. But I can not find anything online.
I need to disable the user from being able to edit the text inside of a text field. So that when the click on the text, a keyboard doesn't show up.
Any ideas?
A programmatic solution or if it is possible through storyboards would be great.
Try this:
Swift 2.0:
textField.userInteractionEnabled = false
Swift 3.0:
textField.isUserInteractionEnabled = false
Or in storyboard uncheck "User Interaction Enabled"
Another solution, declare your controller as UITextFieldDelegate, implement this call-back:
#IBOutlet weak var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField.delegate = self
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if textField == myTextField {
return false; //do not show keyboard nor cursor
}
return true
}
In storyboard you have two choise:
set the control's 'enable' to false.
set the view's 'user interaction enable' false
The diffierence between these choise is:
the appearance of UITextfild to display in the screen.
First is set the control's enable. You can see the backgroud color is
changed.
Second is set the view's 'User interaction enable'. The backgroud color is NOT changed.
Within code:
textfield.enable = false
textfield.userInteractionEnabled = NO
Updated for Swift 3
textField.isEnabled = false
textfield.isUserInteractionEnabled = false
If you want to do it while keeping the user interaction on.
In my case I am using (or rather misusing) isFocused
self.myField.inputView = UIView()
This way it will focus but keyboard won't show up.
I like to do it like old times. You just use a custom UITextField Class like this one:
//
// ReadOnlyTextField.swift
// MediFormulas
//
// Created by Oscar Rodriguez on 6/21/17.
// Copyright © 2017 Nica Code. All rights reserved.
//
import UIKit
class ReadOnlyTextField: UITextField {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
override init(frame: CGRect) {
super.init(frame: frame)
// Avoid keyboard to show up
self.inputView = UIView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Avoid keyboard to show up
self.inputView = UIView()
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// Avoid cut and paste option show up
if (action == #selector(self.cut(_:))) {
return false
} else if (action == #selector(self.paste(_:))) {
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
In swift 5, I used following code to disable the textfield
override func viewDidLoad() {
super.viewDidLoad()
self.textfield.isEnabled = false
//e.g
self.design.isEnabled = false
}
Swift 4.2 / Xcode 10.1:
Just uncheck behavior Enabled in your storyboard -> attributes inspector.
you can use UILabel instead if you don't want the user to be able to modify anything in your UITextField
A programmatic solution would be to use enabled property:
yourTextField.enabled = false
A way to do it in a storyboard:
Uncheck the Enabled checkbox in the properties of your UITextField
textField.isUserInteractionEnabled = false

Custom font for MKAnnotationView Callout

Fortunately, the standard callout view for an MKAnnotationView meets our needs - title, subtitle, leftCalloutAccessoryView, and rightCalloutAccessoryView.
Unfortunately, we use custom fonts in our app, and would like to extend those custom fonts to these callouts.
MKAnnotationView provides no standard way to accomplish this.
How can I use a custom font in an MKAnnotation's callout view?
Since I needed the Swift version - here it is.
Also, you have to call setNeedsLayout() on didAddSubview() because otherwise when you deselect and reselect the annotation layoutSubviews() is not called and the callout has its old font.
// elsewhere, in a category on UIView.
// thanks to this answer: http://stackoverflow.com/a/25877372/607876
typealias ViewBlock = (_ view: UIView) -> Bool
extension UIView {
func loopViewHierarchy(block: ViewBlock?) {
if block?(self) ?? true {
for subview in subviews {
subview.loopViewHierarchy(block: block)
}
}
}
}
// then, in your MKAnnotationView subclass
class CustomFontAnnotationView: MKAnnotationView {
override func didAddSubview(_ subview: UIView) {
if isSelected {
setNeedsLayout()
}
}
override func layoutSubviews() {
// MKAnnotationViews only have subviews if they've been selected.
// short-circuit if there's nothing to loop over
if !isSelected {
return
}
loopViewHierarchy { (view: UIView) -> Bool in
if let label = view as? UILabel {
label.font = labelFont
return false
}
return true
}
}
}
If all you need is a custom font, you need to subclass MKAnnotationView, but you don't have to recreate all the behavior that you get for free with a standard MKAnnotationView. It's actually pretty easy.
Subclass MKAnnotationView
Override -layoutSubviews
When an MKAnnotationView is selected, the callout is added as a subview. Therefore, we can recursively loop through our subclass' subviews and find the UILabel we wish to modify.
That's it!
The only drawback with this method is that you can see the callout adjust it's size if your font is smaller or larger than the standard system font it was expecting. It'd be great if all the adjustments were made before being presented to the user.
// elsewhere, in a category on UIView.
// thanks to this answer: http://stackoverflow.com/a/25877372/607876
//
typedef void(^ViewBlock)(UIView *view, BOOL *stop);
#interface UIView (Helpers)
- (void)loopViewHierarchy:(ViewBlock)block;
#end
#implementation UIView (Helpers)
- (void)loopViewHierarchy:(ViewBlock)block {
BOOL stop = false;
if (block) {
block(self, &stop);
}
if (!stop) {
for (UIView* subview in self.subviews) {
[subview loopViewHierarchy:block];
}
}
}
#end
// then, in your MKAnnotationView subclass
//
#implementation CustomFontAnnotationView
- (void)layoutSubviews
{
// MKAnnotationViews only have subviews if they've been selected.
// short-circuit if there's nothing to loop over
if (!self.selected) {
return;
}
[self loopViewHierarchy:^(UIView *view, BOOL *stop) {
if ([view isKindOfClass:[UILabel class]]) {
*stop = true;
((UILabel *)view).font = {custom_font_name};
}
}];
}
#end
I inspected the view tree by first creating my MKAnnotationView subclass and setting a breakpoint in my overridden -layoutSubviews. In the debugger, I then issued po [self recursiveDescription]. Make sure to turn the breakpoint off when your map first loads, because as mentioned up above, MKAnnotationViews don't have any subviews until their selected. Before you make a selection, enable the breakpoint, tap your pin, break, and print out the view tree. You'll see a UILabel at the very bottom of the tree.

how to set UIButton type in UIButton Subclass

I am subclassing the UIButton, what i want is to set the button type to Round Rect.
Button.h
#interface Button : UIButton {}
- (void)initialize;
#end
Button.m
#implementation Button
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self){
[self initialize];
}
return self;
}
- (void)initialize
{
self.titleLabel.font = [UIFont systemFontOfSize:20];
self.titleLabel.textColor = [UIColor redColor];
self.titleLabel.textAlignment = UITextAlignmentCenter;
//[UIButton buttonWithType:UIButtonTypeRoundedRect];
}
#end
Here i tried [UIButton buttonWithType:UIButtonTypeRoundedRect] but it doesn't work. Can anyone suggest how to make it work?
I know in many previous post it has been said that Subclassing UIButton is not recommended, but the fact that in Developer's Docs there is no mention about NOT subclassing it.
UIButton subclass in Swift
*The following code works in Swift 3 and above.
You cannot set the buttonType of a UIButton subclass by design. It is automatically set to custom which means you get a plain appearance and behavior as a starting point.
If you want to reuse code that sets the button's appearance, you can do this without subclassing. One approach is by providing factory methods that create a UIButton and set visual properties.
Example factory method to avoid subclassing
extension UIButton {
static func createStandardButton() -> UIButton {
let button = UIButton(type: UIButtonType.system)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.setTitleColor(UIColor.black, for: .normal)
button.setTitleColor(UIColor.gray, for: .highlighted)
return button
}
}
let button = UIButton.createStandardButton()
I avoid subclassing UIButton when other customization techniques suffice. The factory method example is one option. There are other options including the UIAppearance API, etc.
Sometimes there are customization needs that require subclassing. For example, I've created UIButton subclasses to take fine control over how the button animates in response to touch events or to have the button call a predefined delegate or closure when tapped (as opposed to assigning a #selector to each instance).
The following is a basic UIButton subclass example as a starting point.
Example UIButton Subclass
internal class CustomButton: UIButton {
init() {
// The frame can be set outside of the initializer. Default to zero.
super.init(frame: CGRect.zero)
initialize()
}
required init?(coder aDecoder: NSCoder) {
// Called when instantiating from storyboard or nib
super.init(coder: aDecoder)
initialize()
}
func initialize() {
print("Execute common initialization code")
}
}
let button = CustomButton()
print(button.buttonType == .custom) // true
A note about UIButtonType
Since the question was asked in 2012, UIButtonType.roundedRect has been deprecated. The header file comments say to use UIButtonType.system instead.
The following is from UIButton.h converted to Swift in Xcode
public enum UIButtonType : Int {
case custom // no button type
#available(iOS 7.0, *)
case system // standard system button
case detailDisclosure
case infoLight
case infoDark
case contactAdd
public static var roundedRect: UIButtonType { get } // Deprecated, use UIButtonTypeSystem instead
}
You may find the discussion at CocoaBuilder's thread How to subclass UIButton? helpful, particularly Jack Nutting's suggestion to ignore the buttonType:
Note that this way the buttonType isn't explicitly set to anything,
which probably means that it's UIButtonTypeCustom. The Docs don't
seem to actually specify that, but since that's the 0 value in the
enum, that's likely what happens (and that seems to be the observable
behavior as well)
not quite what you're looking for, but remember that your subclass still has the buttonWithType method, and it works fine.
buttonWithType calls your subclasses initWithFrame, and sets the type appropriately.
SubclassButton *myButton=[SubclassButton buttonWithType:UIButtonTypeRoundedRect];
Creating a convenience init worked for me:
class CustomButton: UIButton {
convenience init() {
self.init(type: .system)
}
override init(frame: CGRect) {
super.init(frame: frame)
/*Do customization here*/
}
required init?(coder: NSCoder) {
super.init(coder: coder)
/*Do customization here*/
}
}
With iOS7, this is more relevant than before, as sometimes you need to use UIButtonTypeSystem, and you can't simply override init and do [MyButton alloc] init], because then it's a UIButtonTypeCustom, which doesn't reflect tintColor nor have nice highlight fade effects.
To subclass, make a static constructor like so:
+ (instancetype)myButtonWithTitle:(NSString *)title imageNamed:(NSString *)imageName target:(id)target action:(SEL)action {
MyButton *button = [self buttonWithType:UIButtonTypeSystem];
... my custom setup for title, image, target, etc ...
return button;
}
This is not a good solution, so we need to 'thank' the Apple for use this crutch. XD
//if self.buttonType == .system && self.state.contains(.highlighted) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.titleLabel?.alpha = 1.0
}
//}
Add in touchesBegan(...) for example.
You can definitely subclass UIButton. I have successfully created a DateButton which looks like a UITextField but when the user touches it it displays a DatePicker in a Popover. Has a property for storing the Date, etc. Let me know if the above never solved your problem and I'll post details more.
What about this?
- (id)initWithFrame:(CGRect)frame
{
self = [UIButton buttonWithType:UIButtonTypeRoundedRect];
if (self) {
self.frame = frame;
}
return self;
}

Resources