How to create two text columns in PDF? - ios

The problem is to create two text columns in PDF like this:
For generating PDF I use Apple's guide "Drawing and Printing Guide for iOS", two columns I create with tabs:
NSArray *stringList =
#[
#[string1, string2],
#[#"", string4],
#[string5, string6],
#[string7, string8]
];
resultString = [[NSMutableAttributedString alloc] init];
for (NSArray *row in stringList) {
int i = 0;
NSMutableAttributedString *subResult = [[NSMutableAttributedString alloc] init];
NSMutableArray *tabs = #[].mutableCopy;
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
for (NSString *tempString in row) {
NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:200 options:nil];
if ([tempString isKindOfClass:[NSAttributedString class]]) {
[subResult appendAttributedString:(NSAttributedString *)tempString];
[subResult appendAttributedString:[[NSAttributedString alloc] initWithString:#"\t"]];
} else {
[subResult appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\t", tempString]]];
}
[tabs addObject:tab];
i++;
}
[subResult appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
[style setTabStops:tabs];
[subResult addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, subResult.length)];
[resultString appendAttributedString:subResult];
}
As output I get this:
So, the lines of my string marked with red arrows I want see entirely in the second column, like in the picture №1.

The idea is to split the text into blocks and single lines and to use for each part
- (void)drawInRect:(CGRect)rect;
or
void CTLineDraw(CTLineRef line, CGContextRef context);
Solution:
#import CoreText;
<...>
- (IBAction)createPDF:(id)sender {
PDFFileName = <yourString>;
PDFPath = <yourPath>;
[self generatePdfWithFilePath:PDFPath];
}
- (void)generatePdfWithFilePath:(NSString *)thefilePath {
UIGraphicsBeginPDFContextToFile(thefilePath, CGRectZero, nil);
BOOL done = NO;
do {
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 712), nil);
[self drawText];
done = YES;
}
while (!done);
UIGraphicsEndPDFContext();
}
- (void)drawText {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
//Here you can set up attributes for your text
NSDictionary *attributesForText = #{
NSFontAttributeName:[UIFont fontWithName:#"Georgia" size:12]
};
//Drawing text in line
NSMutableAttributedString *stringOneAttributed = [[NSMutableAttributedString alloc] initWithString:stringOne attributes:attributesForText];
CTLineRef stringOneLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)(stringOneAttributed));
CGContextSetTextPosition(currentContext, x, y);
CTLineDraw(stringOneLine, currentContext);
CFRelease(stringOneLine);
//Drawing block of text in rectangle
NSMutableAttributedString *stringTwoAttributed = [[NSMutableAttributedString alloc] initWithString:stringTwo attributes:attributesForText];
[stringTwoAttributed drawInRect:CGRectMake(x, y, width, height)];
}

Related

custom label not being filled with data completely

