I am trying to embed a remote image obtained from a URL into a text using TextKit2
I’m using NSTextAttachmentViewProvider
but I can’t get it to work as expected.
There are two things I can’t understand.
-
The height is not working
- The code says height=25.0, but it is actually larger than that. Changing the value of height is not reflected.
-
Horizontal images are not displayed properly.
- After loading the image from the URL, CGRect is set again, but the width of the image remains the same
The behavior looks like this
enter image description here
The code looks like this
import SwiftUI
import SDWebImageSwiftUI
struct ContentView: View {
@State private var text = "test"
var body: some View {
MFMTextView(text: $text)
.frame(maxWidth: .infinity)
}
}
let oyasu = URL(string: "https://media.misskeyusercontent.jp/misskey/f75cd6a7-61f9-4e72-b47f-9ce0b5aa8602.png")!
struct MFMTextView: UIViewRepresentable {
@Binding var text: String
typealias UIViewType = UITextView
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isEditable = false
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
let attributedText = NSMutableAttributedString()
attributedText.append(NSAttributedString(string: text))
attributedText.append(NSAttributedString(attachment: ImageAttachment(url: oyasu)))
attributedText.append(NSAttributedString(string: text))
uiView.attributedText = attributedText
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: MFMTextView
init(_ parent: MFMTextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
}
}
}
final class ImageAttachment: NSTextAttachment {
let imageUrl: URL
init(url: URL) {
self.imageUrl = url
super.init(data: nil, ofType: nil)
allowsTextAttachmentView = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewProvider(
for parentView: UIView?,
location: NSTextLocation,
textContainer: NSTextContainer?
) -> NSTextAttachmentViewProvider? {
let viewProvider = ImageAttachmentViewProvider(
textAttachment: self,
parentView: parentView,
textLayoutManager: textContainer?.textLayoutManager,
location: location,
imageUrl: imageUrl
)
return viewProvider
}
}
final class ImageAttachmentViewProvider: NSTextAttachmentViewProvider {
let imageUrl: URL
init(
textAttachment: NSTextAttachment,
parentView: UIView?,
textLayoutManager: NSTextLayoutManager?,
location: NSTextLocation,
imageUrl: URL
) {
self.imageUrl = imageUrl
super.init(
textAttachment: textAttachment,
parentView: parentView,
textLayoutManager: textLayoutManager,
location: location
)
tracksTextAttachmentViewBounds = true
}
override func loadView() {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
SDWebImageDownloader.shared.downloadImage(with: imageUrl) { [weak self] (image, _, _, _) in
DispatchQueue.main.async { [weak self] in
imageView.image = image
self?.updateViewSize(for: image)
}
}
self.view = imageView
self.view?.frame.size.height = 25.0
}
private func updateViewSize(for image: UIImage?) {
guard let image = image else { return }
let aspectRatio = image.size.width / image.size.height
let height: CGFloat = 25
let width = height * aspectRatio
// Update the view frame with the new size
view?.frame = CGRect(
x: 0.0,
y: 0.0,
width: width,
height: height
)
print(image.size.width)
print(image.size.height)
// Force layout update
view?.setNeedsLayout()
view?.layoutIfNeeded()
}
override func attachmentBounds(
for attributes: [NSAttributedString.Key : Any],
location: NSTextLocation,
textContainer: NSTextContainer?,
proposedLineFragment: CGRect,
position: CGPoint
) -> CGRect {
guard let imageView = view as? UIImageView, let image = imageView.image else {
return .zero
}
let height = 25.0
let aspectRatio = image.size.width / image.size.height
let width = height * aspectRatio
return CGRect(
x: 0.0,
y: 0.0,
width: width,
height: 100.0
)
}
}
Do you have any good ideas?
Or know of some good sample code?