私はチャットアプリを構築しています。パフォーマンス上の理由から、チャットメッセージを表示するにはUITextViewではなくUILabelを使用する必要があります。私は以前にTextViewを使用しましたが、スクロールでのデータ検出が非常に遅く、途切れがちです。
問題は現在、UILabelsのリンク/電話/アドレスなどが検出されないことです。
文字列内のリンクまたは電話番号がどこにあるかを確認し、UILabel内で強調表示してクリック可能にするにはどうすればよいですか?
これを行うためにリンクの属性を追加する方法に関する多くの記事を読みましたが、それらはすべて、範囲または部分文字列がわかっているリンクでした。
任意の文字列を取り、にリンクが含まれているかどうか、およびそれらのリンクがどこにあるかを確認してから、tapGestureRecognizerをラベルに追加し、タップが発生した場所に基づいてアクションを実行します。
私は外部ライブラリ(TTTAttributedLabel)を組み込もうとしましたが、Swiftを使用していて、Swiftのドキュメントが制限されていることを発見しました。ライブラリがリンクが自動的に検出されていません。
参照ソース-
ILabelのNSAttributedStringにタップ可能な「リンク」を作成しますか?
Swift 4.0に変換されます
これを試して -
以下のようなUILabelのサブクラスを作成します-
Swift 4.0
class CustomLabel: UILabel {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
var onCharacterTapped: ((_ label: UILabel, _ characterIndex: Int) -> Void)?
let tapGesture = UITapGestureRecognizer()
override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
} else {
textStorage = NSTextStorage()
}
}
}
override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
func setUp() {
isUserInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
tapGesture.addTarget(self, action: #selector(CustomLabel.labelTapped(_:)))
addGestureRecognizer(tapGesture)
}
override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
@objc func labelTapped(_ gesture: UITapGestureRecognizer) {
guard gesture.state == .ended else {
return
}
let locationOfTouch = gesture.location(in: gesture.view)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX,
y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x, y: locationOfTouch.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer,
in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
onCharacterTapped?(self, indexOfCharacter)
}
}
ViewコントローラーのviewDidLoad
メソッド内で、以下のようにそのクラスのインスタンスを作成します-
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let label = CustomLabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
let attributedString = NSMutableAttributedString(string: "String with a link", attributes: nil)
let linkRange = NSMakeRange(14, 4); // for the Word "link" in the string above
let linkAttributes: [NSAttributedStringKey : AnyObject] = [
NSAttributedStringKey.foregroundColor : UIColor.blue, NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue as AnyObject,
NSAttributedStringKey.link: "http://www.Apple.com" as AnyObject ]
attributedString.setAttributes(linkAttributes, range:linkRange)
label.attributedText = attributedString
label.onCharacterTapped = { label, characterIndex in
// DO YOUR STUFF HERE
}
}