UITextView:禁用选择,允许链接

时间:2016-03-24 10:44:28

标签: ios uitextview nsattributedstring

我有UITextView,显示NSAttributedString。 textView的editableselectable属性都设置为false

attributionString包含一个URL,我想允许点击URL来打开浏览器。但只有在selectable属性设置为true时才能与URL进行互动。

如何仅允许用户互动来点击链接,而不是选择文字?

20 个答案:

答案 0 :(得分:55)

我发现摆弄内部手势识别器的概念有点可怕,所以试图寻找另一种解决方案。 我发现我们可以覆盖// 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 } 以有效地允许"点击"当用户没有触及内部有链接的文本时:

UITextView

这也意味着如果UITableViewCell内有tableView(didSelectRowAt:)内的链接,则在点击文字的非链接部分时仍会调用 <input type="checkbox" id="perlengkapans1" name="perlengkapans" data-stok="1" value="name item 1" /> <input type="checkbox" id="perlengkapans2" name="perlengkapans" data-stok="4" value="name item 2" /> <input type="checkbox" id="perlengkapans3" name="perlengkapans" data-stok="0" value="name item 3" />

答案 1 :(得分:9)

正如Cœur所说,你可以继承UITextView覆盖selectedTextRange的方法,将其设置为 nil 。并且链接仍然是可点击的,但您将无法选择其余文本。

class CustomTextView: UITextView {
override public var selectedTextRange: UITextRange? {
    get {
        return nil
    }
    set { }
}

答案 2 :(得分:5)

经过一些研究,我已经找到了解决方案。这是一个黑客攻击,我不知道它是否会在未来的iOS版本中运行,但它现在正在运行(iOS 9.3)。

只需添加此UITextView类别(Gist 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];
        }
    }
}

答案 3 :(得分:4)

仅可点击链接而不进行选择的解决方案。

  1. 子类UITextView可以处理手势,因此只能使其可点击。根据{{​​3}}
  2. 的回答
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
    }

}
  1. 设置delegate以从 3D Touch 禁用.preview。从Cœur
  2. 获取参考
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来嵌入链接而无需滚动手势,这可能是一个很好的解决方案。

答案 4 :(得分:3)

如果您的最低部署目标是iOS 11.2或更高版本

您可以通过继承UITextView并禁止可以选择内容的手势来禁用文本选择。

以下解决方案是:

  • 与isEditable兼容
  • 与isScrollEnabled兼容
  • 与链接兼容
/// Class to allow links but no selection.
/// Basically, it disables unwanted UIGestureRecognizer from UITextView.
/// https://stackoverflow.com/a/49443814/1033581
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
        // https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable
        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并禁止可以选择内容或点按某些内容的手势来禁用文本选择。

以下解决方案将禁止选择,并且是:

  • 与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.
/// https://stackoverflow.com/a/49443814/1033581
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:
        // https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable
        // 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.
        // https://stackoverflow.com/a/49535011/1033581
        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)
            }
        }
    }
}

答案 5 :(得分:1)

Swift 3.0

通过@Lukas

获取上述Objective-C版本
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)
                }
            }
        }

    }

答案 6 :(得分:1)

以下是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;
}

答案 7 :(得分:1)

这对我有用:

@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

答案 8 :(得分:0)

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

如何使用它(在这种情况下,在一个tableview单元格中):

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

答案 9 :(得分:0)

请尝试:

func textViewDidChangeSelection(_ textView: UITextView) {
    textView.selectedTextRange = nil
}

答案 10 :(得分:0)

启用可选以使链接检测有效,然后只需取消选择检测到的第二个选择,它将在UI有机会更新之前起作用。

yourTextView.selectable = YES;
yourTextView.delegate = self;

-(void)textViewDidChangeSelection:(UITextView *)textView {

    if (textView == yourTextView) {//selectable is required for clickable links, just unselect if they select
        textView.selectedRange = NSMakeRange(0, 0);
    }

}

答案 11 :(得分:0)

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

答案 12 :(得分:0)

一个丑陋但好吃的东西。

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

在禁用滚动的文本视图中进行了测试。

答案 13 :(得分:0)

我最终结合了https://stackoverflow.com/a/44878203/2015332https://stackoverflow.com/a/49443814/2015332的解决方案(iOS <11变体)。这可以按预期工作:只读,不可选择的UITextView,其上的超链接仍在起作用。 Coeur解决方案的优点之一是触摸检测是即时的,不会显示突出显示,也不会拖放链接。

这是结果代码:

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 https://stackoverflow.com/a/44878203/2015332 and https://stackoverflow.com/a/49443814/1033581

    private var linkGestureRecognizer: UITapGestureRecognizer!

    private func initHyperLinkDetection() {
        // Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
        // https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable

        // 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.
        // https://stackoverflow.com/a/49535011/1033581
        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枚举大小写时遇到麻烦,我将其删除是因为我没有使用它。

答案 14 :(得分:0)

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

答案 15 :(得分:0)

这是一个Swift 4解决方案,它允许水龙头通过水槽,但按下链接时除外。

在父视图中

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($0) }

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

答案 16 :(得分:0)

我为目标C所做的是创建一个子类并覆盖textViewdidChangeSelection:委托方法,因此在实现类中:

