Changing NSAttributedString text value for a UIControl - ios

Currently all my buttons and textfields have attributedText values defined to be attributed strings.
Consider the case with a simple UILabel. Whenever I have to change the text for this UILabel (based on some user action), I have to redefine the attributes on the NSAttributedString. One way is to simply create a subroutine that generates these attributes whenever I require them but that's a concern given there could be a number of different labels (or attributed strings) that would require such convenience methods.
Another could be simply changing the text field and having observers add those attributes but that's the same amount of work and now probably more complicated.
Is there a simple way of achieving the above without redefining attributes?

Exploring #Harry's ideas, here are a few ideas :
Category on NSAttributedString, category on UILabel, or category on NSDictionary, and maybe a mix of them, according to which one best suits you and your project.
Using a category on NSAttributedString in priority before UILabel could be more interesting in case you wanted to use the custom NSAttributedString for others kind of object (like a UITextView).
A good start:
typedef enum : NSUInteger {
AttributeStyle1,
AttributeStyle2,
} AttributeStyles;
A possible category method on NSDictionary:
-(NSDictionary *)attributesForStyle:(AttributeStyles)style
{
NSDictionary *attributes;
switch(style)
{
case AttributeStyle1:
attributes = #{}//Set it
break;
case AttributeStyle2:
attributes = #{}//Set it
break;
default:
attributes = #{}//Set it
break;
}
return attributes;
}
Category possible on UILabel:
-(void)setString:(NSString *)string withAttributes:(NSDictionary *)attributes
{
[self setAttributedText:[[NSAttributedString alloc] initWithString:string attributes:attributes];
}
Category possible on NSAttributedString:
-(NSAttributedString *)initWithString:(NSString *)string withStyle:(AttributedStyles)style
{
//Here, a mix is possible using the first method, or doing here the switch case
//Ex: return [[NSAttributedString alloc] initWithString:string attributes:[NSDictionary attributesForStyle:style];
//And to use like this: [yourLabel setAttributedText:[[NSAttributedString alloc] initWithString:string withStyle:AttributeStyle1];
}

Related

AutoLayout row height miscalculating for NSAttributedString

My app pulls HTML from an API, converts it into a NSAttributedString (in order to allow for tappable links) and writes it to a row in an AutoLayout table. Trouble is, any time I invoke this type of cell, the height is miscalculated and the content is cut off. I have tried different implementations of row height calculations, none of which work correctly.
How can I accurately, and dynamically, calculate the height of one of these rows, while still maintaining the ability to tap HTML links?
Example of undesired behavior
My code is below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch(indexPath.section) {
...
case kContent:
{
FlexibleTextViewTableViewCell* cell = (FlexibleTextViewTableViewCell*)[TableFactory getCellForIdentifier:#"content" cellClass:FlexibleTextViewTableViewCell.class forTable:tableView withStyle:UITableViewCellStyleDefault];
[self configureContentCellForIndexPath:cell atIndexPath:indexPath];
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.desc.font = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
return cell;
}
...
default:
return nil;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UIFont *contentFont = [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f];
switch(indexPath.section) {
...
case kContent:
return [self textViewHeightForAttributedText:[self convertHTMLtoAttributedString:myHTMLString] andFont:contentFont andWidth:self.tappableCell.width];
break;
...
default:
return 0.0f;
}
}
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
return [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)}
documentAttributes:nil
error:nil];
}
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
NSMutableAttributedString *mutableText = [[NSMutableAttributedString alloc] initWithAttributedString:text];
[mutableText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, text.length)];
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:mutableText];
CGSize size = [self text:mutableText.string sizeWithFont:font constrainedToSize:CGSizeMake(width,FLT_MAX)];
CGSize sizeThatFits = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return sizeThatFits.height;
}
In the app I'm working on, the app pulls terrible HTML strings from a lousy API written by other people and converts HTML strings to NSAttributedString objects. I have no choice but to use this lousy API. Very sad. Anyone who has to parse terrible HTML string knows my pain. I use Text Kit. Here is how:
parse html string to get DOM object. I use libxml with a light wrapper, hpple. This combination is super fast and easy to use. Strongly recommended.
traverse the DOM object recursively to construct NSAttributedString object, use custom attribute to mark links, use NSTextAttachment to mark images. I call it rich text.
create or reuse primary Text Kit objects. i.e. NSLayoutManager, NSTextStorage, NSTextContainer. Hook them up after allocation.
layout process
Pass the rich text constructed in step 2 to the NSTextStorage object in step 3. with [NSTextStorage setAttributedString:]
use method [NSLayoutManager ensureLayoutForTextContainer:] to force layout to happen
calculate the frame needed to draw the rich text with method [NSLayoutManager usedRectForTextContainer:]. Add padding or margin if needed.
rendering process
return the height calculated in step 5 in [tableView: heightForRowAtIndexPath:]
draw the rich text in step 2 with [NSLayoutManager drawGlyphsForGlyphRange:atPoint:]. I use off-screen drawing technique here so the result is an UIImage object.
use an UIImageView to render the final result image. Or pass the result image object to the contents property of layer property of contentView property of UITableViewCell object in [tableView:cellForRowAtIndexPath:].
event handling
capture touch event. I use a tap gesture recognizer attached with the table view.
get the location of touch event. Use this location to check if user tapped a link or an image with [NSLayoutManager glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph] and [NSAttributedString attribute:atIndex:effectiveRange:].
Event handling code snippet:
CGPoint location = [tap locationInView:self.tableView];
// tap is a tap gesture recognizer
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if (!indexPath) {
return;
}
CustomDataModel *post = [self getPostWithIndexPath:indexPath];
// CustomDataModel is a subclass of NSObject class.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
location = [tap locationInView:cell.contentView];
// the rich text is drawn into a bitmap context and rendered with
// cell.contentView.layer.contents
// The `Text Kit` objects can be accessed with the model object.
NSUInteger index = [post.layoutManager
glyphIndexForPoint:location
inTextContainer:post.textContainer
fractionOfDistanceThroughGlyph:NULL];
CustomLinkAttribute *link = [post.content.richText
attribute:CustomLinkAttributeName
atIndex:index
effectiveRange:NULL];
// CustomLinkAttributeName is a string constant defined in other file
// CustomLinkAttribute is a subclass of NSObject class. The instance of
// this class contains information of a link
if (link) {
// handle tap on link
}
// same technique can be used to handle tap on image
This approach is much faster and more customizable than [NSAttributedString initWithData:options:documentAttributes:error:] when rendering same html string. Even without profiling I can tell the Text Kit approach is faster. It's very fast and satisfying even though I have to parse html and construct attributed string myself. The NSDocumentTypeDocumentAttribute approach is too slow thus is not acceptable. With Text Kit, I can also create complex layout like text block with variable indentation, border, any-depth nested text block, etc. But it does need to write more code to construct NSAttributedString and to control layout process. I don't know how to calculate the bounding rect of an attributed string created with NSDocumentTypeDocumentAttribute. I believe attributed strings created with NSDocumentTypeDocumentAttribute are handled by Web Kit instead of Text Kit. Thus is not meant for variable height table view cells.
EDIT:
If you must use NSDocumentTypeDocumentAttribute, I think you have to figure out how the layout process happens. Maybe you can set some breakpoints to see what object is responsible for layout process. Then maybe you can query that object or use another approach to simulate the layout process to get the layout information. Some people use an ad-hoc cell or a UITextView object to calculate height which I think is not a good solution. Because in this way, the app has to layout the same chunk of text at least twice. Whether you know or not, somewhere in your app, some object has to layout the text just so you can get information of layout like bounding rect. Since you mentioned NSAttributedString class, the best solution is Text Kit after iOS 7. Or Core Text if your app is targeted on earlier iOS version.
I strongly recommend Text Kit because in this way, for every html string pulled from API, the layout process only happens once and layout information like bounding rect and positions of every glyph are cached by NSLayoutManager object. As long as the Text Kit objects are kept, you can always reuse them. This is extremely efficient when using table view to render arbitrary length text because text are laid out only once and drawn every time a cell is needed to display. I also recommend use Text Kit without UITextView as the official apple docs suggested. Because one must cache every UITextView if he wants to reuse the Text Kit objects attached with that UITextView. Attach Text Kit objects to model objects like I do and only update NSTextStorage and force NSLayoutManager to layout when a new html string is pulled from API. If the number of rows of table view is fixed, one can also use a fixed list of placeholder model objects to avoid repeat allocation and configuration. And because drawRect: causes Core Animation to create useless backing bitmap which must be avoided, do not use UIView and drawRect:. Either use CALayer drawing technique or draw text into a bitmap context. I use the latter approach because that can be done in a background thread with GCD, thus the main thread is free to respond to user's operation. The result in my app is really satisfying, it's fast, the typesetting is nice, the scrolling of table view is very smooth (60 fps) since all the drawing process are done in background threads with GCD. Every app needs to draw some text with table view should use Text Kit.
You need to update intrinsic content size.
I assume that you set attributed text to label in this code [self configureContentCellForIndexPath:cell atIndexPath:indexPath];
So, it should look like this
cell.youLabel.attributedText = NSAttributedString(...)
cell.youLabel.invalidateIntrinsicContentSize()
cell.youLabel.layoutIfNeeded()
You height calculation code (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width should be replaced with cell height calculation using prototyping cell.
I'm assuming you are using a UILabel to display the string?
If you are, I have had countless issues with multiline labels with autoLayout. I provided an answer here
Table View Cell AutoLayout in iOS8
which also references another answer of mine that has a breakdown of how i've solved all my issues. Similar issues have cropped up again in iOS 8 that require a similar fix in a different area.
All comes down to the idea of setting the UILabel's preferredMaxLayoutWidth every time is bounds change. What also helped is setting the cells width to be the width of the tableview before running:
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
I ran into a very similar issue on another project where fields using NSAttributedString weren't rendering with the correct height. Unfortunately, there are two bugs with it that made us completely drop using it in our project.
The first is a bug that you've noticed here, where some HTML will cause an incorrect size calculation. This is usually from the space between the p tags. Injecting CSS sort of solved the issue, but we had no control over the incoming format. This behaves differently between iOS7 and iOS8 where it's wrong on one and right on the other.
The second (and more serious) bug is that NSAttributedString is absurdly slow in iOS 8. I outlined it here: NSAttributedString performance is worse under iOS 8
Rather than making a bunch of hacks to have everything perform as we wanted, the suggestion of using https://github.com/Cocoanetics/DTCoreText worked out really well for the project.
If you can target iOS 8 using dynamic cell sizing is the ideal solution to your problem.
To use dynamic cell sizing, delete heightForRowAtIndexPath: and set self.tableView.rowHeight to UITableViewAutomaticDimension.
Here is a video with more details:
https://developer.apple.com/videos/wwdc/2014/?include=226#226
You can replace this method to calculate the height of attributed string:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width {
CGFloat result = font.pointSize + 4;
if (text)
result = (ceilf(CGRectGetHeight([text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil])) + 1);
return result;
}
Maybe the font you changed doesnt matches with the font of content on html pages. So, use this method to create attributed string with appropriate font:
// HTML -> NSAttributedString
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
NSError *error;
NSDictionary *options = #{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding] options:options documentAttributes:nil error:&error];
if(!attrString) {
NSLog(#"creating attributed string from HTML failed: %#", error.debugDescription);
}
return attrString;
}
// force font thrugh & css
- (NSAttributedString *)attributedStringFromHTML:(NSString *)html withFont:(UIFont *)font {
return [self convertHTMLtoAttributedString:[NSString stringWithFormat:#"<span style=\"font-family: %#; font-size: %f\";>%#</span>", font.fontName, font.pointSize, html]];
}
and in your tableView:heightForRowAtIndexPath: replace it with this:
case kContent:
return [self textViewHeightForAttributedText:[self attributedStringFromHTML:myHTMLString withFont:contentFont] andFont:contentFont andWidth:self.tappableCell.width];
break;
You should be able to convert to an NSString to calculate the height like this.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIFont * font = [UIFont systemFontOfSize:15.0f];
NSString *text = [getYourAttributedTextArray objectAtIndex:indexPath.row] string];
CGFloat height = [text boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width, maxHeight) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:#{NSFontAttributeName: font} context:nil].size.height;
return height + additionalHeightBuffer;
}
[cell.descriptionLabel setPreferredMaxLayoutWidth:375.0];

NSAttributedString reporting incorrect sizes for UITextView sizeThatFits and boundingRectWithSize with correct options set

I have an NSAttributedString that is reporting a boundingRectWithSize (and by extension a UITextView which improperly calculates its sizeThatFits) when the font size is decreased from the font size that was used to create it.
It doesn't happen on all NSAttributedStrings for which I do similar operations, so here's the steps to reproduce.
Use a non-standard font that does not include the full unicode character set.
Make sure the string includes characters in this "unsupported" set. iOS will render them as Helvetica in the proper size.
Scale your font down on all font attributes in your NSAttributedString. My code for doing so that produced the issue looks like this.
From inside a UITextView subclass:
NSMutableAttributedString *mutableString = [self.attributedText mutableCopy];
[mutableString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value) {
UIFont *oldFont = (UIFont *)value;
UIFont *newFont = [oldFont fontWithSize:oldFont.pointSize - 1];
[mutableString removeAttribute:NSFontAttributeName range:range];
[mutableString addAttribute:NSFontAttributeName value:newFont range:range];
}
}];
self.attributedText = [mutableString copy];
I noticed that while running this code in a while loop checking sizeThatFits to know when the text is small enough to fit that I would have a race to zero occur in some circumstances. The height is being calculated as 60px for any font value smaller than what I started with, which happens to be 50px.
When NSLoging the NSAttributedString I find that there are several attributes that I did not add with the key NSOriginalFont which does not appear to be in the list of supported attributes here. What's going on with NSOriginalFont? Why is my size being calculated incorrectly?
I ended up fixing this but found a lack of information on the web about it, so I decided to document my solution here.
NSOriginalFont attributes are created when the font used doesn't support one or more characters in the string. NSAttributedString adds these attributes that track what the font was "supposed" to be before a substitution to Helvetica occurred. I could make up a situation where this is useful (an all-caps font that you sometimes run uppercaseString: on?) but it wasn't useful to me.
In fact it was harmful. As I iterated through my font related attributes to decrease the size as shown above the visible size of the text was decreasing but the NSOriginalFont attribute retained a reference to the large size.
There's no built in constant for NSOriginalFont but if you call it by name it's possible to strip it from your NSMutableAttributedString. If you do you'll begin to get proper results from sizeThatFits, boundingRectWithSize, and other similar functions assuming that you're passing the correct options.
I ended up creating a simple category method on NSMutableAttributedString, included below, that works well.
NSMutableAttributedString+StripOriginalFont.h
#interface NSMutableAttributedString (StripOriginalFont)
- (void) stripOriginalFont;
#end
NSMutableAttributedString+StripOriginalFont.m
#implementation NSMutableAttributedString (StripOriginalFont)
- (void) stripOriginalFont{
[self enumerateAttribute:#"NSOriginalFont" inRange:NSMakeRange(0, self.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value){
[self removeAttribute:#"NSOriginalFont" range:range];
}
}];
}
#end
Presumably you could simply modify it to keep it "in-sync" instead of removing it entirely but it wasn't useful to me for this particular project.
For stripping in swift you can use this extension:
extension NSAttributedString {
func strippedOriginalFont() -> NSAttributedString? {
let mutableCopy = self.mutableCopy() as? NSMutableAttributedString
mutableCopy?.removeAttribute(NSAttributedStringKey(rawValue: "NSOriginalFont"), range: NSMakeRange(0, self.length))
return mutableCopy?.copy() as? NSAttributedString
}
}
Don't know if this will help solve your issue, but check out my solution for auto-sizing text UITextView here: https://stackoverflow.com/a/30400391/1664123
Create NSTextStorage object and init with the attributedString.
and calculate bounds.
NSTextStorage *attributedText = [[NSTextStorage alloc] initWithAttributedString:[[NSAttributedString alloc] initWithString:text attributes:#{NSFontAttributeName:systemFont}]];
CGRect textRect = [attributedText boundingRectWithSize:CGSizeMake(textW, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
I had the same problem. when I call textView.setAttributedString(), it will automatically add NSOriginalFont attribute for me which lead to the wrong size. I also use sizeThatFits to calculate height.
The reason we got the wrong size is textView use the NSOriginalFont to calculate size which is not suitable for changed NSFont.
But if we use a NSTextStorage to create the attributedString and call textView.setAttributedText, then it will not add NSOriginalFont(I don't know why, but this fixes my problem) and the calculation of size will get the right answer.
Simple Code:
func getAttributedStringForTextView(content: String) -> NSAttributedString {
var attriString = NSMutableAttributedString(string: content)
// add attributes here
...
// at last, use an NSTextStorage to wrap the result
return NSTextStorage(attributedString: attriString)
}
Hope this helps.

UILabel - Alternative for Deprecated Method "adjustsLetterSpacingToFitWidth"

So in my code, I was checking if my characters fit in my Label or not and had the following line :
return self.adjustsLetterSpacingToFitWidth;
This was placed in an implementation of UILabel. Can someone tell me just what is the exact alternative for this? The documentation says - Use NSKernAttributeName, but I wasn't quite able to understand that. Can someone help me on this?
In the larger sense - The method is called as:
xLab.adjustLetterSpacingToFitWidth = YES;
In my code, I have:
#implementation UILabel ()
- (BOOL) getter {
return self.adjustsLetterSpacingToFitWidth;
}
- (void) setter:(BOOL) setVal {
self.adjustsLetterSpacingToFitWidth = setVal;
}
#end
First of all, your getter and setter are entirely superfluous as shown; wherever you call getter and/or setter, you could simply get/set adjustsLetterSpacingToFitWidth directly.
As for the question of how to do auto-kerning with NSKernAttributeName, Apple's documentation says: “To turn on auto-kerning in the label, set NSKernAttributeName of the string to [NSNull null]”, i.e., you would do something like:
NSMutableAttributedString *s;
s = [[NSMutableAttributedString alloc] initWithString:my_uilabel.text];
[s addAttribute:NSKernAttributeName
value:[NSNull null]
range:NSMakeRange(0, s.length)];
my_uilabel.attributedText = s;
But if you did not want to do automatic adjustment of letter spacing but rather find out whether the text fits in the label or not, you might want to check the various methods in NSString UIKit additions. (This guess of intent is based on the wording in the original question.)

Is there a way to implicitly make an button be multi-line?

I want a button that is similar to this:
ONE
TWO
Where one word is over the other. However, it may be:
THREE
FOUR
Or any number, really. Is there a way with NSAttributedStrings that I can say to always have a line-break after the first word?
It's really not about NSAttributedString or NSString, but rather about the button itself. You may think of subclassing UIButton and overriding setTitle:forState: to automatically replace the first space with a \n.
Specifically the setTitle:forState: would look something like this
- (void)setTitle:(NSString *)title forState:(UIControlState)state {
NSRange firstSpaceRanger = [title rangeOfString:#" "];
if (firstSpaceRanger.location != NSNotFound) {
title = [title stringByReplacingCharactersInRange:firstSpaceRanger withString:#"\n"];
}
[super setTitle:title forState:state];
}
For instance, given one two three this will produce
one
two three
In order to make the button multiline you can do, in the UIButton initializer:
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
Yes, firstSpaceRanger is intentional. I couldn't resist.

How to set the color of the place holder text for a UITextField while preserving its existing properties?

I have seen some answers that show how to change the placeHolder text color for UITextField by overriding the drawPlaceholderInRect: method such as this one:
iPhone UITextField - Change placeholder text color
but that does not maintain the existing attributes such as alignment, font, etc...what is a better way to solve this?
From iOS 6,
Without any subclassing, one can accomplish this with a couple lines of code like so:
UIColor *color = [UIColor blackColor];
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:placeholderText attributes:#{NSForegroundColorAttributeName: color}];
There are multiple ways to achieve this.
Using Interface Builder or Storyboard
Select the Textfield for which you want to change placeholder color
go to the Identity inspector menu on Top right of Xcode
Add the key value pair this way
Key path = _placeholderLabel.textColor
Click the Type and chose Color attribute .
Then select the color in value.
Set The placeholder color using code :
Process 1:
[textField setValue:[UIColor blueColor] forKeyPath:#"_placeholderLabel.textColor"];
Process 2 :
Override drawPlaceholderInRect:(CGRect)rect method
- (void) drawPlaceholderInRect:(CGRect)rect {
[[UIColor blueColor] setFill];
[[self placeholder] drawInRect:rect withFont:[UIFont systemFontOfSize:14]];
}
There is indeed a much better way to handle this now. This will work for iOS 6 and 7.
(Note this example, I created the code in AwakeFromNib since it won't be changing colors once set. But if you don't use XIB, you will have to change the location where you put this code, such as in drawPlaceholderInRect,)
In this example, we create a subclass of UITextField, override awakeFromNib and then set the placeHolder text color to red:
- (void)awakeFromNib
{
if ([self.attributedPlaceholder length])
{
// Extract attributes
NSDictionary * attributes = (NSMutableDictionary *)[ (NSAttributedString *)self.attributedPlaceholder attributesAtIndex:0 effectiveRange:NULL];
NSMutableDictionary * newAttributes = [[NSMutableDictionary alloc] initWithDictionary:attributes];
[newAttributes setObject:[UIColor redColor] forKey:NSForegroundColorAttributeName];
// Set new text with extracted attributes
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:[self.attributedPlaceholder string] attributes:newAttributes];
}
}
The nice thing about this approach is that it maintains the current UITextField properties for the placeHolder string and so will allow you to work in IB for most of what you set. In addition, its much more efficient than doing everytime you need to draw. It also allows you to change any other property you want on the placeHolder text while maintaining the rest of the properties.
As mentioned above, if don't use XIBs, then you will need to call this at some other time. If you do put this code in the drawPlaceholderInRect: method, then make sure you call [super drawPlaceholderInRect:] at the end of it.
The safe way to customize UITextField’s placeholder is subclassing the UITextField and overriding placeholderRectForBounds:, Apple won’t bother you on this one. However, if you want to take the risk, you can try this way:
[self.MyTextField setValue:[UIColor darkGrayColor] forKeyPath:#"_placeholderLabel.textColor"];
Source.

Resources