When I use a .txt file in my app (even when I fix it up as much as I can) The lines at the end get truncated. I want the text to reach the end of the line (all the way to the right side of the screen, just like the first line) . Should I use another type of file? If so, which? and if not, how do I get the words to reach the end of the line?
Thanks
p.s. cleantxt doesnt fix the problem
I want it to look like this
If I interpret your question correctly (via your example), this is not a question of truncation but a question of justification. You want "full justification" i.e. text stretching from left to right insets so the text appears as a rectangular block of text.
If yes and you are using a UITextView then:
For iOS 6 and earlier
UITextView *textView = #""; // replace with property representing the text from your .txt file
textView.textAlignment = NSTextAlignmentJustified;
For iOS 7+ (above is deprecated)
You unfortunately need to either use CoreText or a UIWebView.
See this post Justified Alignment in UITextView
Do you get the same result using this code?
NSString *fileName = #"impressum.txt";
NSURL *url = [[NSBundle mainBundle] URLForResource:[fileName stringByDeletingPathExtension] withExtension:[fileName pathExtension]];
NSError *error;
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithFileURL:url
options:#{NSDocumentTypeDocumentAttribute:NSPlainTextDocumentType}
documentAttributes:nil
error:&error];
CGRect paragraphRect = [attributedString boundingRectWithSize:CGSizeMake(_textLabel.frame.size.width), CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
CGRect frame = _textLabel.frame;
frame.size.height = paragraphRect.size.height;
_textLabel.frame = frame;
[_textLabel setAttributedText:attributedString];
Related
In my app, I'm trying to have the UILable time text aligned in a way like WhatsApp where if the last sentence of a UITextView's message text is too long the time would be pushed to the next line. So I'm actually trying to align the UILabel's time according to the UITextView's message box.
message box time layout image
This is a text message. 7:16PM
This is a longer
text message. 7:15PM
This is an even longer
text message till end.
7.15PM
One way I could think of was to use UITextView's class and grab the length of the last line and calculate the text width as compared with the UITextView's width to know if it exceeds but no luck. Is there any way?
This is the code that I wrote which solved my issue. Thanks to Greg for pointing me in the right direction.
+ (BOOL)didTextMoveToNewline:(NSString *)text previousSize:(CGSize)previousSize {
UIFont *messageBubbleFont = [UIFont systemFontOfSize:14.0f];
float maximumTextWidth = 188;
NSString *finalText = [[NSString alloc] init];
finalText = [NSString stringWithFormat:#"%# 10:00PM ", text];
CGRect stringRect = [finalText boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName : messageBubbleFont }
context:nil];
CGSize stringSize = CGRectIntegral(stringRect).size;
if (stringSize.height > previousSize.height)
return YES;
else
return NO;
}
The hack would be to automatically add a blank space the length of the time to the end of every message.
let userMessageLabel.text = userMessage + " "
Then let the time always overlap the last line. If the amount of spaces is correct, the text and time will never touch.
I have a UITextView with attributedText that has multiple new line breaks included in its attributed string.
NSMutableAttributedString *info = [[NSMutableAttributedString alloc] initWithString:#""];
NSAttributedString *title = [[NSAttributedString alloc] initWithString: [NSString stringWithFormat:#"%#\n", item.title] attributes:titleAttributes];
NSAttributedString *description = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n\n", description] attributes:descriptionAttributes]
[info appendAttributedString:bioItemTitle];
[info appendAttributedString:bioItemDescription];
textView.attributedText = info;
I've set the lineBreakMode of the textView's textContainer to NSLineBreakByTruncatingTail.
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
The textView's textContainer also has a maximum number of lines.
textView.textContainer.maximumNumberOfLines = 8;
The problem arises when the 8th line of the textView is a new line, and not a line of characters. The textContainer truncates by removing the new line and replacing it with the next line of written characters.
How do I preserve the new line while still setting a lineBreakMode?
See screenshots
Try using UILabel instead of UITextView. It seems to play nicer in regard to truncating a new line.
Random things to try on the off chance you haven't already that aren't even solutions but maybe workarounds, and may not work anyway, but whatever:
Change the maximumNumberOfLines to 0 and rely on frames/autolayout to size the text view and its container, perhaps in combination with setting the text container's heightTracksTextView property to true
Insert horizontal whitespace or invisibles at the beginning of lines that otherwise only contain a newline #hacky
I'm developing an app for a news website, i'm displaying the news articles using UITableView where each cell is an article title, when a user clicks on a cell (i.e an article), another view opens (using segue), now in this view i want to put the following:
The article's Image at the top.
The article's date under the image.
The article's description under the date. (Which could be very long)
The ability for the user to scroll the entire view. (not only the description)
NOTE: I have tried so many ways, i can't seem to know the proper way to implement this structure.
The modern solution is actually relatively simple: compose the whole thing as an attributed string and put it into a UITextView. The text view will automatically deal with the fact that the description may be very long, that all content should scroll together, etc.
E.g.
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:
[[NSTextAttachment new] setImage:[UIImage imageNamed:#"whatever.png"]]];
... and use the natural means for composition of attributed strings and for setting things like font and colour on your other bits of text. Then just textView.attributedString = compoundString;.
Elaborated example:
- (void)setStory:(Story *)story
{
NSAttributedString *image = [story imageString];
NSAttributedString *date = [story dateString];
NSAttributedString *body = [story bodyString];
NSMutableAttributedString *wholeStory = [NSMutableAttributedString new];
// TODO: can you be sure image, date and body are all non-nil?
NSArray *allComponents = #[image, date, body];
for(NSAttributedString *component in allComponents)
{
[wholeStory appendAttributedString:component];
if(component != [allComponents lastObject])
[[wholeStory mutableString] appendString:#"\n\n"];
}
self.textView.attributedString = wholeStory;
}
... elsewhere, in the Story object ...
- (UIImage *)image
{
// ...something...
}
- (NSString *)dateText
{
// ...something, probably using NSDateFormatter unless it's returned
// from a server or wherever already formatted...
}
- (NSString *)bodyText
{
// ... something ...
}
- (NSAttributedString *)imageString
{
return [NSAttributedString attributedStringWithAttachment:
[[NSTextAttachment new] setImage:[self image]]];
}
- (NSAttributedString *)dateString
{
return [[NSAttributedString alloc]
initWithString:[self dateText]
attributes:
#{
NSFontAttributeName: [UIFont preferredFontForTextStyle: UIFontTextStyleSubheadline],
... etc ...
}];
}
- (NSAttributedString *)bodyString
{
return [[NSAttributedString alloc]
initWithString:[self bodyText]
attributes:
#{
NSFontAttributeName: [UIFont preferredFontForTextStyle: UIFontTextStyleBody],
... etc ...
}];
}
Check out the NSAttributedString UIKit Additions documentation for lists of the various attributes you can set other than NSFontAttributeName. Note that I've gone with the iOS 7+ way of asking for fonts by purpose rather than a specific size or font. That means that users who have turned up their default font size will get larger text in your app.
I have created a pod to programmatically add constraints. There is a special category for scrollViews, because they are so complicated to use with auto layout.
Here is the link to the project
There is an example app you can take a look at, but the things you would have to do would be
initialize your views (the image, date label and description label).
add the scrollView
add the subviews of the scrollView
UIScrollView *scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
[scrollView addConstraintsToFillHorizontal];
[scrollView addConstraintsToFillVertical];
[scrollView addConstraintsToAlignVerticalAllViews:#[image, dateLabel, descriptionLabel]];
This should be pretty simple to implement, but if you need more help, just let me know and I could provide you with some more sample code.
Good luck with your project!
Another way would be to display each article as a HTML string in a UIWebView.
NSURL *url = [NSURL URLWithString:self.articleController.url];
NSString *html = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:NULL];
[self.articleView.webView loadHTMLString:html baseURL:baseURL];
CoreText isn't giving the correct height of the attributed string (its short by a line or more). I have seen a lot of posts on SO about this but unable to understand or find a solution. Can somebody explain how Core Text height calculation works? Here's an example code I wrote showing inaccurate height calculation.
Context
I have a collection view where the cell's height is determined by the content inside it.
I am displaying paragraphs of text in the cells. I would like to save some performance by doing the height calculation using core text. I have seen that with core text's height calculation I could save ~300ms.
Code
// Height Calculation
+ (CGFloat)getHeight
{
NSString *text = #"The Apple HIG recommends to use a common color for links and buttons and we did just that. By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";
NSAttributedString *attrStr = [self attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrStr));
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter,
CFRangeMake(0, attrStr.length),
NULL,
CGSizeMake(320, 9999),
NULL);
return suggestedSize.height;
}
// Load the same text when Cell is about to display
- (void)loadData
{
NSString *text = #"The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.The Apple HIG recommends to use a common color for links and buttons and we did just that.By using the same color throughout the app we trained the user to always associate blue to a link.";
NSAttributedString *attrStr = [[self class] attributedString:text withLinespacing:3 withLineBreakMode:NSLineBreakByWordWrapping];
// UILabel element
self.textLabel.attributedText = attrStr;
self.layer.borderColor = [UIColor blueColor].CGColor;
self.layer.borderWidth = 1.0f;
}
// Generate attributed string with leading, font and linebreak
+ (NSAttributedString *)attributedString:(NSString *)string
withLinespacing:(CGFloat)linespacing
withLineBreakMode:(NSLineBreakMode)lineBreakMode
{
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
NSInteger strLength = [string length];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = linespacing;
style.lineBreakMode = lineBreakMode;
[attrStr addAttributes:#{NSParagraphStyleAttributeName: style,
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:15]} range:NSMakeRange(0, strLength)];
return attrStr;
}
The above code uses core text to calculate the height and UILabel to display the text. The UILabel has 3 constraints to the cell {Top:17, Leading:13px, Trailing:13px}
CTFramesetterSuggestFrameSizeWithConstraints is known to be buggy, returning incorrect height values. The missing line bug you experience is very common, and there are no good solutions that I know of, only ugly workarounds which never give 100% accurate results.
For iOS7 and above, I recommend moving to TextKit. Somehow the calculations performed there internally do work correctly, while being based on Core Text also. Using NSLayoutManager's usedRectForTextContainer: returns a correct result.
You can see a more complete answer here. While not exactly 100% on topic, there is some discussion about the bugginess of Core Text calculations.
In my application I have a UILabel that holds an expanding number of entries so for example every time the user presses a button a "1" is appended on the end of the label.
However I would like to be able to detect when adding an additional entry to the label will cause it to overrun the size of it's container and become an ugly "111..." label with the desired behaviour being something along the lines of :
int maximumLengthBeforeOverrun = self.maximumLengthBeforeLabelOverrun;
if(label.text.length > maximumLengthBeforeOverrun) {
NSString * newLabel = [label.text substringTo:label.text.length - 1]
label.text = newLabel;
}
label.text = [label.text appendWithString:toAppend]
My question is there some method in the IOS SDK that will do this for me? Or should I be using a different approach to display the information?
You could use sizeWithFont
NSString *str = #"Test String";
CGSize size = [str sizeWithFont:label.font];
Then use size to compare it with label.frame.size
sizeWithFont was deprecated in iOS 7 so you need to use sizeWithAttributes
NSString *str = #"Test String";
NSDictionary *attributes = #{NSFontAttributeName: label.font};
CGSize size = [str sizeWithAttributes:attributes];