web-dev-qa-db-ja.com

クリック可能なリンクがあるがテキストの強調表示がないUITextView

編集不可能なテキストを表示するUITextViewがあります。ユーザーのリンクや電話番号などをテキストで自動的に解析し、クリックできるようにしたい。

ただし、ユーザーがテキストを強調表示できるようにしたくないのは、これらの長押しとダブルタップの操作をオーバーライドして、別のことを実行したいからです。

IOS7でリンクを解析するには、UITextViewのSelectableスイッチをオンにする必要がありますが、Selectableは強調表示も有効にしますが、これは望ましくありません。

強調表示を防ぐためにLongPressジェスチャをオーバーライドしようとしましたが、リンクの通常のタップも無効になっているようです...

for (UIGestureRecognizer *recognizer in cell.messageTextView.gestureRecognizers) {
    if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
        recognizer.enabled = NO;
    }
    if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]){
        recognizer.enabled = YES;
    }
}

そこには似たようなスレッドがたくさんありますが、リンクが有効になっているというこの特定の質問に対処しているようには見えません。テキストは強調表示できません。

24
Trespassers W

私はまったく同じ問題に取り組んでおり、UITextViewのデリゲートに以下を追加して、選択が行われるとすぐに選択をクリアすることが最善の方法でした。

- (void)textViewDidChangeSelection:(UITextView *)textView {
    if(!NSEqualRanges(textView.selectedRange, NSMakeRange(0, 0))) {
        textView.selectedRange = NSMakeRange(0, 0);
    }
}

再帰を防ぐためのチェックに注意してください。選択のみが無効になっているため、これで問題はほぼ解決されます。リンクは引き続き機能します。

もう1つの接線上の問題は、テキストビューが引き続きファーストレスポンダーになることです。これは、選択した範囲を設定した後、目的のファーストレスポンダーを設定することで修正できます。

注:残っている唯一の視覚的な奇妙な点は、押し続けると虫眼鏡が表示されることです。

24
blushmessenger

これが特定のケースで機能するかどうかはわかりませんが、テキストビューリンクをクリック可能にする必要があるが、テキスト選択を行わせたくないという同様のケースがあり、テキストビューを使用してCollectionViewCellにデータを表示していました。

-canBecomeFirstResponderをオーバーライドして、NOを返すだけで済みました。

@interface MYTextView : UITextView
@end

@implementation MYTextView

- (BOOL)canBecomeFirstResponder {
    return NO;
}

@end
16
lramirez135

他の投稿で書いたように、別の解決策があります。

いくつかのテストの後、私は解決策を見つけました。

リンクをアクティブにし、選択を有効にしない場合は、gestureRecognizersを編集する必要があります。

たとえば、LongPressGestureRecognizersは3つあります。 1つはリンクのクリック用(minimumPressDuration = 0.12)、2つ目は編集可能モードでのズーム用(minimumPressDuration = 0.5)、3つ目は選択用(minimumPressDuration = 0.8)です。このソリューションでは、編集モードでの選択と2番目のズームのLongPressGestureRecognizerが削除されます。

NSArray *textViewGestureRecognizers = self.captionTextView.gestureRecognizers;
NSMutableArray *mutableArrayOfGestureRecognizers = [[NSMutableArray alloc] init];
for (UIGestureRecognizer *gestureRecognizer in textViewGestureRecognizers) {
    if (![gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
        [mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
    } else {
        UILongPressGestureRecognizer *longPressGestureRecognizer = (UILongPressGestureRecognizer *)gestureRecognizer;
        if (longPressGestureRecognizer.minimumPressDuration < 0.3) {
            [mutableArrayOfGestureRecognizers addObject:gestureRecognizer];
        }
    }
}
self.captionTextView.gestureRecognizers = mutableArrayOfGestureRecognizers;

IOS 9でテストされていますが、すべてのバージョン(iOS 7、8、9)で動作するはずです。お役に立てば幸いです。 :)

11
Kubík Kašpar

Swift 4、Xcode 9.2

以下は別のアプローチです、

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
}
5
Vatsal Shukla

これが私のために働いたものです。

拡大鏡を取り除くことはできませんでしたが、これによりテキストビューを選択可能に保つことができます(リンクをタップできます)が、選択に関連するUIはすべて削除します。 iOS9でのみテストされています。

注意Swift以下!

まず、UITextViewをサブクラス化し、次の関数を含めます。

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

これにより、コピーなどのメニューが無効になります。次に、initから呼び出すsetupメソッドを含めます。ここで、セットアップ関連の一連のタスクを実行します。 (私はストーリーボードからのこれらのテキストビューのみを使用するため、デコーダーの初期化を行います):

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setup()
}

private func setup() {
    selectable = true
    editable = false
    tintColor = UIColor.clearColor()
}