#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

答案 17 :(得分:0)

这是我解决此问题的方法-我将可选的textview设为重写canPerformAction的子类以返回false。

class CustomTextView: UITextView {

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

}

答案 18 :(得分:0)

Swift 4,Xcode 9.2

以下是链接的不同方法,将UITextView的JS_CODE = """ # This file contains the JavaScript (CoffeeScript) implementation # for a Bokeh custom extension. The "surface3d.py" contains the # python counterpart. # # This custom model wraps one part of the third-party vis.js library: # # http://visjs.org/index.html # # Making it easy to hook up python data analytics tools (NumPy, SciPy, # Pandas, etc.) to web presentations using the Bokeh server. # These "require" lines are similar to python "import" statements import * as p from "core/properties" import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom" # This defines some default options for the Graph3d feature of vis.js # See: http://visjs.org/graph3d_examples.html for more details. OPTIONS = width: '700px' height: '700px' style: 'dot-color' showPerspective: true showGrid: true keepAspectRatio: true verticalRatio: 1.0 showLegend: false cameraPosition: horizontal: -0.35 vertical: 0.22 distance: 1.8 dotSizeRatio: 0.01 tooltip: (point) -> return 'Party: <b>' + point.z + '</b><br>' + 'Name of the politician: <b>' + point.data.extra # To create custom model extensions that will render on to the HTML canvas # or into the DOM, we must create a View subclass for the model. Currently # Bokeh models and views are based on BackBone. More information about # using Backbone can be found here: # # http://backbonejs.org/ # # In this case we will subclass from the existing BokehJS ``LayoutDOMView``, # corresponding to our export class Surface3dView extends LayoutDOMView initialize: (options) -> super(options) url = "http://visjs.org/dist/vis.js" script = document.createElement('script') script.src = url script.async = false script.onreadystatechange = script.onload = () => @_init() document.querySelector("head").appendChild(script) _init: () -> # Create a new Graph3s using the vis.js API. This assumes the vis.js has # already been loaded (e.g. in a custom app template). In the future Bokeh # models will be able to specify and load external scripts automatically. # # Backbone Views create <div> elements by default, accessible as @el. Many # Bokeh views ignore this default <div>, and instead do things like draw # to the HTML canvas. In this case though, we use the <div> to attach a # Graph3d to the DOM. @_graph = new vis.Graph3d(@el, @get_data(), OPTIONS) # Set Backbone listener so that when the Bokeh data source has a change # event, we can process the new data @connect(@model.data_source.change, () => @_graph.setData(@get_data()) ) # This is the callback executed when the Bokeh data has an change. Its basic # function is to adapt the Bokeh data source to the vis.js DataSet format. get_data: () -> data = new vis.DataSet() source = @model.data_source for i in [0...source.get_length()] data.add({ x: source.get_column(@model.x)[i] y: source.get_column(@model.y)[i] z: source.get_column(@model.z)[i] extra: source.get_column(@model.extra)[i] style: source.get_column(@model.color)[i] }) return data # We must also create a corresponding JavaScript Backbone model sublcass to # correspond to the python Bokeh model subclass. In this case, since we want # an element that can position itself in the DOM according to a Bokeh layout, # we subclass from ``LayoutDOM`` export class Surface3d extends LayoutDOM # This is usually boilerplate. In some cases there may not be a view. default_view: Surface3dView # The ``type`` class attribute should generally match exactly the name # of the corresponding Python class. type: "Surface3d" # The @define block adds corresponding "properties" to the JS model. These # should basically line up 1-1 with the Python model class. Most property # types have counterparts, e.g. ``bokeh.core.properties.String`` will be # ``p.String`` in the JS implementatin. Where the JS type system is not yet # as rich, you can use ``p.Any`` as a "wildcard" property type. @define { x: [ p.String ] y: [ p.String ] z: [ p.String ] color: [ p.String ] extra: [ p.String ] data_source: [ p.Instance ] } """ # This custom extension model will have a DOM view that should layout-able in # Bokeh layouts, so use ``LayoutDOM`` as the base class. If you wanted to create # a custom tool, you could inherit from ``Tool``, or from ``Glyph`` if you # wanted to create a custom glyph, etc. class Surface3d(LayoutDOM): # The special class attribute ``__implementation__`` should contain a string # of JavaScript (or CoffeeScript) code that implements the JavaScript side # of the custom extension model. __implementation__ = JS_CODE # Below are all the "properties" for this model. Bokeh properties are # class attributes that define the fields (and their types) that can be # communicated automatically between Python and the browser. Properties # also support type validation. More information about properties in # can be found here: # # https://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties # This is a Bokeh ColumnDataSource that can be updated in the Bokeh # server by Python code data_source = Instance(ColumnDataSource) # The vis.js library that we are wrapping expects data for x, y, z, and # color. The data will actually be stored in the ColumnDataSource, but # these properties let us specify the *name* of the column that should # be used for each field. x = String y = String z = String extra = String color = String 属性设为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)
            }
        }

    }
}

答案 19 :(得分:0)

覆盖UITextView,如下所示,并使用它来保留可保存的html样式的可点击链接。

公共类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
}

}