I have the following code:
if (product) {
if ([product.title isStringOfInterest]) {
scriptName = [[PPLinearLayoutLabelItem alloc] initWithText:#"" font:[PPFonts bold20] maxWidth:[LayoutValues getMaxWidthClipped]];
[self.topContainerContent addObject:scriptName];
extraName = [[PPLinearLayoutLabelItem alloc] initWithText:#"" font:[PPFonts regular14] maxWidth:[LayoutValues getMaxWidthClipped]];
[scriptName setPaddingTop:20];
[self.topContainerContent addObject:extraName];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
[self loadProductDetails];
});
-(void) loadProductDetails{
IHProduct *product = orderDisplayed.product;
id<ProductDownloadServiceProtocol> productDetailsDownloader = [[ProductDownloadService alloc] initWithClient:[[HttpClient alloc] initWith:APIbackend forEndpoint:EndpointProduct]];
[productDetailsDownloader downloadProductWithProductID:product.productID success:^(ProductDownloadResponse *response) {
scriptName.label.text = [NSString stringWithFormat:#"%# (%#)",response.product.title,response.product.pillTypeShort];
extraName.label.text = response.product.sameAs;
NSString *qtyText = [NSString stringWithFormat:#"%# PACKS (%# at $%# per pack)",[orderDisplayed.interval objectForKey:#"quantity_per_interval"], [orderDisplayed.interval objectForKey:#"quantity_per_interval"], response.product.price];
quantity.label.text = qtyText;
} error:^(ProductDownloadResponse *response) {
[self hideHttpLoadingOverlay];
[RMUniversalAlert showAlertInViewController:self
withTitle:alertErrorTitle
message:#"Error downloading mail order product details"
cancelButtonTitle:OKDialogButton
destructiveButtonTitle:nil
otherButtonTitles:nil
tapBlock:nil];
}];
}
The labels extraname and scriptname are getting filled or getting chopped off in the end i.e. incomplete data. How can I fix this? When I statically put large texts in this the data gets filled into those views nicely. Help!
If you wish adjust width of UILabel based on it text content then use below code;
NSDictionary *fontAttributes = #{NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:14]};
CGRect rectOfText = [textToMeasure boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:fontAttributes
context:nil];
CGRect tempFrame = self.label.frame;
tempFrame.size.width = rectOfText.size.width;
self.label.frame = tempFrame;
And if you wish to change its font based on uilabel width then use below;
NOTE : This will work only if self.label.numberOfLines = 1.
self.label.adjustsFontSizeToFitWidth = YES;
self.label.minimumFontSize = 0;

NSAttributedString with image attachment and NSTextTab, text not aligned

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:
What I expect is the list to be aligned like this:
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:
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
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

CorePlot - multiLine attributed label

I am trying to create a custom label for coreplot using NSAttributedString. Everything works fine when my strings have 1 line.
The problem occurs when I want to have 2 line strings using code:
NSMutableAttributedString* remFinalLabel = [remAttrStr mutableCopy];
NSMutableAttributedString *newLineAttrStr = [[NSMutableAttributedString alloc]initWithString:#"\n"];
[remFinalLabel appendAttributedString:newLineAttrStr];
[remFinalLabel appendAttributedString:rem2AttrStr];
The result looks like that: not even first string is properly displayed. How can I set the number of lines in custom label?
My code to create labels:
NSMutableArray* labelsStrings = [NSMutableArray arrayWithObjects:strAttr1, strAttr2, strAttr3, nil];
NSMutableArray *ticksLocations = [NSMutableArray arrayWithObjects:#0.5, #1.5, #2.5, nil];
NSMutableArray* customLabels = [[NSMutableArray alloc] init];
NSUInteger labelLocation = 0;
#try {
for (NSNumber *tickLocation in ticksLocations) {
NSAttributedString* currentLabel = [labelsStrings objectAtIndex:labelLocation];
CPTTextLayer *txtLayer = [[CPTTextLayer alloc] initWithAttributedText:currentLabel];
CPTAxisLabel* newLabel = [[CPTAxisLabel alloc] initWithContentLayer:txtLayer];
newLabel.tickLocation = tickLocation;
newLabel.offset = 0.0f;
newLabel.alignment = CPTAlignmentLeft;
[customLabels addObject:newLabel];
labelLocation++;
}
}
#catch (NSException * e) {
DLog(#"An exception occurred while creating date labels for x-axis");
}
#finally {
y.axisLabels = [NSSet setWithArray:customLabels];
}
y.majorTickLocations = [NSSet setWithArray:ticksLocations];
You can achieve this by setting the frame of content layer of your CPTAxisLabel.
Try this:-
CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithText:#"Your Text Here" textStyle:#"Text Style"];
[label.contentLayer setFrame:CGRectMake(0, 0, 40, 40)]; //change this frame according to your requirement
Happy Coding..!!

Creating UIButton attributed title based on its original plain text state

I'm trying to create a subclass of UIButton that enforces text kerning. As such, when I build a button in Interface Builder, set the text colour (using plain text) I'm expecting to be able to take the existing text, colour, paragraph style, and font from the plain titleLabel instance and translate it into an attributed label with the same properties.
I written two categories that I thought might help:
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromExistingAttributedString:(NSAttributedString*)attributedString
{
NSDictionary *attributes = [attributedString attributesAtIndex:0 effectiveRange:NULL];
return [[NSMutableAttributedString alloc] initWithString:title attributes:attributes];
}
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromPlainTextLabel:(UILabel*)label
{
NSMutableAttributedString* mutableTitle = [[NSMutableAttributedString alloc] initWithString:title];
[mutableTitle addAttribute:NSFontAttributeName value:label.font range:[mutableTitle fullRange]];
[mutableTitle addAttribute:NSForegroundColorAttributeName value:label.textColor range:[mutableTitle fullRange]];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = label.textAlignment;
[mutableTitle addAttribute:NSParagraphStyleAttributeName value:style range:[mutableTitle fullRange]];
return mutableTitle;
}
And then in my UIButton subclass, I'm overriding these like so:
- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle = [title mutableCopy];
[mutableTitle addAttributes:#{NSKernAttributeName: #(kDefaultKerning)} range:[mutableTitle fullRange]];
[super setAttributedTitle:mutableTitle forState:state];
[self setNeedsDisplay];
}
- (void)setTitle:(NSString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle;
if ([self attributedTitleForState:state]) {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromExistingAttributedString:[self attributedTitleForState:state]];
} else {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromPlainTextLabel:self.titleLabel];
}
[self setAttributedTitle:mutableTitle forState:state];
}
But something's not working - particularly of note, [self attributedTitleForState:state] and [self titleForState:state] always seem to be nil. Seems to me like there's some disconnect in the way the properties are set in Interface Builder, in that while the text comes through OK, all of the styling is missing from it.
Got this to work finally. Turns out that there are several properties of a plain text UIButton that are in several places. Some have helper methods to retrieve directly from the UIButton, some do not.
Font: self.titleLabel.font (no helper method)
Colour: [self titleColorForState:state]
Text: [self titleForState:state]
Attributed Text: [self attributedTitleForState]
And a plain text UIButton does not have any text alignment by default, since that's controlled by content horizontal alignment.
As such, I changed up my helper methods (the first is identical, but I had to change the second):
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title fromExistingAttributedString:(NSAttributedString*)attributedString
{
NSDictionary *attributes = [attributedString attributesAtIndex:0 effectiveRange:NULL];
return [[NSMutableAttributedString alloc] initWithString:title attributes:attributes];
}
+ (NSMutableAttributedString*)attributedStringWithTitle:(NSString*)title font:(UIFont*)font color:(UIColor*)color
{
NSMutableAttributedString* mutableTitle = [[NSMutableAttributedString alloc] initWithString:title];
[mutableTitle addAttribute:NSFontAttributeName value:font range:[mutableTitle fullRange]];
[mutableTitle addAttribute:NSForegroundColorAttributeName value:color range:[mutableTitle fullRange]];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableTitle addAttribute:NSParagraphStyleAttributeName value:style range:[mutableTitle fullRange]];
return mutableTitle;
}
And I've implemented it like this:
- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle = [title mutableCopy];
[mutableTitle addAttributes:#{NSKernAttributeName: #(kDefaultKerning)} range:[mutableTitle fullRange]];
[super setAttributedTitle:mutableTitle forState:state];
[self setNeedsDisplay];
}
- (void)setTitle:(NSString *)title forState:(UIControlState)state
{
NSMutableAttributedString* mutableTitle;
if ([self attributedTitleForState:state]) {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title fromExistingAttributedString:[self attributedTitleForState:state]];
} else {
mutableTitle = [NSMutableAttributedString attributedStringWithTitle:title font:self.titleLabel.font color:[self titleColorForState:state]];
}
[self setAttributedTitle:mutableTitle forState:state];
}

