Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITextField shadow does not display when editing

I want to draw the text in an UITextField with a shadow. In order to do this, I have subclassed UITextField, and implemented the drawTextInRect: method as follows:

- (void)drawTextInRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Create shadow color
    float colorValues[] = {0.21875, 0.21875, 0.21875, 1.0};
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGColorRef shadowColor = CGColorCreate(colorSpace, colorValues);
    CGColorSpaceRelease(colorSpace);

    // Create shadow
    CGSize shadowOffset = CGSizeMake(2, 2);
    CGContextSetShadowWithColor(context, shadowOffset, 0, shadowColor);
    CGColorRelease(shadowColor);

    // Render text
    [super drawTextInRect:rect];    
}

This works great for when the text field is not editing, but as soon as editing begins, the shadow disappears. Is there anything I am missing?

like image 409
conradev Avatar asked Nov 03 '22 19:11

conradev


2 Answers

Here is the code for following component

enter image description here

@interface AZTextField ()
- (void)privateInitialization;
@end

@implementation AZTextField

static CGFloat const kAZTextFieldCornerRadius = 3.0;

- (id)initWithFrame:(CGRect)frame
{

    self = [super initWithFrame:frame];
    if (!self) return nil;
    [self privateInitialization];
    return self;
}

// In case you decided to use it in a nib
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (!self) return nil;
    [self privateInitialization];
    return self;
}

- (void)privateInitialization
{
    self.borderStyle = UITextBorderStyleNone;

    self.layer.masksToBounds = NO;
    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
    self.layer.shadowOpacity = 0.5f;

    self.layer.backgroundColor = [UIColor whiteColor].CGColor;
    self.layer.cornerRadius = 4;

    // This code is better to be called whenever size of the textfield changed,
    // so if you plan to do that you can add an observer for bounds property
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:kAZTextFieldCornerRadius];
    self.layer.shadowPath = shadowPath.CGPath;
}

@end

Couple of things to consider:

  • You want to set borderStyle to none, otherwise you'll end up with UIKit putting subviews into your textfield
  • Depending on the Xcode version you might want to link QuartzCore and to #import <QuartzCore/QuartzCore.h>
  • For more complex appearance you can still use shadow properties of the layer and move the drawing code itself into the drawRect: method, but jake_hetfield was right if you override drawRect you don't want to call super, especially in the end of the method
  • As for the text drawing (you can see that it sticks to close to the component borders), you have a separate drawTextInRect: and drawPlaceholderInRect: method that draws the text and placeholder respectively
  • You can use UIColor method for colors and call CGColor property, it makes code more readable and easier to maintain

Hope that helps!

like image 156
Sash Zats Avatar answered Nov 15 '22 04:11

Sash Zats


Inspired by @jake_hetfield answer I created a custom UITextField that uses an internal label to do the drawing, check it out:

ShadowTextField .h file

#import <UIKit/UIKit.h>

@interface ShadowTextField : UITextField

// properties to change the shadow color & offset
@property (nonatomic, retain) UIColor *textShadowColor;
@property (nonatomic) CGSize textShadowOffset;

- (id)initWithFrame:(CGRect)frame 
               font:(UIFont *)font 
          textColor:(UIColor *)textColor 
        shadowColor:(UIColor *)shadowColor 
       shadowOffset:(CGSize)shadowOffset;

@end 

ShadowTextField .m file

#import "ShadowTextField.h"

@interface ShadowTextField ()
@property (nonatomic, retain) UILabel *internalLabel;
@end

@implementation ShadowTextField
@synthesize internalLabel = _internalLabel;
@synthesize textShadowColor = _textShadowColor;
@synthesize textShadowOffset = _textShadowOffset;

- (id)initWithFrame:(CGRect)frame 
               font:(UIFont *)font 
          textColor:(UIColor *)textColor 
        shadowColor:(UIColor *)shadowColor 
       shadowOffset:(CGSize)shadowOffset
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code

        // register to my own text changes notification, so I can update the internal label
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleUITextFieldTextDidChangeNotification) 
                                                     name:UITextFieldTextDidChangeNotification
                                                   object:nil];

        self.font = font;
        self.textColor = textColor;

        self.textShadowColor = shadowColor;
        self.textShadowOffset = shadowOffset;
    }
    return self;
}

// when the user enter text we update the internal label 
- (void)handleUITextFieldTextDidChangeNotification
{
    self.internalLabel.text = self.text;

    [self.internalLabel sizeToFit];
}

// init the internal label when first needed
- (UILabel *)internalLabel
{
    if (!_internalLabel) {
        _internalLabel = [[UILabel alloc] initWithFrame:self.bounds];
        [self addSubview:_internalLabel];

        _internalLabel.font = self.font;
        _internalLabel.backgroundColor = [UIColor clearColor];
    }
    return _internalLabel;
}

// override this method to update the internal label color
// and to set the original label to clear so we wont get two labels
- (void)setTextColor:(UIColor *)textColor
{
    [super setTextColor:[UIColor clearColor]];

    self.internalLabel.textColor = textColor;
}

// override this method to update the internal label text
- (void)setText:(NSString *)text
{
    [super setText:text];

    self.internalLabel.text = self.text;

    [self.internalLabel sizeToFit];
}

- (void)setTextShadowColor:(UIColor *)textShadowColor
{
    self.internalLabel.shadowColor = textShadowColor;
}

- (void)setTextShadowOffset:(CGSize)textShadowOffset
{
    self.internalLabel.shadowOffset = textShadowOffset;
}

- (void)drawTextInRect:(CGRect)rect {
    // don't draw anything
    // we have the internal label for that...
}

- (void)dealloc {
    [_internalLabel release];
    [_textShadowColor release];

    [super dealloc];
}

@end  

Here is how you use it in your view controller

- (void)viewDidLoad
{
    [super viewDidLoad];

    ShadowTextField *textField = [[ShadowTextField alloc] initWithFrame:CGRectMake(0, 0, 320, 30) 
                                                                   font:[UIFont systemFontOfSize:22.0] 
                                                              textColor:[UIColor whiteColor] 
                                                            shadowColor:[UIColor redColor] 
                                                           shadowOffset:CGSizeMake(0, 1) ] ;
    textField.text = @"This is some text";    
    textField.backgroundColor = [UIColor blackColor];
    [self.view addSubview:textField];
}
like image 40
Eyal Avatar answered Nov 15 '22 06:11

Eyal