martega
martega

Reputation: 2143

How do you set the size of a view based NSTextAttachment?

I am attempting to make something that would make it possible to add custom UIView subclasses as attachments in UITextView. As of iOS 15, there appears to be an API that allows you to register custom "view providers" for text attachments, and I've successfully managed to get a custom view showing up in a UITextView using that API, however I've run into a fairly large road block. I can't figure out how to set the size of one of these text attachment views. Each of my text attachment views gets a fixed size of {.width = 37, .height = 48} and anything I've done to try to change that results in UITextView's default rendering for when you have a plain text attachment with no image (just a "missing image" placeholder icon).

In the sample code below, I just setup a view controller that has a single text view which tries to render some text with two custom view attachments. The first attachment just gets the default size that Apple gives it. For the other one I try to set the size, and you can see how it breaks things.

Am I missing something with how to use this API? I feel like I must be otherwise this API is fairly useless? What good is embedding UIView's in a text view if you can make them the right size?

Code

//--------------------------------------------------------------------

@interface MyViewProvider : NSTextAttachmentViewProvider

@end

@implementation MyViewProvider

- (void)loadView {
    self.view = [UIView new];
    self.view.backgroundColor = [UIColor blueColor];
}

@end

//--------------------------------------------------------------------

@interface MyViewController ()

@end

@implementation MyViewController {
    UITextView *_textView;
}

#pragma mark - Initialization

- (void)viewDidLoad {
    [super viewDidLoad];

    [NSTextAttachment registerTextAttachmentViewProviderClass:[MyViewProvider class] forFileType:@"public.data"];

    NSTextAttachment *firstAttachment = [[NSTextAttachment alloc] initWithData:nil ofType:@"public.data"];

    NSTextAttachment *secondAttachment = [[NSTextAttachment alloc] initWithData:nil ofType:@"public.data"];
    secondAttachment.bounds = CGRectMake(0, 0, 20, 20);

    NSMutableAttributedString *string = [NSMutableAttributedString new];
    [string appendAttributedString:[[NSAttributedString alloc] initWithString:@"This is the 1st attachment: "]];
    [string appendAttributedString:[NSAttributedString attributedStringWithAttachment:firstAttachment]];

    [string appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThis is the 2nd attachment: "]];
    [string appendAttributedString:[NSAttributedString attributedStringWithAttachment:secondAttachment]];

    _textView = [UITextView new];
    _textView.layer.borderColor = [UIColor redColor].CGColor;
    _textView.layer.borderWidth = 1.0;
    _textView.attributedText = string;
    [self.view addSubview:_textView];
}

#pragma mark - Layout

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    _textView.frame = CGRectInset(self.view.bounds, 20, 60);
}

@end

Result

Result of running code sample. There are 2 text attachments, but one has failed to render after giving it a custom size.

Upvotes: 2

Views: 384

Answers (1)

Ben Davis
Ben Davis

Reputation: 236

I had the same trouble as you and found a solution by overriding attachmentBounds(for:location:textContainer:proposedLineFragment:position:) in my NSTextAttachmentViewProvider.

Here is my code...

NSTextAttachment.registerViewProviderClass(MyAttachmentViewProvider.self, forFileType: "public.item")
final class MyAttachmentViewProvider: NSTextAttachmentViewProvider {
  override init(textAttachment: NSTextAttachment,
                parentView: ViewType?,
                textLayoutManager: NSTextLayoutManager?,
                location: NSTextLocation) {
    super.init(textAttachment: textAttachment,
               parentView: parentView,
               textLayoutManager: textLayoutManager,
               location: location)
    tracksTextAttachmentViewBounds = true
  }

  override func loadView() {
    super.loadView()
    view = TestView(frame: .zero)
  }

  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.height, height: proposedLineFragment.height)
  }
}

Upvotes: 1

Related Questions