Replace occurrences of NSString with NSTextAttachment

I have a UITextView and I want to replace some strings with specific images using NSTextAttachment. I'm able to insert an image attachment to the UITextView and I have a method that, from the text view's attributed text, returns a NSString replacing attachments with specific NSString:
-(NSString*)getStringFromAttributedString{
__block NSString *str = self.attributedText.string; // Trivial String representation
__block NSMutableString *string = [NSMutableString new]; // To store customized text
[self.attributedText enumerateAttributesInRange:NSMakeRange(0, self.attributedText.length) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop){
// enumerate through the attributes
NSString *v;
NSObject *x = [attributes valueForKey:#"NSAttachment"];
if(x){ // YES= This is an attachment
v = [Fuctions getObjectTag:x];
if(v == nil){
v=#"";
}
}else{
v = [str substringWithRange:range]; // NO=This is a text block.
}
// append value to string
[string appendString:v];
}];
return string;
}
Now I need a method that returns a NSAttributedString from NSString, replacing some occurrences of string with image attachment, something like this but I'm not able to make it work.
-(NSMutableAttributedString*)getAttributedStringFromString:(NSString*)string{
// create NSMutableAttributedString from NSString in input
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];
// create the attachment with image
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:#"myImageName.png"];
textAttachment.bounds = (CGRect) {0, -2, textAttachment.image.size};
// create NSMutableAttributedString with image attachment
NSMutableAttributedString *attrStringWithImage = [[NSAttributedString attributedStringWithAttachment:textAttachment] mutableCopy];
// here I'd like to replace all occurrences of a string (":D" this string for example) with my NSMutableAttributedString with image attachment).
return string;
}
How can I do?
Thanks, hope i explained myself.
This is my working solution:
-(NSMutableAttributedString*)getEmoticonStringFromString:(NSString*)string{
NSMutableAttributedString *final = [NSMutableAttributedString new]; //To store customized text
NSInteger len = [string length];
unichar buffer[len];
[string getCharacters:buffer range:NSMakeRange(0, len)];
for(int i = 1; i < len; i++) {
if(buffer[i-1] == ':' && buffer[i] == 'D'){
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [Functions scaleImage:[UIImage imageNamed:#"myImageName"] toSize:CGSizeMake(18.0f, 18.0f)];
textAttachment.bounds = (CGRect) {0, -2, textAttachment.image.size};
NSMutableAttributedString *attrStringWithImage = [[NSAttributedString attributedStringWithAttachment:textAttachment] mutableCopy];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init] ;
[paragraphStyle setAlignment:NSTextAlignmentLeft];
[paragraphStyle setLineSpacing:2.0f];
[attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [attrString length])];
[final insertAttributedString:attrStringWithImage atIndex:final.length];
i++;
}else{
[final insertAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%c", buffer[i-1]]] atIndex:final.length];
if(i == len-1){
[final insertAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%c", buffer[i]]] atIndex:final.length];
}
}
}
return final;
}

Resources