I want to have a UIView
inside of a UITextView.
For that I use the new NSTextAttachmentViewProvider
class introduced in iOS 15. The views width should always be equal to the width of the UITextView
this width should update when, for example, the screen rotates.
To do that I am using the tracksTextAttachmentViewBounds
property inside of a NSTextAttachmentViewProvider
subclass. If I understand correctly, if this property is set to true, the function attachmentBounds(for:location:textContainer:proposedLineFragment:position:)
of my NSTextAttachmentViewProvider
subclass should be used to determine the views bounds. In the code example below I have set it up in that manner, sadly the function is never called. (The storyboard consists of a UIViewController
with a UITextView
which four constrains (trailing, leading, bottom, top) are set equal to the safe area, nothing special going on). I have also tried to use a NSTextAttachment
subclass in which I override the attachmentBounds(for:location:textContainer:proposedLineFragment:position:)
function. It is also not called.
The view is appearing, but not with the width and height that I have set in the function (see screenshot below), maybe it is using some default values. When I start typing, the view disappears.
I don’t know what I am doing wrong.
Could somebody help me with that problem?
import UIKit
class SomeNSTextAttachmentViewProvider : NSTextAttachmentViewProvider {
override func loadView() {
super.loadView()
tracksTextAttachmentViewBounds = true
view = UIView()
view!.backgroundColor = .purple
}
override func attachmentBounds(
for attributes: [NSAttributedString.Key : Any],
location: NSTextLocation,
textContainer: NSTextContainer?,
proposedLineFragment: CGRect,
position: CGPoint
) -> CGRect {
return CGRect(x: 0, y: 0, width: proposedLineFragment.width, height: 200)
}
}
class ViewController: UIViewController {
@IBOutlet var textView: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
NSTextAttachment.registerViewProviderClass(SomeNSTextAttachmentViewProvider.self, forFileType: "public.data")
let mutableAttributedString = NSMutableAttributedString()
mutableAttributedString.append(NSAttributedString("purple box: "))
mutableAttributedString.append(
NSAttributedString(
attachment: NSTextAttachment(data: nil, ofType: "public.data")
)
)
textView?.attributedText = mutableAttributedString
textView?.font = UIFont.preferredFont(forTextStyle: .body)
}
}
2
Answers
Works now in iOS 16. Also,
tracksTextAttachmentViewBounds = true
should be moved into an initialiser.Just move
tracksTextAttachmentViewBounds
setter intoinit
as bryanboateng suggested.attachmentBounds(for:location:textContainer:proposedLineFragment:position:) is called before
loadView
gets called, so it makes sense to set variable before it is being used.This behavior is undocumented, hope Apple will add some details into documentation later.
P.S. I’ve checked it in iOS 15.0 Simulator – the same code didn’t work. This solution works under iOS 16 both on iPhone and in Simulator.