NSAttributedString with image attachment and NSTextTab, text not aligned
Asked Answered
P

2

8

I'm trying to have a UIlabel with an image and title on the left and a list of descriptions with bullets on the right.

To do that I'm using NSAttributedString like this :

NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init];
pStyle.tabStops =
    @[ [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:tabLocation options:[NSDictionary dictionary]] ];

NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"test_image"];
textAttachment.bounds = CGRectMake(0, -3, 15, 15);//resize the image
attString = [NSAttributedString attributedStringWithAttachment:textAttachment].mutableCopy;
[attString appendAttributedString:[[NSAttributedString alloc]
                                      initWithString:[NSString stringWithFormat:@"title\t\u2022 %@",
                                                                                [@[ @"description1", @"description2" ]
                                                                                    componentsJoinedByString:@"\n\t\u2022 "]]
                                          attributes:@{NSParagraphStyleAttributeName : pStyle}]];
label.attributedText = attString;

I expect the list on the right to be left aligned but that's not the case, here is the result I get:

enter image description here

What I expect is the list to be aligned like this:

enter image description here

Protege answered 5/8, 2016 at 15:57 Comment(0)
R
4

The issue is with location parameter in NSTextTab

  • According to description, location parameter helps to position text from left margin. So this is what we needed, just replace below lines

    pStyle.tabStops =  @[ [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:tabLocation options:[NSDictionary dictionary]] ];
    

    with

    pStyle.tabStops = @[[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:[self getTextLocationFor:@"test"]  options:[NSDictionary dictionary]] ];
    
  • Add getTextLocationFor: method to calculate location as follows

    -(CGFloat)getTextLocationFor:(NSString *)inputStr{
         CGSize maximuminputStringWidth = CGSizeMake(FLT_MAX, 30);
         CGRect textRect = [inputStr boundingRectWithSize:maximuminputStringWidth
                                            options:NSStringDrawingUsesLineFragmentOrigin
                                         attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15]}
                                            context:nil];
         UIImageView * testImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"close_red"]];//Change image name with yours
    
         return textRect.size.width + testImage.frame.size.width +2;
    }
    
  • That's it we are ready to go run your project now everything will be fine.

RESULT:

enter image description here

Ruder answered 10/8, 2016 at 6:28 Comment(0)
O
2

if I understand you correctly then try these code:

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 460, 460)];
label.numberOfLines = 0;
[self.view addSubview:label];

NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init];
pStyle.tabStops = @[ [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:40 options:@{}] ];

NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"img"];
textAttachment.bounds = CGRectMake(0, -3, 30, 30);


NSString *string = [NSString stringWithFormat:@"title\n\r\u2022 %@", [@[ @"description1", @"description2" ] componentsJoinedByString:@"\n\r\u2022 "]];

NSMutableAttributedString *attributedString = [[NSMutableAttributedString attributedStringWithAttachment:textAttachment] mutableCopy];
[attributedString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:string attributes:@{NSParagraphStyleAttributeName : pStyle}]];

label.attributedText = attributedString;

Here is result

enter image description here

UPDATE

You can only achieve this using TextKit (NSTextLayoutManager) and specify area which should be use to draw text, or use simple solution and subclass from UIView.

Here is solution with view

ListView.h

@interface ListView : UIView

@property(nonatomic,strong) UIImage *image;
@property(nonatomic,strong) NSString *title;
@property(nonatomic,strong) NSArray *list;

@end

ListView.m

static const CGFloat ImageWidth = 13.f;

@interface ListView()
@property (nonatomic,weak) UIImageView *imageView;
@property (nonatomic,weak) UILabel *titleLabel;
@property (nonatomic,weak) UILabel *listLabel;
@end

@implementation ListView
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    [self setup];
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    [self setup];
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    [self setup];
}

- (void)setup {
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:imageView];
    self.imageView = imageView;

    UILabel *titleLabel = [[UILabel alloc] init];
    titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
    titleLabel.numberOfLines = 0;
    [titleLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
    [titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
    [self addSubview:titleLabel];
    self.titleLabel = titleLabel;

    UILabel *listLabel = [[UILabel alloc] init];
    listLabel.translatesAutoresizingMaskIntoConstraints = NO;
    listLabel.numberOfLines = 0;
    [listLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
    [listLabel setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
    [self addSubview:listLabel];
    self.listLabel = listLabel;

    NSDictionary *views = NSDictionaryOfVariableBindings(imageView,titleLabel,listLabel);
    NSDictionary *metrics = @{ @"ImageHeight" : @(ImageWidth) };

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[imageView(ImageHeight)]-0-[titleLabel]-0-[listLabel]-0-|" options:0 metrics:metrics views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[imageView(ImageHeight)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[titleLabel]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[listLabel]-0-|" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
}

- (void)setImage:(UIImage *)image {
    _image = image;
    self.imageView.image = image;
    [self setNeedsLayout];
}

- (void)setTitle:(NSString *)title {
    _title = title;
    self.titleLabel.text = title;
    [self setNeedsLayout];
}

- (void)setList:(NSArray *)list {
    _list = list;

    NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init];
    pStyle.tabStops = @[ [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:40 options:@{}] ];

    NSString *string = [NSString stringWithFormat:@"\u2022 %@", [list componentsJoinedByString:@"\n\u2022 "]];
    self.listLabel.attributedText = [[NSAttributedString alloc] initWithString:string attributes:@{NSParagraphStyleAttributeName : pStyle}];

    [self setNeedsLayout];
}

@end
Occupational answered 8/8, 2016 at 11:29 Comment(6)
Thank you for your answer, your solution is not working and I don't understand why use \r ?Protege
Could you show what you are expecting in result and I correct my answer.Occupational
I updated my question with the expected output, thanks !Protege
Is it possible to use two labels for you solution?Occupational
I thought about that, but it may be tricky as I have multiple lists like that to display depending on the dataProtege
I cannot achieve these with one attributed string. You can use subclass from view and use it across your app, or use TextKit from Apple (especially LayoutManager)Occupational

© 2022 - 2024 — McMap. All rights reserved.