Содержание
Как-то задумал я сделать UITextField с закругленными краями, такой же как поле поиска в мобильном Safari. К сожалению в iOS SDK нету такого типа поля, есть с недостаточно закругленными краями. Первое что приходит на ум — использовать UIImageView с картинкой, а поверх него положить UITextField. Решение подходит, если поле будет оставаться таких же размеров с какими было создано. Но мне нужно было выполнять анимации над полем, в таком случае пришлось бы пересчитывать размеры картинок в блоке анимации, что порождало бы лишний код. К тому же, хотелось чтобы текстовое поле существовало неразрывно с его закругленными краями. В общем, такое решение не годится. Попытки установить угол закругления через QuartzCore и CALayer так же не дали нужного результата.
Сделаем небольшую постановку задачи: создать текстовое поле с закругленными краями, которое можно использовать как и через Interface Builder, так и создавая кодом. Поле должно быть неразрывно с картинкой, отображающей закругленные края. Текст должен помещаться внутрь заданной области и не перекрывать края закругления.
Приступаем к решению
Для начала, я решил подготовить картинки этих самых закругленных полей. Тут очень помог сайт teehanlax.com с iOS 5 GUI PSD. В итоге я получил вот это:
Как видите, я выделил центральную часть, которая будет растягиваться и два закругленных края.
Так как я хотел получить нечто целое, хорошо бы создать свой класс текстового поля и наследовать его от UITextField:
@interface RoundedTextField : UITextField @end
Первым делом следует перекрыть метод setBorderStyle: так как он может повлиять на вид поля, а я этого очень не хотел.
-(void) setBorderStyle:(UITextBorderStyle)borderStyle { [super setBorderStyle:UITextBorderStyleNone]; }
Если кто-то задумает изменить стиль границ поля (например, Interface Builder) — этого не получится.
Немного про Interface builder: так как я планировал добавлять это поле через Interface Builder и класс поля унаследован от UITextField — можно смело добавлять на вид стандартный UITextField, изменив ему Custom class.
На картинке вы видите текстовое поле с типом границ UITextBorderStyleRoundedRect, но это никак не влияет на наше поле т.к. мы перекрыли метод установки типа границ.
Отрисовка картинок
Закругленные края и центральную часть я решил рисовать в методе drawRect:, выглядело это так:
-(void) drawRect:(CGRect)rect { UIImage* leftImage = [UIImage imageNamed:@"leftRoundedField.png"]; UIImage* rightImage = [UIImage imageNamed:@"rightRoundedField.png"]; UIImage* centerImage = [UIImage imageNamed:@"centerRoundedField.png"];// левая граница, рисуем сразу от начала [leftImage drawAtPoint:CGPointZero];
// правая граница [rightImage drawAtPoint:CGPointMake(self.bounds.size.width - rightImage.size.width, 0)];
// центральная часть CGRect centerImageRect = { {leftImage.size.width, 0}, // точка {self.bounds.size.width-leftImage.size.width-rightImage.size.width, centerImage.size.height} // размеры }; }
Получилось вот что:
Как видим, текст перекрывает левую границу, что никуда не годится. Отложив на время решение этой проблемы, я решил посмотреть как анимируется изменение размеров поля. Я запустил такую анимацию:
[UIView animateWithDuration:1.0 animations:^{ self.textField.frame = CGRectInset(self.textField.frame, -50.0, 0); }];
И получил такой результат:
Очевидно, что нужно перерисовывать картинки при изменении фрейма, я перекрыл метод изменения фрейма. Сделал, чтобы при изменении фрейма содержимое рисовалось заново (вызывался метод drawRect:):
-(void) setFrame:(CGRect)frame { [super setFrame:frame]; [self setNeedsDisplay]; }
Результат был следующий, и он был таким как мне нужен. Но в процессе анимации поле выглядело деформированным:
Подумав, что это никак не исправить, я решил не использовать рисование в методе drawRect:. Я решил добавлять картинки границ используя UIImageView (уж они то точно не деформируются!), но не отдельно от UITextField, а на него. При таком подходе можно избавиться от перекрытия метода setFrame:.
-(void) configureTextField { UIImage* leftImage = [UIImage imageNamed:@"leftRoundedField.png"]; UIImage* rightImage = [UIImage imageNamed:@"rightRoundedField.png"]; UIImage* centerImage = [UIImage imageNamed:@"centerRoundedField.png"];UIImageView* leftImageView = [[[UIImageView alloc] initWithImage:leftImage] autorelease]; leftImageView.frame = CGRectMake(0, 0, leftImage.size.width, leftImage.size.height);
UIImageView* rightImageView = [[[UIImageView alloc] initWithImage:rightImage] autorelease]; rightImageView.frame = CGRectMake(self.bounds.size.width-rightImage.size.width, 0, rightImage.size.width, rightImage.size.height); rightImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
UIImageView* centerImageView = [[[UIImageView alloc] initWithImage:centerImage] autorelease]; centerImageView.frame = CGRectMake(leftImage.size.width, 0, self.bounds.size.width - leftImage.size.width - rightImage.size.width, self.bounds.size.height); centerImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubview:centerImageView]; [self addSubview:leftImageView]; [self addSubview:rightImageView]; }
Этот метод следуюет вызывать, когда поле создается:
// если создается из Interface Builder -(id) initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; [self configureTextField]; return self; }// если создается из кода -(id) initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; [self configureTextField]; return self; }
Результат анимации получился таким как и требовалось.
Уменьшаем границы области ввода
Осталась одна проблема — сделать так, чтобы текст не перекрывал закругленную границу. У UITextField существует набор методов, которые возвращают границы для рисования конкретных частей — области ввода, кнопки «Clear» и т.д. Эти методы не стоит вызывать напрямую, но всегда можно переопределить. Вот они:
- (CGRect)borderRectForBounds:(CGRect)bounds; - (CGRect)textRectForBounds:(CGRect)bounds; - (CGRect)placeholderRectForBounds:(CGRect)bounds; - (CGRect)editingRectForBounds:(CGRect)bounds; - (CGRect)clearButtonRectForBounds:(CGRect)bounds; - (CGRect)leftViewRectForBounds:(CGRect)bounds; - (CGRect)rightViewRectForBounds:(CGRect)bounds;
Нужно перекрыть нужные методы и возвращать уменьшенную область. Я определил метод, который из оригинальной области делает ту, которая не перекрывает закругленные края:
- (CGRect) adjustBoundsFromBounds:(CGRect)bounds { return CGRectInset(bounds, 10.0, 0); }
А далее просто перекрыл нужные методы:
- (CGRect)borderRectForBounds:(CGRect)bounds { return [super borderRectForBounds:[self adjustBoundsFromBounds:bounds]]; } - (CGRect)textRectForBounds:(CGRect)bounds { return [super textRectForBounds:[self adjustBoundsFromBounds:bounds]]; } - (CGRect)editingRectForBounds:(CGRect)bounds { return [super editingRectForBounds:[self adjustBoundsFromBounds:bounds]]; } - (CGRect)leftViewRectForBounds:(CGRect)bounds { return [super leftViewRectForBounds:[self adjustBoundsFromBounds:bounds]]; } - (CGRect)rightViewRectForBounds:(CGRect)bounds { return [super rightViewRectForBounds:[self adjustBoundsFromBounds:bounds]]; }
Как видите, в каждом методе я вызываю метод родительского класса, чтобы он правильно посчитал область. Но область я передаю уже уменьшенную.
Получилось вот что (и кнопка «Clear» на месте!):
Велосипед
Как оказалось, я изобрел очередной велосипед с рисованием картинок. Подсказал в Twitter @talissman и @dlebedev в комментариях.
Можно использовать свойство background у UITextField. Для этого я подготовил новую картинку:
Изменился метод configureTextfield:
-(void) configureTextField { UIImage* background = [[UIImage imageNamed:@"bg.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];[self setBackground:background]; }
Обновленный проект с исходным кодом можно загрузить по этой ссылке.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: