UITextView:禁用选择,允许链接
UITextView: Disable selection, allow links
我有一个显示 NSAttributedString
的 UITextView
。 textView 的 editable
和 selectable
属性都设置为 false
.
attributedString 包含 URL,我想允许点击 URL 打开浏览器。但只有当 selectable
属性设置为 true
.
时,才能与 URL 交互
如何允许用户仅点击链接进行交互,而不是选择文本?
经过一番研究,我找到了解决方案。这是一个 hack,我不知道它是否可以在未来的 iOS 版本中使用,但它现在可以使用 (iOS 9.3)。
只需添加此 UITextView
类别(要点 here):
@implementation UITextView (NoFirstResponder)
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
@try {
id targetAndAction = ((NSMutableArray *)[gestureRecognizer valueForKey:@"_targets"]).firstObject;
NSArray <NSString *>*actions = @[@"action=loupeGesture:", // link: no, selection: shows circle loupe and blue selectors for a second
@"action=longDelayRecognizer:", // link: no, selection: no
/*@"action=smallDelayRecognizer:", // link: yes (no long press), selection: no*/
@"action=oneFingerForcePan:", // link: no, selection: shows rectangular loupe for a second, no blue selectors
@"action=_handleRevealGesture:"]; // link: no, selection: no
for (NSString *action in actions) {
if ([[targetAndAction description] containsString:action]) {
[gestureRecognizer setEnabled:false];
}
}
}
@catch (NSException *e) {
}
@finally {
[super addGestureRecognizer: gestureRecognizer];
}
}
}
我发现摆弄内部手势识别器的概念有点可怕,所以试图找到另一种解决方案。
我发现我们可以覆盖 point(inside:with:)
以有效地允许 "tap-through" 当用户没有触及其中包含 link 的文本时:
// Inside a UITextView subclass:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
}
这也意味着,如果您在 UITableViewCell
中有一个带有 link 的 UITextView
,那么在点击非 link 时仍然会调用 tableView(didSelectRowAt:)
]ed 部分文本 :)
Swift 3.0
以上Objective-C版本来自@Lukas
extension UITextView {
override open func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) {
do {
let array = try gestureRecognizer.value(forKey: "_targets") as! NSMutableArray
let targetAndAction = array.firstObject
let actions = ["action=oneFingerForcePan:",
"action=_handleRevealGesture:",
"action=loupeGesture:",
"action=longDelayRecognizer:"]
for action in actions {
print("targetAndAction.debugDescription: \(targetAndAction.debugDescription)")
if targetAndAction.debugDescription.contains(action) {
gestureRecognizer.isEnabled = false
}
}
} catch let exception {
print("TXT_VIEW EXCEPTION : \(exception)")
}
defer {
super.addGestureRecognizer(gestureRecognizer)
}
}
}
}
如果您的最低部署目标是 iOS 11.2 或更高版本
您可以通过子类化 UITextView
并禁止可以 select 某些东西的手势来禁用文本 selection。
下面的解决方案是:
- 与 isEditable 兼容
- 与 isScrollEnabled 兼容
- 与链接兼容
/// Class to allow links but no selection.
/// Basically, it disables unwanted UIGestureRecognizer from UITextView.
///
class UnselectableTappableTextView: UITextView {
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer {
// required for compatibility with isScrollEnabled
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// allowing smallDelayRecognizer for links
//
if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
// comparison value is used to distinguish between 0.12 (smallDelayRecognizer) and 0.5 (textSelectionForce and textLoupe)
longPressGestureRecognizer.minimumPressDuration < 0.325 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// preventing selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
gestureRecognizer.isEnabled = false
return false
}
}
如果您的最低部署目标是 iOS 11.1 或更早版本
原生 UITextView 链接手势识别器在 iOS 11.0-11.1 上损坏,需要 小延迟长按 而不是 点击: Xcode 9 UITextView links no longer clickable
您可以使用自己的手势识别器正确支持链接,并且可以通过子类化 UITextView
禁用文本 selection 并禁止可以 select 某些东西或点击某些东西的手势。
下面的解决方案将不允许 selection 并且是:
- 与 isScrollEnabled 兼容
- 与链接兼容
- iOS 11.0 和 iOS 11.1 的解决方法限制,但在点击文本附件时失去 UI 效果
/// Class to support links and to disallow selection.
/// It disables most UIGestureRecognizer from UITextView and adds a UITapGestureRecognizer.
///
class UnselectableTappableTextView: UITextView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
//
// So we add our own UITapGestureRecognizer.
linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
linkGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(linkGestureRecognizer)
linkGestureRecognizer.isEnabled = true
}
var linkGestureRecognizer: UITapGestureRecognizer!
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
// Prevents drag and drop gestures,
// but also prevents a crash with links on iOS 11.0 and 11.1.
//
gestureRecognizer.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == linkGestureRecognizer {
// Supporting links correctly.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if gestureRecognizer is UIPanGestureRecognizer {
// Compatibility support with isScrollEnabled.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// Preventing selection gestures and disabling broken links support.
gestureRecognizer.isEnabled = false
return false
}
@objc func textTapped(recognizer: UITapGestureRecognizer) {
guard recognizer == linkGestureRecognizer else {
return
}
var location = recognizer.location(in: self)
location.x -= textContainerInset.left
location.y -= textContainerInset.top
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let characterRange = NSRange(location: characterIndex, length: 1)
if let attachment = attributedText?.attribute(.attachment, at: index, effectiveRange: nil) as? NSTextAttachment {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
}
}
if let url = attributedText?.attribute(.link, at: index, effectiveRange: nil) as? URL {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
}
}
}
}
像下面那样覆盖 UITextView 并使用它呈现可点击的 link 并保留 html 样式。
public class LinkTextView: UITextView {
override public var selectedTextRange: UITextRange? {
get {
return nil
}
set {}
}
public init() {
super.init(frame: CGRect.zero, textContainer: nil)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
self.tintColor = UIColor.black
self.isScrollEnabled = false
self.delegate = self
self.dataDetectorTypes = []
self.isEditable = false
self.delegate = self
self.font = Style.font(.sansSerif11)
self.delaysContentTouches = true
}
@available(iOS 10.0, *)
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
// Handle link
return false
}
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
// Handle link
return false
}
}
正如 Coeur 所说,您可以子类化 UITextView
覆盖 selectedTextRange
的方法,将其设置为 nil。链接仍然可以点击,但您将无法 select 其余文本。
class PIUnselectableTextView: PITextView {
override public var selectedTextRange: UITextRange? {
get {
return nil
}
set { }
}
}
这是 Max Chuquimia 发布的答案的 Objective C 版本。
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITextPosition *position = [self closestPositionToPoint:point];
if (!position) {
return NO;
}
UITextRange *range = [self.tokenizer rangeEnclosingPosition:position
withGranularity:UITextGranularityCharacter
inDirection:UITextLayoutDirectionLeft];
if (!range) {
return NO;
}
NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument
toPosition:range.start];
return [self.attributedText attribute:NSLinkAttributeName
atIndex:startIndex
effectiveRange:nil] != nil;
}
Swift4,Xcode9.2
下面是 link 的不同方法,将 UITextView 的 isSelectable
属性 变为 false
class TextView: UITextView {
//MARK: Properties
open var didTouchedLink:((URL,NSRange,CGPoint) -> Void)?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = Array(touches)[0]
if let view = touch.view {
let point = touch.location(in: view)
self.tapped(on: point)
}
}
}
extension TextView {
fileprivate func tapped(on point:CGPoint) {
var location: CGPoint = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
let charIndex = layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard charIndex < self.textStorage.length else {
return
}
var range = NSRange(location: 0, length: 0)
if let attributedText = self.attributedText {
if let link = attributedText.attribute(NSAttributedStringKey.link, at: charIndex, effectiveRange: &range) as? URL {
print("\n\t##-->You just tapped on '\(link)' withRange = \(NSStringFromRange(range))\n")
self.didTouchedLink?(link, range, location)
}
}
}
}
如何使用,
let textView = TextView()//Init your textview and assign attributedString and other properties you want.
textView.didTouchedLink = { (url,tapRange,point) in
//here goes your other logic for successfull URL location
}
我是这样解决这个问题的——我将我的可选文本视图作为一个子类,将 canPerformAction 重写为 return false。
class CustomTextView: UITextView {
override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
我为 Objective C 做的是创建一个子 class 并覆盖 textViewdidChangeSelection: 委托方法,所以在实现中 class:
#import "CustomTextView.h"
@interface CustomTextView()<UITextViewDelegate>
@end
@implementation CustomTextView
。
.
.
.
.
.
.
- (void) textViewDidChangeSelection:(UITextView *)textView
{
UITextRange *selectedRange = [textView selectedTextRange];
NSString *selectedText = [textView textInRange:selectedRange];
if (selectedText.length > 1 && selectedText.length < textView.text.length)
{
textView.selectedRange = NSMakeRange(0, 0);
}
}
别忘了设置 self.delegate = self
这是一个 Swift 4 解决方案,它允许点击通过槽,但按下 link 时除外;
在父视图中
private(set) lazy var textView = YourCustomTextView()
func setupView() {
textView.isScrollEnabled = false
textView.isUserInteractionEnabled = false
let tapGr = UITapGestureRecognizer(target: textView, action: nil)
tapGr.delegate = textView
addGestureRecognizer(tapGr)
textView.translatesAutoresizingMaskIntoConstraints = false
addSubview(textView)
NSLayoutConstraint.activate(textView.edges(to: self))
}
自定义UITextView
class YourCustomTextView: UITextView, UIGestureRecognizerDelegate {
var onLinkTapped: (URL) -> Void = { print([=11=]) }
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gesture = gestureRecognizer as? UITapGestureRecognizer else {
return true
}
let location = gesture.location(in: self)
guard let closest = closestPosition(to: location), let startPosition = position(from: closest, offset: -1), let endPosition = position(from: closest, offset: 1) else {
return false
}
guard let textRange = textRange(from: startPosition, to: endPosition) else {
return false
}
let startOffset = offset(from: beginningOfDocument, to: textRange.start)
let endOffset = offset(from: beginningOfDocument, to: textRange.end)
let range = NSRange(location: startOffset, length: endOffset - startOffset)
guard range.location != NSNotFound, range.length != 0 else {
return false
}
guard let linkAttribute = attributedText.attributedSubstring(from: range).attribute(.link, at: 0, effectiveRange: nil) else {
return false
}
guard let linkString = linkAttribute as? String, let url = URL(string: linkString) else {
return false
}
guard delegate?.textView?(self, shouldInteractWith: url, in: range, interaction: .invokeDefaultAction) ?? true else {
return false
}
onLinkTapped(url)
return true
}
}
Swift 4.2
简单
class MyTextView: UITextView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: UITextDirection(rawValue: UITextLayoutDirection.left.rawValue)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
return attributedText.attribute(NSAttributedString.Key.link, at: startIndex, effectiveRange: nil) != nil
}
}
这对我有用:
@interface MessageTextView : UITextView <UITextViewDelegate>
@end
@implementation MessageTextView
-(void)awakeFromNib{
[super awakeFromNib];
self.delegate = self;
}
- (BOOL)canBecomeFirstResponder {
return NO;
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
textView.selectedTextRange = nil;
[textView endEditing:YES];
}
@end
我最终结合了 and 的解决方案(iOS < 11 变体)。这按预期工作:read-only,hyperlinks 仍在工作的不可选择的 UITextView。 Coeur 解决方案的优点之一是触摸检测是即时的,不显示突出显示,也不允许拖放 link.
这是生成的代码:
class HyperlinkEnabledReadOnlyTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isEditable = false
isSelectable = false
initHyperLinkDetection()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isEditable = false
isSelectable = false
initHyperLinkDetection()
}
// MARK: - Prevent interaction except on hyperlinks
// Combining and
private var linkGestureRecognizer: UITapGestureRecognizer!
private func initHyperLinkDetection() {
// Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
//
// So we add our own UITapGestureRecognizer, which moreover detects taps faster than native one
linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
linkGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(linkGestureRecognizer)
linkGestureRecognizer.isEnabled = true // because previous call sets it to false
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
// Prevents drag and drop gestures, but also prevents a crash with links on iOS 11.0 and 11.1.
//
gestureRecognizer.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Allow only taps located over an hyperlink
var location = point
location.x -= textContainerInset.left
location.y -= textContainerInset.top
guard location.x >= bounds.minX, location.x <= bounds.maxX, location.y >= bounds.minY, location.y <= bounds.maxY else { return false }
let charIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return attributedText.attribute(.link, at: charIndex, effectiveRange: nil) != nil
}
@objc private func textTapped(recognizer: UITapGestureRecognizer) {
guard recognizer == linkGestureRecognizer else { return }
var location = recognizer.location(in: self)
location.x -= textContainerInset.left
location.y -= textContainerInset.top
guard location.x >= bounds.minX, location.x <= bounds.maxX, location.y >= bounds.minY, location.y <= bounds.maxY else { return }
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let characterRange = NSRange(location: characterIndex, length: 1)
if let attachment = attributedText?.attribute(.attachment, at: index, effectiveRange: nil) as? NSTextAttachment {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
}
}
if let url = attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
}
}
}
}
请注意,我在编译 .attachment
枚举案例时遇到了一些问题,我将其删除是因为我没有使用它。
一个丑陋的但一个好人。
private class LinkTextView: UITextView {
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
[]
}
override func caretRect(for position: UITextPosition) -> CGRect {
CGRect.zero.offsetBy(dx: .greatestFiniteMagnitude, dy: .greatestFiniteMagnitude)
}
}
已使用禁用滚动的文本视图进行测试。
@Max Chuquimia 回答将解决问题。但双击仍会显示 textView 的选项菜单。只需将下面的代码添加到您的自定义视图中。
override func canPerformAction(_ action: Selector, withSender sender: (Any)?) -> Bool {
UIMenuController.shared.hideMenu()
//do not display the menu
self.resignFirstResponder()
//do not allow the user to selected anything
return false
}
只点击链接不选择的解决方法
- 子类
UITextView
来处理手势,使其只能点击。根据 的回答
class UnselectableTappableTextView: UITextView {
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
return false
}
}
- 设置
delegate
以禁用 3D Touch 中的 .preview
。参考 hackingwithswift
class ViewController: UIViewController, UITextViewDelegate {
@IBOutlet var textView: UITextView!
override func viewDidLoad() {
//...
textView.delegate = self
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
UIApplication.shared.open(URL)
// Disable `.preview` by 3D Touch and other interactions
return false
}
}
如果您希望 UITextView
仅用于嵌入没有滚动手势的链接,这可能是一个很好的解决方案。
启用 selectable
以便 link 可以点击,然后在检测到选择后立即取消选择。它会在 UI 有机会更新之前生效。
yourTextView.selectable = YES;//required for tappable links
yourTextView.delegate = self;//use <UITextViewDelegate> in .h
- (void)textViewDidChangeSelection:(UITextView *)textView {
if (textView == yourTextView && textView.selectedTextRange != nil) {
// `selectable` is required for tappable links but we do not want
// regular text selection, so clear the selection immediately.
textView.delegate = nil;//Disable delegate while we update the selectedTextRange otherwise this method will get called again, circularly, on some architectures (e.g. iPhone7 sim)
textView.selectedTextRange = nil;//clear selection, will happen before copy/paste/etc GUI renders
textView.delegate = self;//Re-enable delegate
}
}
现在,在较新的 iOS 版本中,如果您在 UITextView 上按住并拖动,光标现在可以使用上述方法闪烁和闪烁,因此为了解决这个问题,我们将简单地制作通过调整色调颜色清除光标和选择(突出显示),然后将 link 颜色设置回我们想要的任何颜色(因为它之前也使用了色调颜色)。
UIColor *originalTintColor = textView.tintColor;
[textView setTintColor:[UIColor clearColor]];//hide selection and highlight which now appears for a split second when tapping and holding in newer iOS versions
[textView setLinkTextAttributes:@{NSForegroundColorAttributeName: originalTintColor}];//manually set link color since it was using tint color before
请试一试:
func textViewDidChangeSelection(_ textView: UITextView) {
textView.selectedTextRange = nil
}
SWIFT 5
以下是对我有用的不同答案和评论的组合:
UITextView 的子类:
class DescriptionAndLinkTextView: UITextView {
// MARK: - Initialization
required init?(coder: NSCoder) {
super.init(coder: coder)
dataDetectorTypes = .all
backgroundColor = .clear
isSelectable = true
isEditable = false
isScrollEnabled = false
contentInset = .zero
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
linkTextAttributes = [.foregroundColor: UIColor.red,
.font: UIFont.systemFontSize,
.underlineStyle: 0,
.underlineColor: UIColor.clear]
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard super.point(inside: point, with: event) else { return false }
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
guard startIndex < self.attributedText.length - 1 else { return false } // to handle the case where the text ends with a link and the user taps in the space after the link.
return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
}
}
如何使用它(在本例中,在表格视图单元格中):
class MyTableViewCell: UITableViewCell {
// MARK: - IBOutlets
@IBOutlet weak var infoTextView: DescriptionAndLinkTextView! {
didSet {
infoTextView.delegate = self
}
}
// MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .none
}
}
// MARK: - UITextViewDelegate
extension MyTableViewCell: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
DispatchQueue.main.async {
UIApplication.shared.open(URL)
}
// Returning false, to prevent long-press-preview.
return false
}
func textViewDidChangeSelection(_ textView: UITextView) {
if (textView == infoTextView && textView.selectedTextRange != nil) {
// `selectable` is required for tappable links but we do not want
// regular text selection, so clear the selection immediately.
textView.delegate = nil // Disable delegate while we update the selectedTextRange otherwise this method will get called again, circularly, on some architectures (e.g. iPhone7 sim)
textView.selectedTextRange = nil // clear selection, will happen before copy/paste/etc GUI renders
textView.delegate = self // Re-enable delegate
}
}
}
我的解决方法如下
解决方案。
我的问题是在没有 3D 预览的情况下为 UITableViewCell
添加可点击的 link。 我的解决方案可能会帮助那些正在寻找解决方案的人表格视图。
为此,我只需要将委托添加到我的 tableView 中的 TextView 变量,这是一个 UITableViewCell 实例变量。这是我的 tableView code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.update(text: text)
cell.textView.delegate = self
return cell
}
这是我自定义的 TaleViewCell
final class TableViewCell: UITableViewCell, UITextViewDelegate {
@IBOutlet weak var textView: UITextView!
func update(text: text) {
textView.isEditable = false
textView.isUserInteractionEnabled = true
}
}
这是分机
extension UITextView {
// To prevent blue background selection from any situation
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
return false
}
}
我有一个显示 NSAttributedString
的 UITextView
。 textView 的 editable
和 selectable
属性都设置为 false
.
attributedString 包含 URL,我想允许点击 URL 打开浏览器。但只有当 selectable
属性设置为 true
.
如何允许用户仅点击链接进行交互,而不是选择文本?
经过一番研究,我找到了解决方案。这是一个 hack,我不知道它是否可以在未来的 iOS 版本中使用,但它现在可以使用 (iOS 9.3)。
只需添加此 UITextView
类别(要点 here):
@implementation UITextView (NoFirstResponder)
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
@try {
id targetAndAction = ((NSMutableArray *)[gestureRecognizer valueForKey:@"_targets"]).firstObject;
NSArray <NSString *>*actions = @[@"action=loupeGesture:", // link: no, selection: shows circle loupe and blue selectors for a second
@"action=longDelayRecognizer:", // link: no, selection: no
/*@"action=smallDelayRecognizer:", // link: yes (no long press), selection: no*/
@"action=oneFingerForcePan:", // link: no, selection: shows rectangular loupe for a second, no blue selectors
@"action=_handleRevealGesture:"]; // link: no, selection: no
for (NSString *action in actions) {
if ([[targetAndAction description] containsString:action]) {
[gestureRecognizer setEnabled:false];
}
}
}
@catch (NSException *e) {
}
@finally {
[super addGestureRecognizer: gestureRecognizer];
}
}
}
我发现摆弄内部手势识别器的概念有点可怕,所以试图找到另一种解决方案。
我发现我们可以覆盖 point(inside:with:)
以有效地允许 "tap-through" 当用户没有触及其中包含 link 的文本时:
// Inside a UITextView subclass:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
}
这也意味着,如果您在 UITableViewCell
中有一个带有 link 的 UITextView
,那么在点击非 link 时仍然会调用 tableView(didSelectRowAt:)
]ed 部分文本 :)
Swift 3.0
以上Objective-C版本来自@Lukas
extension UITextView {
override open func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) {
do {
let array = try gestureRecognizer.value(forKey: "_targets") as! NSMutableArray
let targetAndAction = array.firstObject
let actions = ["action=oneFingerForcePan:",
"action=_handleRevealGesture:",
"action=loupeGesture:",
"action=longDelayRecognizer:"]
for action in actions {
print("targetAndAction.debugDescription: \(targetAndAction.debugDescription)")
if targetAndAction.debugDescription.contains(action) {
gestureRecognizer.isEnabled = false
}
}
} catch let exception {
print("TXT_VIEW EXCEPTION : \(exception)")
}
defer {
super.addGestureRecognizer(gestureRecognizer)
}
}
}
}
如果您的最低部署目标是 iOS 11.2 或更高版本
您可以通过子类化 UITextView
并禁止可以 select 某些东西的手势来禁用文本 selection。
下面的解决方案是:
- 与 isEditable 兼容
- 与 isScrollEnabled 兼容
- 与链接兼容
/// Class to allow links but no selection.
/// Basically, it disables unwanted UIGestureRecognizer from UITextView.
///
class UnselectableTappableTextView: UITextView {
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer {
// required for compatibility with isScrollEnabled
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// allowing smallDelayRecognizer for links
//
if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
// comparison value is used to distinguish between 0.12 (smallDelayRecognizer) and 0.5 (textSelectionForce and textLoupe)
longPressGestureRecognizer.minimumPressDuration < 0.325 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// preventing selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
gestureRecognizer.isEnabled = false
return false
}
}
如果您的最低部署目标是 iOS 11.1 或更早版本
原生 UITextView 链接手势识别器在 iOS 11.0-11.1 上损坏,需要 小延迟长按 而不是 点击: Xcode 9 UITextView links no longer clickable
您可以使用自己的手势识别器正确支持链接,并且可以通过子类化 UITextView
禁用文本 selection 并禁止可以 select 某些东西或点击某些东西的手势。
下面的解决方案将不允许 selection 并且是:
- 与 isScrollEnabled 兼容
- 与链接兼容
- iOS 11.0 和 iOS 11.1 的解决方法限制,但在点击文本附件时失去 UI 效果
/// Class to support links and to disallow selection.
/// It disables most UIGestureRecognizer from UITextView and adds a UITapGestureRecognizer.
///
class UnselectableTappableTextView: UITextView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
//
// So we add our own UITapGestureRecognizer.
linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
linkGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(linkGestureRecognizer)
linkGestureRecognizer.isEnabled = true
}
var linkGestureRecognizer: UITapGestureRecognizer!
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
// Prevents drag and drop gestures,
// but also prevents a crash with links on iOS 11.0 and 11.1.
//
gestureRecognizer.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == linkGestureRecognizer {
// Supporting links correctly.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if gestureRecognizer is UIPanGestureRecognizer {
// Compatibility support with isScrollEnabled.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// Preventing selection gestures and disabling broken links support.
gestureRecognizer.isEnabled = false
return false
}
@objc func textTapped(recognizer: UITapGestureRecognizer) {
guard recognizer == linkGestureRecognizer else {
return
}
var location = recognizer.location(in: self)
location.x -= textContainerInset.left
location.y -= textContainerInset.top
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let characterRange = NSRange(location: characterIndex, length: 1)
if let attachment = attributedText?.attribute(.attachment, at: index, effectiveRange: nil) as? NSTextAttachment {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
}
}
if let url = attributedText?.attribute(.link, at: index, effectiveRange: nil) as? URL {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
}
}
}
}
像下面那样覆盖 UITextView 并使用它呈现可点击的 link 并保留 html 样式。
public class LinkTextView: UITextView {
override public var selectedTextRange: UITextRange? {
get {
return nil
}
set {}
}
public init() {
super.init(frame: CGRect.zero, textContainer: nil)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
self.tintColor = UIColor.black
self.isScrollEnabled = false
self.delegate = self
self.dataDetectorTypes = []
self.isEditable = false
self.delegate = self
self.font = Style.font(.sansSerif11)
self.delaysContentTouches = true
}
@available(iOS 10.0, *)
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
// Handle link
return false
}
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
// Handle link
return false
}
}
正如 Coeur 所说,您可以子类化 UITextView
覆盖 selectedTextRange
的方法,将其设置为 nil。链接仍然可以点击,但您将无法 select 其余文本。
class PIUnselectableTextView: PITextView {
override public var selectedTextRange: UITextRange? {
get {
return nil
}
set { }
}
}
这是 Max Chuquimia 发布的答案的 Objective C 版本。
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITextPosition *position = [self closestPositionToPoint:point];
if (!position) {
return NO;
}
UITextRange *range = [self.tokenizer rangeEnclosingPosition:position
withGranularity:UITextGranularityCharacter
inDirection:UITextLayoutDirectionLeft];
if (!range) {
return NO;
}
NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument
toPosition:range.start];
return [self.attributedText attribute:NSLinkAttributeName
atIndex:startIndex
effectiveRange:nil] != nil;
}
Swift4,Xcode9.2
下面是 link 的不同方法,将 UITextView 的 isSelectable
属性 变为 false
class TextView: UITextView {
//MARK: Properties
open var didTouchedLink:((URL,NSRange,CGPoint) -> Void)?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = Array(touches)[0]
if let view = touch.view {
let point = touch.location(in: view)
self.tapped(on: point)
}
}
}
extension TextView {
fileprivate func tapped(on point:CGPoint) {
var location: CGPoint = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
let charIndex = layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard charIndex < self.textStorage.length else {
return
}
var range = NSRange(location: 0, length: 0)
if let attributedText = self.attributedText {
if let link = attributedText.attribute(NSAttributedStringKey.link, at: charIndex, effectiveRange: &range) as? URL {
print("\n\t##-->You just tapped on '\(link)' withRange = \(NSStringFromRange(range))\n")
self.didTouchedLink?(link, range, location)
}
}
}
}
如何使用,
let textView = TextView()//Init your textview and assign attributedString and other properties you want.
textView.didTouchedLink = { (url,tapRange,point) in
//here goes your other logic for successfull URL location
}
我是这样解决这个问题的——我将我的可选文本视图作为一个子类,将 canPerformAction 重写为 return false。
class CustomTextView: UITextView {
override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
我为 Objective C 做的是创建一个子 class 并覆盖 textViewdidChangeSelection: 委托方法,所以在实现中 class:
#import "CustomTextView.h"
@interface CustomTextView()<UITextViewDelegate>
@end
@implementation CustomTextView
。 . . . . . .
- (void) textViewDidChangeSelection:(UITextView *)textView
{
UITextRange *selectedRange = [textView selectedTextRange];
NSString *selectedText = [textView textInRange:selectedRange];
if (selectedText.length > 1 && selectedText.length < textView.text.length)
{
textView.selectedRange = NSMakeRange(0, 0);
}
}
别忘了设置 self.delegate = self
这是一个 Swift 4 解决方案,它允许点击通过槽,但按下 link 时除外;
在父视图中
private(set) lazy var textView = YourCustomTextView()
func setupView() {
textView.isScrollEnabled = false
textView.isUserInteractionEnabled = false
let tapGr = UITapGestureRecognizer(target: textView, action: nil)
tapGr.delegate = textView
addGestureRecognizer(tapGr)
textView.translatesAutoresizingMaskIntoConstraints = false
addSubview(textView)
NSLayoutConstraint.activate(textView.edges(to: self))
}
自定义UITextView
class YourCustomTextView: UITextView, UIGestureRecognizerDelegate {
var onLinkTapped: (URL) -> Void = { print([=11=]) }
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gesture = gestureRecognizer as? UITapGestureRecognizer else {
return true
}
let location = gesture.location(in: self)
guard let closest = closestPosition(to: location), let startPosition = position(from: closest, offset: -1), let endPosition = position(from: closest, offset: 1) else {
return false
}
guard let textRange = textRange(from: startPosition, to: endPosition) else {
return false
}
let startOffset = offset(from: beginningOfDocument, to: textRange.start)
let endOffset = offset(from: beginningOfDocument, to: textRange.end)
let range = NSRange(location: startOffset, length: endOffset - startOffset)
guard range.location != NSNotFound, range.length != 0 else {
return false
}
guard let linkAttribute = attributedText.attributedSubstring(from: range).attribute(.link, at: 0, effectiveRange: nil) else {
return false
}
guard let linkString = linkAttribute as? String, let url = URL(string: linkString) else {
return false
}
guard delegate?.textView?(self, shouldInteractWith: url, in: range, interaction: .invokeDefaultAction) ?? true else {
return false
}
onLinkTapped(url)
return true
}
}
Swift 4.2
简单
class MyTextView: UITextView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: UITextDirection(rawValue: UITextLayoutDirection.left.rawValue)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
return attributedText.attribute(NSAttributedString.Key.link, at: startIndex, effectiveRange: nil) != nil
}
}
这对我有用:
@interface MessageTextView : UITextView <UITextViewDelegate>
@end
@implementation MessageTextView
-(void)awakeFromNib{
[super awakeFromNib];
self.delegate = self;
}
- (BOOL)canBecomeFirstResponder {
return NO;
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
textView.selectedTextRange = nil;
[textView endEditing:YES];
}
@end
我最终结合了
这是生成的代码:
class HyperlinkEnabledReadOnlyTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isEditable = false
isSelectable = false
initHyperLinkDetection()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isEditable = false
isSelectable = false
initHyperLinkDetection()
}
// MARK: - Prevent interaction except on hyperlinks
// Combining and
private var linkGestureRecognizer: UITapGestureRecognizer!
private func initHyperLinkDetection() {
// Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
//
// So we add our own UITapGestureRecognizer, which moreover detects taps faster than native one
linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
linkGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(linkGestureRecognizer)
linkGestureRecognizer.isEnabled = true // because previous call sets it to false
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
// Prevents drag and drop gestures, but also prevents a crash with links on iOS 11.0 and 11.1.
//
gestureRecognizer.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Allow only taps located over an hyperlink
var location = point
location.x -= textContainerInset.left
location.y -= textContainerInset.top
guard location.x >= bounds.minX, location.x <= bounds.maxX, location.y >= bounds.minY, location.y <= bounds.maxY else { return false }
let charIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return attributedText.attribute(.link, at: charIndex, effectiveRange: nil) != nil
}
@objc private func textTapped(recognizer: UITapGestureRecognizer) {
guard recognizer == linkGestureRecognizer else { return }
var location = recognizer.location(in: self)
location.x -= textContainerInset.left
location.y -= textContainerInset.top
guard location.x >= bounds.minX, location.x <= bounds.maxX, location.y >= bounds.minY, location.y <= bounds.maxY else { return }
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let characterRange = NSRange(location: characterIndex, length: 1)
if let attachment = attributedText?.attribute(.attachment, at: index, effectiveRange: nil) as? NSTextAttachment {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
}
}
if let url = attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
}
}
}
}
请注意,我在编译 .attachment
枚举案例时遇到了一些问题,我将其删除是因为我没有使用它。
一个丑陋的但一个好人。
private class LinkTextView: UITextView {
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
[]
}
override func caretRect(for position: UITextPosition) -> CGRect {
CGRect.zero.offsetBy(dx: .greatestFiniteMagnitude, dy: .greatestFiniteMagnitude)
}
}
已使用禁用滚动的文本视图进行测试。
@Max Chuquimia 回答将解决问题。但双击仍会显示 textView 的选项菜单。只需将下面的代码添加到您的自定义视图中。
override func canPerformAction(_ action: Selector, withSender sender: (Any)?) -> Bool {
UIMenuController.shared.hideMenu()
//do not display the menu
self.resignFirstResponder()
//do not allow the user to selected anything
return false
}
只点击链接不选择的解决方法
- 子类
UITextView
来处理手势,使其只能点击。根据 的回答
class UnselectableTappableTextView: UITextView {
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
return false
}
}
- 设置
delegate
以禁用 3D Touch 中的.preview
。参考 hackingwithswift
class ViewController: UIViewController, UITextViewDelegate {
@IBOutlet var textView: UITextView!
override func viewDidLoad() {
//...
textView.delegate = self
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
UIApplication.shared.open(URL)
// Disable `.preview` by 3D Touch and other interactions
return false
}
}
如果您希望 UITextView
仅用于嵌入没有滚动手势的链接,这可能是一个很好的解决方案。
启用 selectable
以便 link 可以点击,然后在检测到选择后立即取消选择。它会在 UI 有机会更新之前生效。
yourTextView.selectable = YES;//required for tappable links
yourTextView.delegate = self;//use <UITextViewDelegate> in .h
- (void)textViewDidChangeSelection:(UITextView *)textView {
if (textView == yourTextView && textView.selectedTextRange != nil) {
// `selectable` is required for tappable links but we do not want
// regular text selection, so clear the selection immediately.
textView.delegate = nil;//Disable delegate while we update the selectedTextRange otherwise this method will get called again, circularly, on some architectures (e.g. iPhone7 sim)
textView.selectedTextRange = nil;//clear selection, will happen before copy/paste/etc GUI renders
textView.delegate = self;//Re-enable delegate
}
}
现在,在较新的 iOS 版本中,如果您在 UITextView 上按住并拖动,光标现在可以使用上述方法闪烁和闪烁,因此为了解决这个问题,我们将简单地制作通过调整色调颜色清除光标和选择(突出显示),然后将 link 颜色设置回我们想要的任何颜色(因为它之前也使用了色调颜色)。
UIColor *originalTintColor = textView.tintColor;
[textView setTintColor:[UIColor clearColor]];//hide selection and highlight which now appears for a split second when tapping and holding in newer iOS versions
[textView setLinkTextAttributes:@{NSForegroundColorAttributeName: originalTintColor}];//manually set link color since it was using tint color before
请试一试:
func textViewDidChangeSelection(_ textView: UITextView) {
textView.selectedTextRange = nil
}
SWIFT 5
以下是对我有用的不同答案和评论的组合:
UITextView 的子类:
class DescriptionAndLinkTextView: UITextView {
// MARK: - Initialization
required init?(coder: NSCoder) {
super.init(coder: coder)
dataDetectorTypes = .all
backgroundColor = .clear
isSelectable = true
isEditable = false
isScrollEnabled = false
contentInset = .zero
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
linkTextAttributes = [.foregroundColor: UIColor.red,
.font: UIFont.systemFontSize,
.underlineStyle: 0,
.underlineColor: UIColor.clear]
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard super.point(inside: point, with: event) else { return false }
guard let pos = closestPosition(to: point) else { return false }
guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
let startIndex = offset(from: beginningOfDocument, to: range.start)
guard startIndex < self.attributedText.length - 1 else { return false } // to handle the case where the text ends with a link and the user taps in the space after the link.
return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
}
}
如何使用它(在本例中,在表格视图单元格中):
class MyTableViewCell: UITableViewCell {
// MARK: - IBOutlets
@IBOutlet weak var infoTextView: DescriptionAndLinkTextView! {
didSet {
infoTextView.delegate = self
}
}
// MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .none
}
}
// MARK: - UITextViewDelegate
extension MyTableViewCell: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
DispatchQueue.main.async {
UIApplication.shared.open(URL)
}
// Returning false, to prevent long-press-preview.
return false
}
func textViewDidChangeSelection(_ textView: UITextView) {
if (textView == infoTextView && textView.selectedTextRange != nil) {
// `selectable` is required for tappable links but we do not want
// regular text selection, so clear the selection immediately.
textView.delegate = nil // Disable delegate while we update the selectedTextRange otherwise this method will get called again, circularly, on some architectures (e.g. iPhone7 sim)
textView.selectedTextRange = nil // clear selection, will happen before copy/paste/etc GUI renders
textView.delegate = self // Re-enable delegate
}
}
}
我的解决方法如下
我的问题是在没有 3D 预览的情况下为 UITableViewCell
添加可点击的 link。 我的解决方案可能会帮助那些正在寻找解决方案的人表格视图。
为此,我只需要将委托添加到我的 tableView 中的 TextView 变量,这是一个 UITableViewCell 实例变量。这是我的 tableView code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.update(text: text)
cell.textView.delegate = self
return cell
}
这是我自定义的 TaleViewCell
final class TableViewCell: UITableViewCell, UITextViewDelegate {
@IBOutlet weak var textView: UITextView!
func update(text: text) {
textView.isEditable = false
textView.isUserInteractionEnabled = true
}
}
这是分机
extension UITextView {
// To prevent blue background selection from any situation
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
return false
}
}