リンクをタップ可能に保つにはSelectable = true、編集可能なテキストビューではリンクをタップできないため、editable = false。クリアtintColorを指定すると、選択範囲の最初と最後に表示される青いバーが非表示になります。

最後に、サブクラス化されたテキストビューを使用しているコントローラーで、UITextViewDelegateプロトコルが含まれていること、デリゲートがtextView.delegate = selfに設定されていることを確認し、次のデリゲート関数を実装します。

func textViewDidChangeSelection(textView: UITextView) {
    var range = NSRange()
    range.location = 0
    range.length = 0
    textView.selectedRange = range
}

この機能がないと、選択バーとコンテキストメニューは無効になりますが、選択したテキストの後ろに色付きの背景が残ります。この関数は、その選択の背景を取り除きます。

私が言ったように、私は拡大鏡を取り除く方法を見つけていません、しかし彼らがリンク以外のどこかで長いタップをするならば、拡大鏡が消えると何も残されません。

3
RyJ

これは、リンクされたテキストに到達するポイントのみを認識するUITextViewサブクラスアプローチです。

class LinkTextView: UITextView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let tapLocation = point.applying(CGAffineTransform(translationX: -textContainerInset.left, y: -textContainerInset.top))
        let characterAtIndex = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        let linkAttributeAtIndex = textStorage.attribute(.link, at: characterAtIndex, effectiveRange: nil)

        // Returns true for points located on linked text
        return linkAttributeAtIndex != nil
    }

    override func becomeFirstResponder() -> Bool {
        // Returning false disables double-tap selection of link text
        return false
    }
}
2
zath

テキストの選択が無効になり、虫眼鏡が非表示になるため、これで問題がほぼ解決されます。リンクは引き続き機能します。

func textViewDidChangeSelection(_ textView: UITextView) {
    if let gestureRecognizers = textView.gestureRecognizers {
        for recognizer in gestureRecognizers {
            if recognizer is UILongPressGestureRecognizer {
                if let index = textView.gestureRecognizers?.index(of: recognizer) {
                    textView.gestureRecognizers?.remove(at: index)
                }
            }
        }
    }
}

注:削除する代わりに、レコグナイザーを目的のレコグナイザーに置き換えることができます。

2
Kush Taneja

将来の実装変更の可能性に直面して確かに脆弱ですが、KubíkKašparのアプローチは私のために働いた唯一のものです。

ただし、(a)UITextViewをサブクラス化すると、これを簡単に行うことができます。(b)許可するインタラクションがリンクのタップのみの場合は、タップをすぐに認識させることができます。

@interface GMTextView : UITextView
@end

@implementation GMTextView

- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {

  // discard all recognizers but the one that activates links, by just not calling super
  // (in iOS 9.2.3 a short press for links is 0.12s, long press for selection is 0.75s)

  if ([gestureRecognizer isMemberOfClass:UILongPressGestureRecognizer.class] &&
      ((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration < 0.25) {  

    ((UILongPressGestureRecognizer*)gestureRecognizer).minimumPressDuration = 0.0;
    [super addGestureRecognizer:gestureRecognizer]; 
  }
}

@end
1
jawj

lramirez135KubíkKašpar の答えがこの問題をほぼ解決したことがわかりました。ただし、 lramirez135 の答えは長押しで選択することはできませんが、 KubíkKašpar の答えはiOSのバージョンによって異なります。

私はそれらのロジックを組み合わせて、SwiftでUITextViewのこのサブクラスを作成しました。これは私のために機能します。

class CustomUITextView: UITextView {
    override var canBecomeFirstResponder: Bool {
        return false
    }

    init() {
        super.init(frame: .zero, textContainer: nil)

        guard let textViewGestureRecognizers = self.gestureRecognizers else { return }
        for textViewGestureRecognizer in textViewGestureRecognizers {
            if textViewGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) {
                textViewGestureRecognizer.isEnabled = false
            }
        }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Objective-cバージョン:

@implementation CustomTextView

- (BOOL)canBecomeFirstResponder {
    return NO;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.backgroundColor = UIColor.whiteColor;
        self.textContainerInset = UIEdgeInsetsZero;
        self.textContainer.lineFragmentPadding = 0;
        self.editable = NO;
        self.scrollEnabled = NO;
        self.linkTextAttributes = @{ NSForegroundColorAttributeName: UIColor.orangeColor };

        NSArray<UIGestureRecognizer *> *textViewGestureRecognizers = (NSArray<UIGestureRecognizer *> *)self.gestureRecognizers;
        for (UIGestureRecognizer *textViewGestureRecognizer in textViewGestureRecognizers) {
            if ([textViewGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
                [textViewGestureRecognizer setEnabled:NO];
            }
        }
    }
    return self;
}

@end
0
Banghua Zhao