mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
fix snapshot not completed issue
This commit is contained in:
parent
e8518188ac
commit
1d9e4c88c2
6 changed files with 159 additions and 32 deletions
|
@ -183,6 +183,7 @@
|
||||||
7BAF54D927ACD0E3003D12F8 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */; };
|
7BAF54D927ACD0E3003D12F8 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */; };
|
||||||
7BAF54DA27ACD0E3003D12F8 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */; };
|
7BAF54DA27ACD0E3003D12F8 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */; };
|
||||||
7BAF54DC27ACD12B003D12F8 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */; };
|
7BAF54DC27ACD12B003D12F8 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */; };
|
||||||
|
7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */; };
|
||||||
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
|
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
|
||||||
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
|
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
|
||||||
|
@ -1191,6 +1192,7 @@
|
||||||
7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
|
7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
|
||||||
7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
|
7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
|
||||||
7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableLabel.swift; sourceTree = "<group>"; };
|
||||||
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
|
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
|
||||||
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -2916,6 +2918,7 @@
|
||||||
B8CCF638239721E20091D419 /* TabBar.swift */,
|
B8CCF638239721E20091D419 /* TabBar.swift */,
|
||||||
B8BB82B423947F2D00BA5194 /* TextField.swift */,
|
B8BB82B423947F2D00BA5194 /* TextField.swift */,
|
||||||
C3C3CF8824D8EED300E1CCE7 /* TextView.swift */,
|
C3C3CF8824D8EED300E1CCE7 /* TextView.swift */,
|
||||||
|
7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4533,6 +4536,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C331FF972558FA6B00070591 /* Fonts.swift in Sources */,
|
C331FF972558FA6B00070591 /* Fonts.swift in Sources */,
|
||||||
|
7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */,
|
||||||
C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */,
|
C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */,
|
||||||
C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */,
|
C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */,
|
||||||
C331FFE72558FB0000070591 /* TextField.swift in Sources */,
|
C331FFE72558FB0000070591 /* TextField.swift in Sources */,
|
||||||
|
|
|
@ -513,7 +513,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
||||||
// Show the context menu if applicable
|
// Show the context menu if applicable
|
||||||
guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
|
guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
|
||||||
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell,
|
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell,
|
||||||
let snapshot = cell.snapshot(afterScreenUpdates: false), contextMenuWindow == nil,
|
let snapshot = cell.bubbleView.snapshotView(afterScreenUpdates: false), contextMenuWindow == nil,
|
||||||
!ContextMenuVC.actions(for: viewItem, delegate: self).isEmpty else { return }
|
!ContextMenuVC.actions(for: viewItem, delegate: self).isEmpty else { return }
|
||||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||||
let frame = cell.convert(cell.bubbleView.frame, to: UIApplication.shared.keyWindow!)
|
let frame = cell.convert(cell.bubbleView.frame, to: UIApplication.shared.keyWindow!)
|
||||||
|
|
|
@ -399,6 +399,10 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
|
||||||
delegate?.handleMentionSelected(mention, from: view)
|
delegate?.handleMentionSelected(mention, from: view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tapableLabel(_ label: TappableLabel, didTapUrl url: String, atRange range: NSRange) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
private func container(for button: InputViewButton) -> UIView {
|
private func container(for button: InputViewButton) -> UIView {
|
||||||
let result = UIView()
|
let result = UIView()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import NVActivityIndicatorView
|
import NVActivityIndicatorView
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
final class LinkPreviewView : UIView {
|
final class LinkPreviewView : UIView {
|
||||||
private let viewItem: ConversationViewItem?
|
private let viewItem: ConversationViewItem?
|
||||||
|
@ -59,7 +60,7 @@ final class LinkPreviewView : UIView {
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var bodyTextView: UITextView?
|
var bodyTextView: TappableLabel?
|
||||||
|
|
||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
private static let loaderSize: CGFloat = 24
|
private static let loaderSize: CGFloat = 24
|
||||||
|
@ -163,7 +164,7 @@ final class LinkPreviewView : UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Delegate
|
// MARK: Delegate
|
||||||
protocol LinkPreviewViewDelegate : UITextViewDelegate & BodyTextViewDelegate {
|
protocol LinkPreviewViewDelegate : TappableLabelDelegate {
|
||||||
var lastSearchedText: String? { get }
|
var lastSearchedText: String? { get }
|
||||||
|
|
||||||
func handleLinkPreviewCanceled()
|
func handleLinkPreviewCanceled()
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
||||||
private var isHandlingLongPress: Bool = false
|
private var isHandlingLongPress: Bool = false
|
||||||
private var unloadContent: (() -> Void)?
|
private var unloadContent: (() -> Void)?
|
||||||
private var previousX: CGFloat = 0
|
private var previousX: CGFloat = 0
|
||||||
var albumView: MediaAlbumView?
|
var albumView: MediaAlbumView?
|
||||||
var bodyTextView: UITextView?
|
var bodyTextView: TappableLabel?
|
||||||
// Constraints
|
// Constraints
|
||||||
private lazy var headerViewTopConstraint = headerView.pin(.top, to: .top, of: self, withInset: 1)
|
private lazy var headerViewTopConstraint = headerView.pin(.top, to: .top, of: self, withInset: 1)
|
||||||
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0)
|
private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0)
|
||||||
|
@ -631,9 +632,10 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
func tapableLabel(_ label: TappableLabel, didTapUrl url: String, atRange range: NSRange) {
|
||||||
|
if let URL = URL(string: url) {
|
||||||
delegate?.openURL(URL)
|
delegate?.openURL(URL)
|
||||||
return false
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetReply() {
|
private func resetReply() {
|
||||||
|
@ -776,50 +778,67 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
||||||
return isGroupThread && viewItem.shouldShowSenderProfilePicture && senderSessionID != nil
|
return isGroupThread && viewItem.shouldShowSenderProfilePicture && senderSessionID != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func getBodyTextView(for viewItem: ConversationViewItem, with availableWidth: CGFloat, textColor: UIColor, delegate: UITextViewDelegate & BodyTextViewDelegate) -> UITextView {
|
static func getBodyTextView(for viewItem: ConversationViewItem, with availableWidth: CGFloat, textColor: UIColor, delegate: TappableLabelDelegate) -> TappableLabel {
|
||||||
// Take care of:
|
// Take care of:
|
||||||
// • Highlighting mentions
|
// • Highlighting mentions
|
||||||
// • Linkification
|
// • Linkification
|
||||||
// • Highlighting search results
|
// • Highlighting search results
|
||||||
|
|
||||||
|
func detectLinks(body: String?) -> [String: NSRange] {
|
||||||
|
var links: [String: NSRange] = [:]
|
||||||
|
guard let body = body else { return links }
|
||||||
|
let detector: NSDataDetector
|
||||||
|
do {
|
||||||
|
detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||||
|
} catch {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
let matches = detector.matches(in: body, options: [], range: NSRange(location: 0, length: body.count))
|
||||||
|
for match in matches {
|
||||||
|
guard let matchURL = match.url else { continue }
|
||||||
|
|
||||||
|
// If the URL entered didn't have a scheme it will default to 'http', we want to catch this and
|
||||||
|
// set the scheme to 'https' instead as we don't load previews for 'http' so this will result
|
||||||
|
// in more previews actually getting loaded without forcing the user to enter 'https://' before
|
||||||
|
// every URL they enter
|
||||||
|
let urlString: String = (matchURL.absoluteString == "http://\(body)" ?
|
||||||
|
"https://\(body)" :
|
||||||
|
matchURL.absoluteString
|
||||||
|
)
|
||||||
|
if URL(string: urlString) != nil {
|
||||||
|
links[urlString] = (body as NSString).range(of: urlString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() }
|
guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() }
|
||||||
let isOutgoing = (message.interactionType() == .outgoingMessage)
|
let isOutgoing = (message.interactionType() == .outgoingMessage)
|
||||||
let result = BodyTextView(snDelegate: delegate)
|
|
||||||
result.isEditable = false
|
|
||||||
let attributes: [NSAttributedString.Key:Any] = [
|
let attributes: [NSAttributedString.Key:Any] = [
|
||||||
.foregroundColor : textColor,
|
.foregroundColor : textColor,
|
||||||
.font : UIFont.systemFont(ofSize: getFontSize(for: viewItem))
|
.font : UIFont.systemFont(ofSize: getFontSize(for: viewItem))
|
||||||
]
|
]
|
||||||
let attributedText = NSMutableAttributedString(attributedString: MentionUtilities.highlightMentions(in: message.body ?? "", isOutgoingMessage: isOutgoing, threadID: viewItem.interaction.uniqueThreadId, attributes: attributes))
|
let attributedText = NSMutableAttributedString(attributedString: MentionUtilities.highlightMentions(in: message.body ?? "", isOutgoingMessage: isOutgoing, threadID: viewItem.interaction.uniqueThreadId, attributes: attributes))
|
||||||
|
let links = detectLinks(body: message.body)
|
||||||
|
for (urlString, range) in links {
|
||||||
|
let linkCustomAttributes: [NSAttributedString.Key : Any] = [
|
||||||
|
.font: UIFont.systemFont(ofSize: getFontSize(for: viewItem)),
|
||||||
|
.foregroundColor: textColor,
|
||||||
|
.underlineColor: textColor,
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.attachment: URL(string: urlString)!]
|
||||||
|
attributedText.addAttributes(linkCustomAttributes, range: range)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = TappableLabel()
|
||||||
result.attributedText = attributedText
|
result.attributedText = attributedText
|
||||||
result.dataDetectorTypes = .link
|
|
||||||
result.backgroundColor = .clear
|
result.backgroundColor = .clear
|
||||||
result.isOpaque = false
|
result.isOpaque = false
|
||||||
result.textContainerInset = UIEdgeInsets.zero
|
|
||||||
result.contentInset = UIEdgeInsets.zero
|
|
||||||
result.textContainer.lineFragmentPadding = 0
|
|
||||||
result.isScrollEnabled = false
|
|
||||||
result.isUserInteractionEnabled = true
|
result.isUserInteractionEnabled = true
|
||||||
result.delegate = delegate
|
result.delegate = delegate
|
||||||
result.linkTextAttributes = [ .foregroundColor : textColor, .underlineStyle : NSUnderlineStyle.single.rawValue ]
|
|
||||||
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
|
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
|
||||||
let size = result.sizeThatFits(availableSpace)
|
let size = result.sizeThatFits(availableSpace)
|
||||||
result.set(.height, to: size.height)
|
result.set(.height, to: size.height)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VisibleMessageCell {
|
|
||||||
public func snapshot(afterScreenUpdates afterUpdates: Bool) -> UIView? {
|
|
||||||
let labelForRendering = UILabel()
|
|
||||||
labelForRendering.numberOfLines = 0
|
|
||||||
labelForRendering.backgroundColor = self.bubbleView.backgroundColor
|
|
||||||
if let bodyTextView = self.bodyTextView {
|
|
||||||
labelForRendering.attributedText = bodyTextView.attributedText
|
|
||||||
self.snContentView.addSubview(labelForRendering)
|
|
||||||
labelForRendering.frame = self.snContentView.convert(bodyTextView.frame, to: self.snContentView)
|
|
||||||
}
|
|
||||||
let snapshot = self.bubbleView.snapshotView(afterScreenUpdates: true)
|
|
||||||
labelForRendering.removeFromSuperview()
|
|
||||||
return snapshot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
99
SessionUIKit/Components/TappableLabel.swift
Normal file
99
SessionUIKit/Components/TappableLabel.swift
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// Requirements:
|
||||||
|
// • Links should show up properly and be tappable.
|
||||||
|
// • Text should * not * be selectable.
|
||||||
|
// • The long press interaction that shows the context menu should still work.
|
||||||
|
|
||||||
|
// See https://stackoverflow.com/questions/47983838/how-can-you-change-the-color-of-links-in-a-uilabel
|
||||||
|
|
||||||
|
public protocol TappableLabelDelegate: AnyObject {
|
||||||
|
func tapableLabel(_ label: TappableLabel, didTapUrl url: String, atRange range: NSRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TappableLabel: UILabel {
|
||||||
|
|
||||||
|
private var links: [String: NSRange] = [:]
|
||||||
|
private(set) var layoutManager = NSLayoutManager()
|
||||||
|
private(set) var textContainer = NSTextContainer(size: CGSize.zero)
|
||||||
|
private(set) var textStorage = NSTextStorage() {
|
||||||
|
didSet {
|
||||||
|
textStorage.addLayoutManager(layoutManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public weak var delegate: TappableLabelDelegate?
|
||||||
|
|
||||||
|
public override var attributedText: NSAttributedString? {
|
||||||
|
didSet {
|
||||||
|
if let attributedText = attributedText {
|
||||||
|
textStorage = NSTextStorage(attributedString: attributedText)
|
||||||
|
findLinksAndRange(attributeString: attributedText)
|
||||||
|
} else {
|
||||||
|
textStorage = NSTextStorage()
|
||||||
|
links = [:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var lineBreakMode: NSLineBreakMode {
|
||||||
|
didSet {
|
||||||
|
textContainer.lineBreakMode = lineBreakMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var numberOfLines: Int {
|
||||||
|
didSet {
|
||||||
|
textContainer.maximumNumberOfLines = numberOfLines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
isUserInteractionEnabled = true
|
||||||
|
layoutManager.addTextContainer(textContainer)
|
||||||
|
textContainer.lineFragmentPadding = 0
|
||||||
|
textContainer.lineBreakMode = lineBreakMode
|
||||||
|
textContainer.maximumNumberOfLines = numberOfLines
|
||||||
|
numberOfLines = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
textContainer.size = bounds.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private func findLinksAndRange(attributeString: NSAttributedString) {
|
||||||
|
links = [:]
|
||||||
|
let enumerationBlock: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Void = { [weak self] value, range, isStop in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
if let value = value {
|
||||||
|
let stringValue = "\(value)"
|
||||||
|
strongSelf.links[stringValue] = range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributeString.enumerateAttribute(.link, in: NSRange(0..<attributeString.length), options: [.longestEffectiveRangeNotRequired], using: enumerationBlock)
|
||||||
|
attributeString.enumerateAttribute(.attachment, in: NSRange(0..<attributeString.length), options: [.longestEffectiveRangeNotRequired], using: enumerationBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
guard let locationOfTouch = touches.first?.location(in: self) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
textContainer.size = bounds.size
|
||||||
|
let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
|
||||||
|
for (urlString, range) in links where NSLocationInRange(indexOfCharacter, range) {
|
||||||
|
delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue