I have a UITextField with the text right-aligned.
I wanted to change the color of the placeholder text, so I use - (void)drawPlaceholderInRect:(CGRect)rect method. It works great BUT the placeholder text is left-aligned now (the text remains right-aligned). I guess I can add some code to override it but I didn't find which one. Thanks in advance !
- (void)drawPlaceholderInRect:(CGRect)rect
{
[[UIColor redColor] setFill];
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Medium" size:18];
[[self placeholder] drawInRect:rect withFont:font];
}
Here is the code snippet based on Michael solution. You should create subclass of text field and add the below method. Below method basically changes x-position and width of place holder bounds.
- (CGRect)placeholderRectForBounds:(CGRect)bounds{
CGRect newbounds = bounds;
CGSize size = [[self placeholder] sizeWithAttributes:
#{NSFontAttributeName: self.font}];
int width = bounds.size.width - size.width;
newbounds.origin.x = width ;
newbounds.size.width = size.width;
return newbounds;
}
You've discovered that "drawInRect" is automagically drawing from the left edge going right.
What you need to do is adjust the "rect" passed to "drawInRect" to have left edge that allows the right edge of the drawn text to touch the right edge of your UITextField rect.
To do this, I'd recommend using this method: NSString's [self placeholder] sizeWithFont: constrainedToSize:] (assuming [self placeholder] is a NSString) which will give you the true width of the string. Then subtract the width from the right edge of the text field box and you have the left edge where you need to start your drawing from.
I enhanced #Saikiran's snippet a little, this works for me:
- (CGRect)placeholderRectForBounds:(CGRect)bounds
{
return self.editing ? ({CGRect bounds_ = [super placeholderRectForBounds:bounds];
bounds_.origin.x = bounds_.size.width
- ceilf(self.attributedPlaceholder.size.width)
+ self.inset.x;
bounds_.origin.y = .5f * (.5f * bounds_.size.height
- ceilf(self.attributedPlaceholder.size.height));
bounds_.size.width = ceilf(self.attributedPlaceholder.size.width);
bounds_.size.height = ceilf(self.attributedPlaceholder.size.height);
bounds_;
}) : [super placeholderRectForBounds:bounds];
}
Related
I am creating (or attempting to) a custom UISlider look, still horizontal but much taller. I have two problems.
1.This is the code I'm using to get the images onto the slider.
UIImage *minImage = [UIImage imageNamed:#"sliderMin.png"];
UIImage *maxImage = [UIImage imageNamed:#"sliderMax.png"];
UIImage *thumbImage = [UIImage imageNamed:#"sliderThumb.png"];
minImage = [minImage stretchableImageWithLeftCapWidth:33.0 topCapHeight:0.0];
maxImage = [maxImage stretchableImageWithLeftCapWidth:33.0 topCapHeight:0.0];
// thumbImage = [minImage stretchableImageWithLeftCapWidth:10.0 topCapHeight:0.0];
[_contrastSlider setMinimumTrackImage:minImage forState:UIControlStateNormal];
[_contrastSlider setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[_contrastSlider setThumbImage:thumbImage forState:UIControlStateNormal];
minImage = nil;
maxImage = nil;
thumbImage = nil;
[_contrastSlider setMinimumValue:0.0];
[_contrastSlider setMaximumValue:100.00];
[_contrastSlider setValue:25.0];
I have three images, min, max and thumb. These are the images and how they look on the device, which isn't correct.
I can't post images yet, so here is a link to MYSlider.jpeg which shows the components.
The slider should be cream to the right of the thumb, pink to the left. But this isn't happening.
That's problem 1. Problem 2 is the thumb size default is too small. I'm using this :UISlider Height Hack to try to increase the height of the thumb, but sadly it's making it wider not taller. So I am asking for help to make the area taller, not wider, and wondering why my pink image is being ignored?
Thank you for any help.
If it helps, here is my UISlider subclass .m:
-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent*)event {
CGRect bounds = self.bounds;
bounds = CGRectInset(bounds, -10, -8);
return CGRectContainsPoint(bounds, point);
}
-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGRect bounds = self.bounds;
float thumbPercent = (self.value - self.minimumValue) / (self.maximumValue - self.minimumValue);
float thumbPos = THUMB_SIZE + (thumbPercent * (bounds.size.width - (2 * THUMB_SIZE)));
CGPoint touchPoint = [touch locationInView:self];
return (touchPoint.x >= (thumbPos - EFFECTIVE_THUMB_SIZE) && touchPoint.x <= (thumbPos + EFFECTIVE_THUMB_SIZE));
}
Thank you.
It turns out setting my slider values in the views did load method was causing problems. I set them in Interface Builder and that was fixed. Once set in IB I can tweak them in code if needs be with no problems.
As for my other problem, I'm wondering if this is because I'm not testing on an actual device, so I'm going to pause that question until I get it on the device in a few weeks.
I have a UILabel that is a fixed size. Unfortunately on rare occasions, the text I need to fit into it doesn't fit! I have tried reducing the font size, but it needs to reduce so much that it looks terrible.
Is it possible to change the font width somehow? UIFont does not seem to have any properties to allow me to do this? Do I need to use a UIWebView and use CSS? I don't know much CSS, so any help is much appreciated if this is the best way to solve this.
Alternatively, any other ways to solve this?
Thanks Craig
The simplest way to shrink just the width of the text is to apply a transform to the label's layer:
label.layer.transform = CATransform3DMakeScale(desiredWidth/textWidth, 1.0, 1.0);
Do you mean you want to squeeze it horizontally while keeping the height? This is achievable, up to about 60% of the regular width. Beyond that it looks terrible.
Here is the drawRect for a UILabel subclass which squeezes independently on either axis if necessary.
// This drawRect for a UILabel subclass reproduces most common UILabel formatting, but does not do truncation, line breaks, or scaling to fit.
// Instead, it identifies cases where the label text is too large on either axis, and shrinks along that axis.
// For small adjustments, this can keep text readable. In extreme cases, it will create an ugly opaque block.
- (void) drawRect:(CGRect)rect;
{
CGRect bounds = [self bounds];
NSString *text = [self text];
UIFont *font = [self font];
// Find the space needed for all the text.
CGSize textSize = [text sizeWithFont:font];
// topLeft is the point from which the text will be drawn. It may have to move due to compensate for scaling, or due to the chosen alignment.
CGPoint topLeft = bounds.origin;
// Default to no scaling.
CGFloat scaleX = 1.0;
CGFloat scaleY = 1.0;
// If the text is too wide for its space, reduce it.
// Remove the second half of this AND statement to have text scale WIDER than normal to fill the space. Useless in most cases, but can be amusing.
if ((textSize.width>0) && (bounds.size.width/textSize.width<1))
{
scaleX = bounds.size.width/textSize.width;
topLeft.x /= scaleX;
}
else
{
// Alignment only matters if the label text doesn't already fill the space available.
switch ([self textAlignment])
{
case UITextAlignmentLeft :
{
topLeft.x = bounds.origin.x;
}
break;
case UITextAlignmentCenter :
{
topLeft.x = bounds.origin.x+(bounds.size.width-textSize.width)/2;
}
break;
case UITextAlignmentRight :
{
topLeft.x = bounds.origin.x+bounds.size.width-textSize.width;
}
break;
}
}
// Also adjust the height if necessary.
if ((textSize.height>0) && (bounds.size.height/textSize.height<1))
{
scaleY = bounds.size.height/textSize.height;
topLeft.y /= scaleY;
}
else
{
// If the label does not fill the height, center it vertically.
// A common feature request is for labels that do top or bottom alignment. If this is needed, add a property for vertical alignment, and obey it here.
topLeft.y = bounds.origin.y+(bounds.size.height-textSize.height)/2;
}
// Having calculated the transformations needed, apply them here.
// All drawing that follows will be scaled.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleX, scaleY);
// Begin drawing.
// UILabels may have a shadow.
if ([self shadowColor])
{
[[self shadowColor] set];
CGPoint shadowTopLeft = CGPointMake(topLeft.x+[self shadowOffset].width/scaleX, topLeft.y+[self shadowOffset].height/scaleY);
[text drawAtPoint:shadowTopLeft withFont:font];
}
// The text color may change with highlighting.
UIColor *currentTextColor;
if ((![self isHighlighted]) || (![self highlightedTextColor]))
currentTextColor = [self textColor];
else
currentTextColor = [self highlightedTextColor];
// Finally, draw the regular text.
if (currentTextColor)
{
[currentTextColor set];
[text drawAtPoint:topLeft withFont:font];
}
}
You can set the minimum font size of a UILabel to a smaller value, and check Autoshrink to let it automatically shrink. This parameter is available in Interface Builder.
The internal implementation will reduce kerning, which is the width of space between characters. It cannot actually reduce width though.
This is your better bet. If you are still unsatisfied with results. You may have to change your design.
I would like to create a view like the notes app on iPhone and therefor need the view to have ruled lines as per the notes app, I have done this in windows where you need to get the font metrics and then draw the lines onto the device context, has anyone done this in the UITextView if so some help would be appriciated
Subclass UITextView. Override -drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetLineWidth(context, self.lineWidth);
CGFloat strokeOffset = (self.lineWidth / 2);
CGFloat rowHeight = self.font.lineHeight;
if (rowHeight > 0) {
CGRect rowRect = CGRectMake(self.contentOffset.x, - self.bounds.size.height, self.contentSize.width, rowHeight);
while (rowRect.origin.y < (self.bounds.size.height + self.contentSize.height)) {
CGContextMoveToPoint(context, rowRect.origin.x + strokeOffset, rowRect.origin.y + strokeOffset);
CGContextAddLineToPoint(context, rowRect.origin.x + rowRect.size.width + strokeOffset, rowRect.origin.y + strokeOffset);
CGContextDrawPath(context, kCGPathStroke);
rowRect.origin.y += rowHeight;
}
}
}
When you init the text view, be sure to set the contentMode to UIViewContentModeRedraw. Otherwise the lines won't scroll with the text.
self.contentMode = UIViewContentModeRedraw;
This isn't perfect. Ideally you should just draw into the rect that's passed. But I was lazy and this worked for my needs.
I think this works OK but I feel it has been hacked and I do not fully undestand the mechanism of the UITextView class;
first you must add the following to your delegate to force a redraw on scrolling
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// NSLog(#"scrollViewDidScroll The scroll offset is ---%f",scrollView.contentOffset.y);
[noteText setNeedsDisplay];
}
then implement drawRect in the subclass as so
- (void)drawRect:(CGRect)rect {
// Drawing code
// Get the graphics context
CGContextRef ctx = UIGraphicsGetCurrentContext();
[super drawRect:rect];
// Get the height of a single text line
NSString *alpha = #"ABCD";
CGSize textSize = [alpha sizeWithFont:self.font constrainedToSize:self.contentSize lineBreakMode:UILineBreakModeWordWrap];
NSUInteger height = textSize.height;
// Get the height of the view or contents of the view whichever is bigger
textSize = [self.text sizeWithFont:self.font constrainedToSize:self.contentSize lineBreakMode:UILineBreakModeWordWrap];
NSUInteger contentHeight = (rect.size.height > textSize.height) ? (NSUInteger)rect.size.height : textSize.height;
NSUInteger offset = 6 + height; // MAGIC Number 6 to offset from 0 to get first line OK ???
contentHeight += offset;
// Draw ruled lines
CGContextSetRGBStrokeColor(ctx, .8, .8, .8, 1);
for(int i=offset;i < contentHeight;i+=height) {
CGPoint lpoints[2] = { CGPointMake(0, i), CGPointMake(rect.size.width, i) };
CGContextStrokeLineSegments(ctx, lpoints, 2);
}
}
Still worry about this Magic Number 6
Bob
You can try setting the backgroundColor of you textView using an image with ruled lines
textView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"RuledLinesPage.png"]];
Color with pattern image creates a tiled image if the area to be filled with the color is larger than the image. So you will have to make sure that the image size is correct size/tileable (I don't think 'tileable' is a real word but i hope you get what i mean). Also you will have to create the image with ruled lines to best match you textView's font.
Good Luck.
#lukya,
Your solution is bit messy as when we scroll the UITextView the text only scrolls leaving the lines (coming from the image) in its place.
A better solution would be to add subview to your text view where you have drawn the lines. You need to add an observer to the text view in order to track its change in content size as the text increase/decrease.
Is it possible to add a shadow to the text in a UITextField?
As of 3.2, you can use the CALayer shadow properties.
_textField.layer.shadowOpacity = 1.0;
_textField.layer.shadowRadius = 0.0;
_textField.layer.shadowColor = [UIColor blackColor].CGColor;
_textField.layer.shadowOffset = CGSizeMake(0.0, -1.0);
I have a slightly different problem - I want a blurred shadow on a UILabel. Luckily, the solution to this turned out to be number (2) from Tyler
Here's my code :
- (void) drawTextInRect:(CGRect)rect {
CGSize myShadowOffset = CGSizeMake(4, -4);
CGFloat myColorValues[] = {0, 0, 0, .8};
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(myContext);
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
CGContextSetShadowWithColor (myContext, myShadowOffset, 5, myColor);
[super drawTextInRect:rect];
CGColorRelease(myColor);
CGColorSpaceRelease(myColorSpace);
CGContextRestoreGState(myContext);
}
This is in a class that extends from UILabel and draws the text with a shadow down and to the right 4px, the shadow is grey at 80% opacity and is sightly blurred.
I think that Tyler's solution number 2 is a little better for performance than Tyler's number 1 - you're only dealing with one UILabel in the view and, assuming that you're not redrawing every frame, it's not a hit in rendering performance over a normal UILabel.
PS This code borrowed heavily from the Quartz 2D documentation
I don't think you get built-in support for text shadows here, the way you do with UILabel.
Two ideas:
(1) [Moderately tricky to code.] Add a second UITextField behind the original, at a very small offset (maybe by (0.2,0.8)? ). You can listen to every text change key-by-key by implementing the textField:shouldChangeCharactersInRange:replacementString: method in the UITextFieldDelegate protocol. Using that, you can update the lower text simultaneously. You could also make the lower text (the shadow text) gray, and even slightly blurry using the fact that fractionally-offset text rects appear blurry. Added: Oh yea, don't forget to set the top text field's background color to [UIColor clearColor] if you go with this idea.
(2) [Even more fun to code.] Subclass UITextField and override the drawRect: method. I haven't done this before, so I'll mention up front that this depends on this being the designated drawing method, and it may turn out that you have to override another drawing function, such as drawTextInRect:, which is specific to UITextField. Now set up the drawing context to draw shadows via the CGContextSetShadow functions, and call [super drawRect:rect];. Hopefully that works -- in case the original UITextField code clears the drawing context's shadow parameters, that idea is hosed, and you'll have to write the whole drawing code yourself, which I anti-recommend because of all the extras that come with UITextFields like copy-and-paste and kanji input in Japanese.
Although the method of applying the shadow directly to the UITextView will work, it's the wrong way to do this. By adding the shadow directly with a clear background color, all subviews will get the shadow, even the cursor.
The approach that should be used is with NSAttributedString.
NSMutableAttributedString* attString = [[NSMutableAttributedString alloc] initWithString:textView.text];
NSRange range = NSMakeRange(0, [attString length]);
[attString addAttribute:NSFontAttributeName value:textView.font range:range];
[attString addAttribute:NSForegroundColorAttributeName value:textView.textColor range:range];
NSShadow* shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor whiteColor];
shadow.shadowOffset = CGSizeMake(0.0f, 1.0f);
[attString addAttribute:NSShadowAttributeName value:shadow range:range];
textView.attributedText = attString;
However textView.attributedText is for iOS6. If you must support lower versions, you could use the following approach. (Dont forget to add #import <QuartzCore/QuartzCore.h>)
CALayer *textLayer = (CALayer *)[textView.layer.sublayers objectAtIndex:0];
textLayer.shadowColor = [UIColor whiteColor].CGColor;
textLayer.shadowOffset = CGSizeMake(0.0f, 1.0f);
textLayer.shadowOpacity = 1.0f;
textLayer.shadowRadius = 0.0f;
I want to draw some text in a view, rotated 90°. I'm pretty new to iPhone development, and poking around the web reveals a number of different solutions. I've tried a few and usually end up with my text getting clipped.
What's going on here? I am drawing in a fairly small space (a table view cell), but there has to be a "right" way to do this… right?
Edit: Here are a couple of examples. I'm trying to display the text "12345" along the black bar at the left.
First attempt, from RJShearman on the Apple Discussions
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSelectFont (context, "Helvetica-Bold", 16.0, kCGEncodingMacRoman);
CGContextSetTextDrawingMode (context, kCGTextFill);
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextSetTextMatrix (context, CGAffineTransformRotate(CGAffineTransformScale(CGAffineTransformIdentity, 1.f, -1.f ), M_PI/2));
CGContextShowTextAtPoint (context, 21.0, 55.0, [_cell.number cStringUsingEncoding:NSUTF8StringEncoding], [_cell.number length]);
CGContextRestoreGState(context);
(source: deeptechinc.com)
Second attempt, from zgombosi on iPhone Dev SDK. Identical results (the font was slightly smaller here, so there's less clipping).
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint point = CGPointMake(6.0, 50.0);
CGContextSaveGState(context);
CGContextTranslateCTM(context, point.x, point.y);
CGAffineTransform textTransform = CGAffineTransformMakeRotation(-1.57);
CGContextConcatCTM(context, textTransform);
CGContextTranslateCTM(context, -point.x, -point.y);
[[UIColor redColor] set];
[_cell.number drawAtPoint:point withFont:[UIFont fontWithName:#"Helvetica-Bold" size:14.0]];
CGContextRestoreGState(context);
Attempt two. There is almost identical clipping http://dev.deeptechinc.com/sidney/share/iphonerotation/attempt2.png
It turns out that the my table cell was always initialized 44px high regardless of the row height, so all of my drawing was getting clipped 44px from the top of the cell.
To draw larger cells it was necessary to set the content view's autoresizingMask with
cellContentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
or
cellContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
…and drawRect is called with the correct size. In a way, this makes sense, because UITableViewCell's initWithStyle:reuseIdentifier: makes no mention of the size of the cell, and only the table view actually knows how big each row is going to be, based on its own size and its delegate's response to tableView:heightForRowAtIndexPath:.
I read the Quartz 2D Programming Guide until the drawing model and functions started to make sense, and the code to draw my rotated text became simple and obvious:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextRotateCTM(context, -(M_PI/2));
[_cell.number drawAtPoint:CGPointMake(-57.0, 5.5) withFont:[UIFont fontWithName:#"Helvetica-Bold" size:16.0]];
CGContextRestoreGState(context);
Thanks for the tips, it looks like I'm all set.
Use :-
label.transform = CGAffineTransformMakeRotation(- 90.0f * M_PI / 180.0f);
where label is the object of UILabel.
Here's a tip. I presume you're doing this drawing in drawRect. Why don't you draw a frame around drawRect to see how big the rect is and if that is why you get clipping.
An alternative is to put your text in a UILabel, and then rotate that 90 degrees when you make your cells in cellForRowAtIndexPath.
You know about the UITableViewDelegate method heightForRowAtIndexPath right?
Here's a simple tutorial on various graphics level methods. Presuming you know how big your text is you should be able to size your table view row size appropriately.
Also, I'd check to make sure that the bounds after any transform actually meet your expectations. (Either use a debugger or log statement to verify this).
to what #Sidnicious said, and what i collected through out stack overflow, i want to give a usage example - appended my code to completely draw a ruler to the left screen side, with numbers rotated:
RulerView : UIView
// simple testing for iPhones (check for device descriptions to get all iPhones + iPads)
- (float)getPPI
{
switch ((int)[UIScreen mainScreen].bounds.size.height) {
case 568: // iPhone 5*
case 667: // iPhone 6
return 163.0;
break;
case 736: // iPhone 6+
return 154.0;
break;
default:
return -1.0;
break;
}
}
- (void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setFill];
float ppi = [self getPPI];
if (ppi == -1.0) // unable to draw, maybe an ipad.
return;
float linesDist = ppi/25.4; // ppi/mm per inch (regular size iPad would be 132.0, iPhone6+ 154.0)
float linesWidthShort = 15.0;
float linesWidthMid = 20.0;
float linesWidthLong = 25.0;
for (float i = 0, c = 0; i <= self.bounds.size.height; i = i + linesDist, c = c +1.0)
{
bool isMid = (int)c % 5 == 0;
bool isLong = (int)c % 10 == 0;
float linesWidth = isLong ? linesWidthLong : isMid ? linesWidthMid : linesWidthShort;
UIRectFillUsingBlendMode( (CGRect){0, i, linesWidth, .5} , kCGBlendModeNormal);
/* FONT: Numbers without rotation (yes, is short)
if (isLong && i > 0 && (int)c % 10 == 0)
[[NSString stringWithFormat:#"%d", (int)(c/10)] drawAtPoint:(CGPoint){linesWidthLong +2, i -5} withAttributes:#{
NSFontAttributeName: [UIFont systemFontOfSize:9],
NSBaselineOffsetAttributeName: [NSNumber numberWithFloat:1.0]
}];
*/
// FONT: Numbers with rotation (yes, requires more effort)
if (isLong && i > 0 && (int)c % 10 == 0)
{
NSString *str = [NSString stringWithFormat:#"%d", (int)(c/10)];
NSDictionary *attrs = #{
NSFontAttributeName: [UIFont systemFontOfSize:9],
NSBaselineOffsetAttributeName: [NSNumber numberWithFloat:0.0]
};
CGSize textSize = [str sizeWithAttributes:attrs];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextRotateCTM(context, +(M_PI/2));
[str drawAtPoint:(CGPoint){i - (textSize.width/2), -(linesWidthLong + textSize.height +2)} withAttributes:attrs];
CGContextRestoreGState(context);
}
}
}
After I discovered that I needed to add the following to the top of my file I liked Matt's approach. Very simple.
#define degreesToRadian(x) (M_PI * (x) / 180.0)
mahboudz's suggestion will probably be your path of least resistance. You can rotate the UILabel 90deg with this: [label setTransform:CGAffineTransformMakeRotation(DegreesToRadians(-90.0f))]; You'll just have to calculate your cell height based upon the label width. -Matt – Matt Long Nov 10 at 0:09