I have found that a call to boundingRectWithSize is extremely incorrect, missing an entire additional line, when called with NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody]. However, using the font [UIFont fontWithName:#"Helvetica Neue" size:17.f], it is just fine.
Here is test code showing the bug:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *measureText = #"I'm the king of Portmanteaus ... My students slow clap";
CGSize maxSize = CGSizeMake(226.f, CGFLOAT_MAX);
UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
CGRect stringRect = [measureText boundingRectWithSize:maxSize
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName : targetFont }
context:nil];
UILabel *drawLabel = [[UILabel alloc] initWithFrame:stringRect];
drawLabel.font = targetFont;
[self.view addSubview:drawLabel];
drawLabel.textColor = [UIColor blackColor];
drawLabel.text = measureText;
drawLabel.numberOfLines = 0;
}
And the output:
However, if I make targetFont = [UIFont fontWithName:#"Helvetica Neue" size:17.f];
It renders properly:
In the first case, the size is {226.20200000000008, 42.281000000000006}, but in the correct second case the size is {228.64999999999998, 60.366999999999997}. Therefore, this is not a rounding issue; this is missing an entire new line.
Is it wrong to pass [UIFont preferredFontForTextStyle:UIFontTextStyleBody] as an argument into boundingRectWithSize?
Edit
Per the comments in the answer below, I believe there is a bug with how iOS treats three periods in connection with the proceeding word. I have added this code:
measureText = [measureText stringByReplacingOccurrencesOfString:#" ..." withString:#"…"];
and it works properly.
A label adds a little margin round the outside of the text, and you have not allowed for that.
If, instead, you use a custom UIView exactly the size of the string rect, you will see that the text fits perfectly:
Here's the code I used. In the view controller:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *measureText = #"I'm the king of Portmanteaus ... My students slow clap";
CGSize maxSize = CGSizeMake(226.f, CGFLOAT_MAX);
UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
CGRect stringRect = [measureText boundingRectWithSize:maxSize
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName : targetFont }
context:nil];
stringRect.origin = CGPointMake(100,100);
StringDrawer* sd = [[StringDrawer alloc] initWithFrame:stringRect];
[self.view addSubview:sd];
[sd draw:[[NSAttributedString alloc] initWithString:measureText attributes:#{ NSFontAttributeName : targetFont }]];
}
And here is String Drawer (a UIView subclass):
#interface StringDrawer()
#property (nonatomic, copy) NSAttributedString* text;
#end
#implementation StringDrawer
- (instancetype) initWithFrame: (CGRect) frame {
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor yellowColor];
return self;
}
- (void) draw: (NSAttributedString*) text {
self.text = text;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
[self.text drawInRect:rect];
}
#end
Also, if your purpose is to make a label that contains the text by sizing its height, why not let Auto Layout do its thing? This next screen shot is a UILabel, with a width constraint 226, and no height constraint. I've assigned it your font and your text, in code. As you can see, it has sized itself to accommodate all the text:
That was achieved by this code, and no more was needed:
NSString *measureText = #"I'm the king of Portmanteaus ... My students slow clap";
UIFont *targetFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
self.lab.font = targetFont;
self.lab.text = measureText;
Related
Sorry, I have read a bunch of tutorials how to create a custom Callout for MapKit Annotation. It works with NSLog, but I cannot display the information in the Callouts.
I have two type of icons on the map. This is my viewForAnnotation method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if (! [annotation isKindOfClass:[IGAMapAnnotation class]]) {
return nil;
}
IGAMapAnnotation *myLocation = (IGAMapAnnotation *) annotation;
self.typeIsFix = [myLocation.navaidType isEqualToString:#"FIX"];
self.typeIsPort = [myLocation.navaidType isEqualToString:#"PORT"];
int planeImageViewTag = 42;
NSString *reuseId;
if (self.typeIsPort)
reuseId = #"IGAMapAnnotationPort";
else if (self.typeIsFix)
reuseId = #"IGAMapAnnotationFix";
else
reuseId = #"IGAMapAnnotationOther";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
annotationView.enabled = YES;
UIButton *annotationInfo = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = annotationInfo;
annotationView.canShowCallout = YES;
if (self.typeIsPort)
{
annotationView.image = [UIImage imageNamed:#"mapPORT.png"];
annotationView.centerOffset = CGPointMake(0, 0);
}
else if (self.typeIsFix)
{
annotationView.image = [UIImage imageNamed:#"mapFIX.png"];
annotationView.centerOffset = CGPointMake(0, 0);
}
else
return nil;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
Then I have a calloutAccessoryControlTapped method
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
IGAAnnotationInfoViewController *popOverCallout = [[IGAAnnotationInfoViewController alloc]init];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:popOverCallout];
popOver.popoverContentSize = CGSizeMake(300, 200);
[popOver presentPopoverFromRect:view.bounds
inView:view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
I have also created a UIViewController which I assigned to UIPopoverController.
Now, when I tap the button on my annotation I see a white space for text. Great. If I assign text to a label in UIViewController, it also works great (the following is my UIViewController.m):
- (void)viewDidLoad {
[super viewDidLoad];
txtCallout = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300, 200) ];
txtCallout.font = [UIFont fontWithName:#"Arial Rounded MT Bold" size:(14.0)];
txtCallout.numberOfLines = 0;
txtCallout.clipsToBounds = YES;
txtCallout.backgroundColor = [UIColor clearColor];
txtCallout.textColor = [UIColor blackColor];
txtCallout.textAlignment = NSTextAlignmentLeft;
txtCallout.text = #"text\ntext\ntext";
[self.view addSubview:txtCallout];
}
But how do I insert the text from my annotation method? Also the text must be different depending on the icon type, say #"PORT, PORT" or #"FIX,FIX". How do I do it?
EDIT:
I have managed to display callouts with the necessary information passed. My last problem is that sometimes my callout is 3 lines, sometimes -- as many as 15. How is it possible to make the callout adjust automatically to the number of lines in my string? Should I modify my popoverContentSize or my label size in the UIViewController?
Thank you so much!
I have figured out how to adjust the MK Annotation callout to a UILabel.
Implement the calloutAccessoryControlTapped method
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// OPTIONAL: Deselecting Annotation View when Callout is tapped
//[mapview deselectAnnotation:view.annotation animated:YES];
NSString *calloutDetails;
IGAMapAnnotation *annotationTapped = (IGAMapAnnotation *)view.annotation;
calloutDetails = [NSString stringWithFormat:#"YOUR TEXT:\nYOURTEXT\n"];
// Declare and initialize the UIViewController that has the label to contain the callout information
IGAAnnotationInfoViewController *detailViewController = [[IGAAnnotationInfoViewController alloc]initWithText:calloutDetails];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:detailViewController];
// Size of the UIPopoverController = size of the label + 40 pts
popOver.popoverContentSize = CGSizeMake(detailViewController.txtCallout.frame.size.width+40,detailViewController.txtCallout.frame.size.height+40);
// Show popover controller
[popOver presentPopoverFromRect:view.bounds
inView:view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
Now, IGAAnnotationInfoViewController.h
#interface IGAAnnotationInfoViewController : UIViewController {
CGRect calloutSize;
}
#property (strong,nonatomic) NSString *calloutInformation;
#property (strong,nonatomic) IGACalloutLabel *txtCallout;
-(IGAAnnotationInfoViewController*) initWithText : (NSString*) calloutText;
IGAAnnotationInfoViewController.m
#implementation IGAAnnotationInfoViewController
#synthesize calloutInformation,txtCallout;
-(IGAAnnotationInfoViewController*) initWithText : (NSString*) calloutText {
self = [super init];
if ( self ) {
calloutInformation = calloutText;
// Creating a label that will display the callout information (passed from IGAAcarasViewController - Map Annotation)
txtCallout = [[IGACalloutLabel alloc] initWithFrame:CGRectMake(20, 20, 0, 0)];
txtCallout.lineBreakMode = NSLineBreakByWordWrapping;
txtCallout.numberOfLines=0;
txtCallout.backgroundColor = [UIColor clearColor];
txtCallout.textColor=[UIColor blueColor];
txtCallout.text = calloutInformation;
[txtCallout drawTextInRect:CGRectMake(10,10,0,0)];
[txtCallout sizeToFit];
[self.view addSubview:txtCallout];
}
return self;
}
Finally, subclass the UILabel class:
implementation IGACalloutLabel
#synthesize topInset, leftInset, bottomInset, rightInset;
- (void)drawTextInRect:(CGRect)rect
{
UIEdgeInsets insets = {topInset,leftInset,bottomInset,rightInset};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
Regards,
I have a UICollectionView displaying a horizontal layout of images. Under each image, I'm displaying a label. In the storyboard, I've extended the height of the cell so that the label will be displayed underneath the cell. I've also set the height of the UIImageView in storyboard to be 20pts less than the cell. However, no matter what I do, the images take up the entire cell and the label is displayed on top of the image. Should I be setting the size of the imageview elsewhere? I found this thread which I thought would help since it's basically, identical, but the solution did not help me.
Here is my code...
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell"; // string value identifier for cell reuse
ImageViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.layer.borderWidth = 1.0;
cell.layer.borderColor = [UIColor grayColor].CGColor;
NSString *myPatternString = [self.imageNames objectAtIndex:indexPath.row];
cell.imageView.image = [self.imagesArray objectAtIndex:indexPath.row];
cell.imageView.contentMode = UIViewContentModeScaleAspectFill;
cell.imageView.clipsToBounds = YES;
CGSize labelSize = CGSizeMake(CellWidth, 20);
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(cell.bounds.size.width/2, cell.bounds.size.height-labelSize.height, cell.bounds.size.width, labelSize.height)];
testLabel.text = myPatternString;
[cell.contentView addSubview:testLabel];
return cell;
}
Don't know if this will work for you, but I do this will a subclass of UIView which contains two subviews -- a UIImageView for the image and a UILabel for the label. Here is the essence of that subclass below (don't be bothered by the rounding of bottoms; in some areas I need the bottoms to be rounded, in others I don't). I just add this subview to the contentView of the cell. Don't know if it helps you but here it is. BTW, imageView is a class variable here but you might define it as a property.
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl roundBottoms:(BOOL)roundBottoms;
{
self = [super initWithFrame:frame];
if (self)
{
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height-25.0f)];
if (roundBottoms)
{
imageView.layer.cornerRadius = 12;
imageView.layer.masksToBounds = YES;
}
else
{
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect: imageView.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii: (CGSize){12.0, 12.}].CGPath;
imageView.layer.mask = maskLayer;
}
[imageView setImage:img];
imageView.contentMode = UIViewContentModeScaleAspectFill; // default
[self addSubview:imageView];
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, frame.size.height - 25.0f, frame.size.width, 25.0f)];
label.text = lbl;
labelText = lbl;
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor blackColor];
label.adjustsFontSizeToFitWidth = YES;
label.minimumScaleFactor = 0.50f;
label.backgroundColor = [UIColor clearColor];
self.tag = imageItem; // 101
[self addSubview:label];
}
return self;
}
I have another version of it I use where I calculate the height of the label based on the overall frame height that is passed in. In this one, the cell sizes are static so the label heights can be as well.
I was trying to create an animated text label and suddenly this odd problem hit me. I am trying to write out a simple label using CATextLayer on a view. As you can see, I have tried to calculate the frame of the text by using the sizeWithAttributes: of the NSAttributedString class. This gives out a frame that doesn't perfectly fit the CATextLayer even though I am displaying the same attributed string using the layer.
Any insights into why this odd font-rendering problem is happening would be appreciated.
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize view;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
view.layer = [CALayer layer];
[view setWantsLayer:YES];
CATextLayer *textLayer = [CATextLayer layer];
textLayer.fontSize = 12;
textLayer.position = CGPointMake(100, 50);
[view.layer addSublayer:textLayer];
NSFont *font = [NSFont fontWithName:#"Helvetica-Bold" size:12];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
NSString *helloString = #"This is a long string";
NSAttributedString *hello = [[NSAttributedString alloc] initWithString:helloString attributes:attrs];
NSSize stringBounds = [helloString sizeWithAttributes:attrs];
stringBounds.width = ceilf(stringBounds.width);
stringBounds.height = ceilf(stringBounds.height);
textLayer.bounds = CGRectMake(0, 0, stringBounds.width, stringBounds.height);
textLayer.string = hello;
}
#end
CATextLayer renders the glyphs using CoreText and not Cocoa (educated guess based on experiments).
Check out this sample code to see how to calculate the size using CoreText and a CTTypesetter. This gave us much more accurate results:
https://github.com/rhult/height-for-width
I must be doing something wrong here:
My Cocoa app has a scrollview around a custom view which in turn has a textview. I only expect to see one "This is a " string but there the extra one up in the corner.
I have reduced the code to something very minimal and still do not understand what my error is, so here I am fishing for a clue.
The view controller for the custom view follows, but for simplicity here is a link to the project.
#import "TTTSimpleCtrlView.h"
#interface TTTSimpleCtrlView ()
#property (strong,nonatomic) NSTextView *tv1;
#property (strong,nonatomic) NSTextStorage *ts;
#end
#implementation TTTSimpleCtrlView
- (void) awakeFromNib {
NSFont *font = [NSFont fontWithName:#"Courier New Bold" size:20.0f];
NSMutableParagraphStyle *styleModel = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[styleModel setLineHeightMultiple:1.0];
// [styleModel setLineSpacing:fontRect.size.height * 2];
NSDictionary *textAttrs = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName,
[NSColor blackColor] ,NSForegroundColorAttributeName,
[NSColor whiteColor], NSBackgroundColorAttributeName,
styleModel, NSParagraphStyleAttributeName,
nil];
NSString *pilcrowStr = #"This is a test.";
NSAttributedString *s = [[NSAttributedString alloc] initWithString:pilcrowStr attributes:textAttrs];
NSRect rect = [s boundingRectWithSize:NSMakeSize(INFINITY,INFINITY)options:0];
NSLayoutManager *lm = [[NSLayoutManager alloc] init];
NSTextContainer *tc = [NSTextContainer new];
[tc setContainerSize:s.size];
[lm addTextContainer:tc];
_ts = [[NSTextStorage alloc] init];
[_ts setAttributedString:s];
[_ts addLayoutManager:lm];
[lm replaceTextStorage:_ts];
rect.origin.x = 10;
rect.origin.y = rect.size.height;
NSTextView *v = [[NSTextView alloc] initWithFrame:rect textContainer:tc];
[v setDrawsBackground:YES];
[self addSubview:v];
}
- (BOOL) isFlipped {
return YES;
}
- (void)drawRect:(NSRect)rect
{
NSLog(#"drawRect & %lu subviews",self.subviews.count);
for (NSTextView *v in self.subviews) {
if(CGRectIntersectsRect(v.frame, rect) || CGRectContainsRect(rect, v.frame)) {
[v drawRect:rect];
NSLog(#"frame = %#",NSStringFromRect(v.frame));
}
}
[super drawRect:rect];
}
You are calling:
[super drawRect:rect];
and you are drawing the text yourself in your draw function.
In effect you are drawing the text and cocoa is drawing the text for you as well.
So don't call super.
I'm trying to make a view looking almost like the info-view in appStore, with text on the first half of the view and images on the second half. I've tried to use a scrollview in another scrollview. The first view (containing a textview on the top half and a scrollview on the bottom half) scrolls nice, but the scrollview at the bottom (containing several imageviews) doesn't scroll.
Any ideas how to make the second scrollview to scroll?
scrollView is the view containing a textview and a scrollview.
imageScrollView is the view with the imageviews.
[scrollView setContentSize:CGSizeMake(320, 585)];
[imageScrollView setContentSize:CGSizeMake(1520, 400)];
I recently figured it out myself, but forgot to post the answer. The problem was that I set the sizes of the scrollviews in .xib to the same sizes as in the code. When I changed it to 320,416 and 320,400 I was able to scroll them with the setContentSize. In the imageScrollView I then added images along the x-axis (first image at 0, second at image width + a small space and so on). Hope this could help someone.
HERE IS CODE OF scroll-view display images from Documents Directory ByType .png Images who seved by user from Iphone Photo Gallery
- (void)viewDidLoad {
int scrollviewwidh = 120;
self.view.backgroundColor = [UIColor darkTextColor];
hiImage = [[UIImageView alloc]initWithFrame:CGRectMake(10, 10, 300, 240)];
hiImage.backgroundColor = [UIColor blackColor];
hiImage.userInteractionEnabled = YES;
[hiImage.layer setBorderColor:[UIColor blackColor].CGColor];
[hiImage.layer setBorderWidth:2.01f];
[self.view addSubview:hiImage];
[hiImage release];
scroll=[[UIScrollView alloc]initWithFrame:CGRectMake(0, 260, 320, 110)];
[self.view addSubview:scroll];
scroll.showsHorizontalScrollIndicator = NO;
scroll.pagingEnabled=YES;
scroll.delegate=self;
scroll.contentSize=CGSizeMake(scrollviewwidh,80);
scroll.showsVerticalScrollIndicator =YES;
scroll.backgroundColor = [UIColor darkTextColor];
}
-(void) viewWillAppear:(BOOL)animated{
self.fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
self.documentsDir = [paths objectAtIndex:0];
NSLog(#"the ----------- %#",documentsDir);
for (UIButton * img in self.scroll.subviews) {
[img removeFromSuperview];
img = nil;
}
int xoffcet = 0;
int col = 0;
int scrollviewwidh = 120;
for (NSString* fileName in [self.fileManager contentsOfDirectoryAtPath: self.documentsDir error:nil]){
if ( [fileName rangeOfString:#".png"].location != NSNotFound ) {
NSLog(#"add %#", fileName);
UIImage* img = [UIImage imageWithContentsOfFile:
[self.documentsDir stringByAppendingPathComponent:fileName]
];
imgView = [[UIButton alloc] init];
[imgView setImage:img forState:UIControlStateNormal];
[imgView addTarget:self action:#selector(imageViewClicked:) forControlEvents:UIControlEventTouchUpInside];
[imgView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
[imgView.layer setBorderWidth:1.0f];
imgView.bounds = CGRectMake(10,10, 50, 50);
imgView.frame = CGRectMake(5+xoffcet, 0, 160, 110);
scroll.contentSize =CGSizeMake(scrollviewwidh+xoffcet,110);
[scroll addSubview:imgView];
[imgView release];
xoffcet +=170;
}
if (col++>1) {
//row++;
col = 0;
}
}
self.title = #"saved Photo";
// [scroll release];
[super viewDidLoad];
// scroll.pagingEnabled = YES;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:
UIBarButtonSystemItemCancel
target:self
action:#selector(photogallary:)];
}
-(IBAction)imageViewClicked:(UIButton *)sender{
for (NSString* fileName in [self.fileManager contentsOfDirectoryAtPath: self.documentsDir error:nil]){
if ( [fileName rangeOfString:#".png"].location != NSNotFound ) {
NSLog(#"add %#", fileName);
UIImage* img = [UIImage imageWithContentsOfFile:
[self.documentsDir stringByAppendingPathComponent:fileName]];
hiImage.image = img;
}
}
}
All The Very Best i Hope its useful Thank You If Thats code Help please give Your Feedback