Add events to views inside attribute string
paulocoutinhox opened this issue · 1 comments
paulocoutinhox commented
Hi,
I want know if you understand how to add events to a view inserted into attributed string, like a view with buttons.
Example:
import UIKit
import Down
class MainViewController: UIViewController {
private var textView: UITextView!
private let exampleMarkdown = """
Aqui está um link para um produto: [produto](ubook-app://product/123).
Aqui está um link para um outro produto: [produto](ubook-app://product/456) que também é um produto bom.
"""
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupTextView()
parseMarkdown()
}
private func setupTextView() {
textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isEditable = false
textView.backgroundColor = .white
textView.textColor = .black
textView.isScrollEnabled = true
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.delegate = self
textView.isUserInteractionEnabled = true
textView.isSelectable = false
view.addSubview(textView)
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
}
private func parseMarkdown() {
let down = Down(markdownString: exampleMarkdown)
if let attributedString = try? down.toAttributedString() {
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
let fullRange = NSRange(location: 0, length: attributedString.length)
mutableAttributedString.enumerateAttributes(in: fullRange, options: []) { attributes, range, _ in
if let link = attributes[.link] as? URL, link.scheme == "ubook-app", link.host == "product" {
let productId = link.lastPathComponent
let attachment = NSTextAttachment()
let productView = createProductView(productId: productId)
attachment.image = imageFromView(view: productView)
let attachmentString = NSAttributedString(attachment: attachment)
mutableAttributedString.replaceCharacters(in: range, with: attachmentString)
self.textView.attributedText = mutableAttributedString
// Carregar os dados do produto de forma assíncrona
self.loadProductData(productId: productId) { product in
DispatchQueue.main.async {
self.updateProductView(productView: productView, product: product)
attachment.image = self.imageFromView(view: productView)
self.textView.attributedText = mutableAttributedString // Atualizar o texto após carregar o produto
self.textView.layoutIfNeeded() // Garantir que a UI seja atualizada
}
}
}
}
}
}
private func createProductView(productId: String) -> UIView {
let productView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 80))
productView.backgroundColor = .red
productView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(productViewTapped(_:)))
productView.addGestureRecognizer(tapGesture)
let loadingView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 80))
loadingView.backgroundColor = .green
loadingView.translatesAutoresizingMaskIntoConstraints = false
loadingView.tag = 1
productView.addSubview(loadingView)
let activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.startAnimating()
loadingView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: loadingView.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: loadingView.centerYAnchor),
loadingView.topAnchor.constraint(equalTo: productView.topAnchor),
loadingView.leadingAnchor.constraint(equalTo: productView.leadingAnchor),
loadingView.trailingAnchor.constraint(equalTo: productView.trailingAnchor),
loadingView.bottomAnchor.constraint(equalTo: productView.bottomAnchor)
])
let productContentView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 80))
productContentView.backgroundColor = .blue
productContentView.translatesAutoresizingMaskIntoConstraints = false
productContentView.isHidden = true
productContentView.tag = 2
productView.addSubview(productContentView)
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.tag = 3
productContentView.addSubview(imageView)
let titleLabel = UILabel()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.textColor = .white
titleLabel.tag = 4
productContentView.addSubview(titleLabel)
let listenButton = UIButton(type: .system)
listenButton.setTitle("OUVIR", for: .normal)
listenButton.backgroundColor = .white
listenButton.setTitleColor(.blue, for: .normal)
listenButton.translatesAutoresizingMaskIntoConstraints = false
listenButton.tag = 5
listenButton.addTarget(self, action: #selector(listenButtonTapped(_:)), for: .touchUpInside)
productContentView.addSubview(listenButton)
let viewButton = UIButton(type: .system)
viewButton.setTitle("VER", for: .normal)
viewButton.backgroundColor = .white
viewButton.setTitleColor(.blue, for: .normal)
viewButton.translatesAutoresizingMaskIntoConstraints = false
viewButton.tag = 6
viewButton.addTarget(self, action: #selector(viewButtonTapped(_:)), for: .touchUpInside)
productContentView.addSubview(viewButton)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: loadingView.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: loadingView.centerYAnchor),
loadingView.topAnchor.constraint(equalTo: productView.topAnchor),
loadingView.leadingAnchor.constraint(equalTo: productView.leadingAnchor),
loadingView.trailingAnchor.constraint(equalTo: productView.trailingAnchor),
loadingView.bottomAnchor.constraint(equalTo: productView.bottomAnchor),
imageView.topAnchor.constraint(equalTo: productContentView.topAnchor, constant: 10),
imageView.leadingAnchor.constraint(equalTo: productContentView.leadingAnchor, constant: 10),
imageView.widthAnchor.constraint(equalToConstant: 50),
imageView.heightAnchor.constraint(equalToConstant: 60),
titleLabel.topAnchor.constraint(equalTo: productContentView.topAnchor, constant: 10),
titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10),
titleLabel.trailingAnchor.constraint(equalTo: productContentView.trailingAnchor, constant: -10),
listenButton.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
listenButton.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10),
listenButton.widthAnchor.constraint(equalToConstant: 80),
viewButton.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
viewButton.leadingAnchor.constraint(equalTo: listenButton.trailingAnchor, constant: 10),
viewButton.trailingAnchor.constraint(equalTo: productContentView.trailingAnchor, constant: -10),
productContentView.topAnchor.constraint(equalTo: productView.topAnchor),
productContentView.leadingAnchor.constraint(equalTo: productView.leadingAnchor),
productContentView.trailingAnchor.constraint(equalTo: productView.trailingAnchor),
productContentView.bottomAnchor.constraint(equalTo: productView.bottomAnchor)
])
return productView
}
@objc private func listenButtonTapped(_ sender: UIButton) {
if let productView = sender.superview?.superview as? UIView,
let titleLabel = productView.viewWithTag(4) as? UILabel {
print("OUVIR botão clicado para o produto: \(titleLabel.text ?? "")")
}
}
@objc private func viewButtonTapped(_ sender: UIButton) {
if let productView = sender.superview?.superview as? UIView,
let titleLabel = productView.viewWithTag(4) as? UILabel {
print("VER botão clicado para o produto: \(titleLabel.text ?? "")")
}
}
@objc private func productViewTapped(_ gesture: UITapGestureRecognizer) {
if let productView = gesture.view,
let titleLabel = productView.viewWithTag(4) as? UILabel {
print("Produto clicado: \(titleLabel.text ?? "")")
}
}
private func updateProductView(productView: UIView, product: Product) {
if let loadingView = productView.viewWithTag(1),
let productContentView = productView.viewWithTag(2),
let imageView = productContentView.viewWithTag(3) as? UIImageView,
let titleLabel = productContentView.viewWithTag(4) as? UILabel {
print("Atualizando a view do produto")
loadingView.isHidden = true
productContentView.isHidden = false
imageView.image = product.image
titleLabel.text = "\(product.title) (\(product.id))"
productView.setNeedsLayout()
productView.layoutIfNeeded()
} else {
print("Não foi possível encontrar as views para atualizar")
}
}
private func imageFromView(view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
private func loadProductData(productId: String, completion: @escaping (Product) -> Void) {
// Simula carregamento assíncrono de dados
DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
let product = Product(id: productId, title: "Produto \(productId)", image: UIImage(systemName: "book")!)
DispatchQueue.main.async {
completion(product)
}
}
}
}
struct Product {
let id: String
let title: String
let image: UIImage
}
extension MainViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return false
}
}
zhgchgli0718 commented
Not related to ZMarkupParser issues.