diff --git a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h deleted file mode 100644 index 775aa9f8b..000000000 --- a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -@import UIKit; - -NS_ASSUME_NONNULL_BEGIN - -@interface UITextView(MatrixKit) - -/** - Determine if there is a link near a location point in UITextView bounds. - - @param point The point inside the UITextView bounds - @return YES to indicate that a link has been detected near the location point. - */ -- (BOOL)isThereALinkNearPoint:(CGPoint)point; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m deleted file mode 100644 index b22817f60..000000000 --- a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2019 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "UITextView+MatrixKit.h" - -@implementation UITextView(MatrixKit) - -- (BOOL)isThereALinkNearPoint:(CGPoint)point -{ - if (!CGRectContainsPoint(self.bounds, point)) - { - return NO; - } - - UITextPosition *textPosition = [self closestPositionToPoint:point]; - - if (!textPosition) - { - return NO; - } - - UITextRange *textRange = [self.tokenizer rangeEnclosingPosition:textPosition - withGranularity:UITextGranularityCharacter - inDirection:UITextLayoutDirectionLeft]; - - if (!textRange) - { - return NO; - } - - NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument toPosition:textRange.start]; - - if (startIndex < 0) - { - return NO; - } - - return [self.attributedText attribute:NSLinkAttributeName atIndex:startIndex effectiveRange:NULL] != nil; -} - -@end diff --git a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.swift b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.swift new file mode 100644 index 000000000..2807c3f49 --- /dev/null +++ b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.swift @@ -0,0 +1,66 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +extension UITextView { + private enum Constants { + /// Distance threshold at which link can still be considered as "near" from location. + static let linkHorizontalDistanceThreshold: CGFloat = 16.0 + } + + /// Determine if there is a link near a location point in UITextView bounds. + /// + /// - Parameters: + /// - point: The point inside the UITextView bounds + /// - Returns: true to indicate that a link has been detected near the location point. + @objc func isThereALinkNearPoint(_ point: CGPoint) -> Bool { + guard bounds.contains(point), + let textPosition = closestPosition(to: point) + else { + return false + } + + // Depending on cursor position on a character containing both an attachment + // and a link (e.g. a mention pill), a positive result can be retrieved either + // from textStylingAtPosition or tokenizer's rangeEnclosingPosition. + if let attributes = textStyling(at: textPosition, in: .forward), + attributes[.link] != nil { + // Using textStyling shouldn't provide false positives. + return true + } else if let textRange = tokenizer.rangeEnclosingPosition(textPosition, + with: .character, + inDirection: .layout(.left)) { + let startIndex = offset(from: beginningOfDocument, to: textRange.start) + if attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil { + // Fix false positives from tokenizer's rangeEnclosingPosition. + // These occur if given point is located on the same line as a + // trailing character containing a mention pill. Detected link is + // rejected if actual distance from attachment trailing to point + // is greater than linkHorizontalDistanceThreshold. + let glyphIndex = layoutManager.glyphIndexForCharacter(at: startIndex) + let attachmentWidth = layoutManager.attachmentSize(forGlyphAt: glyphIndex).width + let glyphStartX = layoutManager.location(forGlyphAt: glyphIndex).x + let distance = point.x - (glyphStartX + attachmentWidth) + + // TODO: improve using a range perhaps (beware of negative values when no attachment) + return distance < Constants.linkHorizontalDistanceThreshold + } + } + + return false + } +} diff --git a/Riot/Modules/MatrixKit/Views/MXKMessageTextView.m b/Riot/Modules/MatrixKit/Views/MXKMessageTextView.m index d68ff2cba..416e2ac65 100644 --- a/Riot/Modules/MatrixKit/Views/MXKMessageTextView.m +++ b/Riot/Modules/MatrixKit/Views/MXKMessageTextView.m @@ -15,7 +15,7 @@ */ #import "MXKMessageTextView.h" -#import "UITextView+MatrixKit.h" +#import "GeneratedInterface-Swift.h" @interface MXKMessageTextView() diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m index c4367d598..1c1b10dcc 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m @@ -28,7 +28,6 @@ #import "NSBundle+MatrixKit.h" #import "MXRoom+Sync.h" #import "MXKMessageTextView.h" -#import "UITextView+MatrixKit.h" #import "GeneratedInterface-Swift.h"