// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit public extension NSAttributedString.Key { static let currentUserMentionBackgroundColor: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundColor") static let currentUserMentionBackgroundCornerRadius: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundCornerRadius") static let currentUserMentionBackgroundPadding: NSAttributedString.Key = NSAttributedString.Key(rawValue: "currentUserMentionBackgroundPadding") } public class HighlightMentionBackgroundView: UIView { weak var targetLabel: UILabel? var maxPadding: CGFloat = 0 init(targetLabel: UILabel) { self.targetLabel = targetLabel super.init(frame: .zero) self.isOpaque = false } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Functions public func calculateMaxPadding(for attributedText: NSAttributedString) -> CGFloat { var allMentionRadii: [CGFloat?] = [] let path: CGMutablePath = CGMutablePath() path.addRect(CGRect( x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude )) let framesetter = CTFramesetterCreateWithAttributedString(attributedText as CFAttributedString) let frame: CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedText.length), path, nil) let lines: [CTLine] = frame.lines lines.forEach { line in let runs: [CTRun] = line.ctruns runs.forEach { run in let attributes: NSDictionary = CTRunGetAttributes(run) allMentionRadii.append( attributes .value(forKey: NSAttributedString.Key.currentUserMentionBackgroundPadding.rawValue) as? CGFloat ) } } let maxRadii: CGFloat? = allMentionRadii .compactMap { $0 } .max() return (maxRadii ?? 0) } // MARK: - Drawing override public func draw(_ rect: CGRect) { guard let targetLabel: UILabel = self.targetLabel, let attributedText: NSAttributedString = targetLabel.attributedText, let context = UIGraphicsGetCurrentContext() else { return } // Need to invery the Y axis because iOS likes to render from the bottom left instead of the top left context.textMatrix = .identity context.translateBy(x: 0, y: bounds.size.height) context.scaleBy(x: 1.0, y: -1.0) // Note: Calculations MUST happen based on the 'targetLabel' size as this class has extra padding // which can result in calculations being off let path = CGMutablePath() let size = targetLabel.sizeThatFits(CGSize(width: targetLabel.bounds.width, height: .greatestFiniteMagnitude)) path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity) let framesetter = CTFramesetterCreateWithAttributedString(attributedText as CFAttributedString) let frame: CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedText.length), path, nil) let lines: [CTLine] = frame.lines var origins = [CGPoint](repeating: .zero, count: lines.count) CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &origins) for lineIndex in 0..