16 декабря 2012 г.

UIDatePicker как клавиатура для UITextField

Цель: сделать удобной выбор даты в UITextField, будем использовать UIDatePicker
Алгоритм: перехватим событие - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField у UITextField и вместо показа системной клавиатуры, покажем наше вью.

Думаю не стоит описывать процесс создания и назначения делегата у UITextField, это можно сделать как в Interface Builder-е так и динамически. Сразу смотрим метод делегата -(void)textFieldDidBeginEditing:(UITextField *)textField:

- (void)textFieldDidBeginEditing:(UITextField *)aTextField
{
 [aTextField resignFirstResponder];
 
 [_pickerViewPopup release];
 _pickerViewPopup = [[UIActionSheet alloc] initWithTitle:nil delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
 
 [_pickerView release];
 _pickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 44.0, 0.0, 0.0)];
 _pickerView.datePickerMode = UIDatePickerModeDate;
 _pickerView.hidden = NO;
 _pickerView.date = [NSDate date];
 [_pickerView addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
 
 UIToolbar *pickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
 pickerToolbar.barStyle = UIBarStyleBlackOpaque;
 [pickerToolbar sizeToFit];
 
 NSMutableArray *barItems = [[NSMutableArray alloc] init];
 
 UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
 [barItems addObject:flexSpace];
 [flexSpace release];
 
 UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonPressed:)];
 [barItems addObject:doneBtn];
 [doneBtn release];
 
 UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonPressed:)];
 [barItems addObject:cancelBtn];
 [cancelBtn release];
 
 [pickerToolbar setItems:barItems animated:YES];
 [barItems release];
 
 [_pickerViewPopup addSubview:pickerToolbar];
 [_pickerViewPopup addSubview:_pickerView];
 [_pickerViewPopup showInView:self.window];
 [_pickerViewPopup setBounds:CGRectMake(0.0, 0.0, 320.0, 464.0)];
 [pickerToolbar release];
}


тут мы создаем модальное окно, на основе UIActionSheet и в него вставляем UIDatePicker и UIToolbar с кнопками "Ok" и "Cancel".

И события нажатия на эти кнопки:

- (void)doneButtonPressed:(id)sender
{
 self.dt = [_pickerView date];
 
 [_pickerViewPopup dismissWithClickedButtonIndex:1 animated:YES];
 [_pickerView removeTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}

- (void)cancelButtonPressed:(id)sender
{
 [_pickerViewPopup dismissWithClickedButtonIndex:1 animated:YES];
 [_pickerView removeTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}


В обоих случаях скрываем наше "модельное" окно и отписываемся от события получения нотификации о изменении даты на UIDatePicker. А при нажатии на "Ok" так-же сохраняем выбранную дату на "барабане".

И событие изменения даты на UIDatePicker для изменения даты так-же в текстовом поле:

- (void)dateChanged
{
 NSDateFormatter *df = [[NSDateFormatter alloc] init];
 [df setDateFormat:@"dd.MM.yy"];
 _tf.text = [df stringFromDate:[_pickerView date]];
}


Проект на github-е. Утащено с stackoverflow.

9 декабря 2012 г.

Применение изображения-маски на UIView

После длительного перерыва решил опубликовать новый пост в блоге.

Традиционно все UIView в iOS имеют прямоугольную форму. И с помощью полупрозрачного цвета (так-же изображений с alpha каналом) и порядком (z-order) наложения вью друг на друга можно добиваться практически всего, что можно вообразить. Но вот понадобился мне полупрозрачный свой UISwitch в котором был предопределен порядок вьюшек.

И что б этот пост не был постом об одном методе, опишу процесс создания компоненты.

Есть изображения:

1. подложка компоненты

2. маска под эту подложку

3. включенное и выключенное состояния

Наследоваться будем от UIView.

В методе init будем создавать вью которые нам и понадобятся.

  _backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"butt_bg"]];
  [self addSubview:_backgroundImage];


создаем подложку компоненты и добавляем ее на родительское вью

  UIView *maskedView = [[UIView alloc] initWithFrame:self.bounds];
  maskedView.backgroundColor = [UIColor clearColor];
  [self addSubview:maskedView];


создаем вью, к которому будет примененна маска, на него будем ложить дальше компоненты для того, что б они не вылазили за пределы внутреннего "овала"

  _backgroundView = [[UIScrollView alloc] initWithFrame:self.bounds];
  _backgroundView.backgroundColor = [UIColor clearColor];
  _backgroundView.clipsToBounds = YES;
  _backgroundView.delegate = self;
  _backgroundView.clearsContextBeforeDrawing = NO;
  _backgroundView.pagingEnabled = YES;
  _backgroundView.showsVerticalScrollIndicator = NO;
  _backgroundView.showsHorizontalScrollIndicator = NO;
  _backgroundView.scrollsToTop = NO;
  _backgroundView.contentSize = CGSizeMake(105.0, _backgroundView.bounds.size.height);
  _backgroundView.bounces = NO;
  [maskedView addSubview:_backgroundView];
  
  _onLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _onLabel.backgroundColor = [UIColor clearColor];
  _onLabel.opaque = NO;
  _onLabel.textColor = [UIColor blackColor];
  _onLabel.font = [UIFont boldSystemFontOfSize:16.0];
  _onLabel.textAlignment = UITextAlignmentCenter;
  _onLabel.text = NSLocalizedString(@"ON", nil);
  _onLabel.frame = CGRectMake(0.0, 0.0, 43.0, _backgroundView.frame.size.height);
  [_backgroundView addSubview:_onLabel];

  UIImage *img = [UIImage imageNamed:@"r_butt_on"];
  _ballImage0 = [[UIImageView alloc] initWithImage:img];
  _ballImage0.frame = CGRectMake((_backgroundView.contentSize.width-img.size.width)/2.0,
           (_backgroundView.contentSize.height-img.size.height)/2.0,
           img.size.width, img.size.height);
  [_backgroundView addSubview:_ballImage0];
  
  img = [UIImage imageNamed:@"r_butt_off"];
  _ballImage1 = [[UIImageView alloc] initWithImage:img];
  _ballImage1.frame = _ballImage0.frame;
  _ballImage1.alpha = 0.0;
  [_backgroundView addSubview:_ballImage1];
  
  _offLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _offLabel.backgroundColor = [UIColor clearColor];
  _offLabel.opaque = NO;
  _offLabel.textColor = [UIColor blackColor];
  _offLabel.font = [UIFont boldSystemFontOfSize:16.0];
  _offLabel.textAlignment = UITextAlignmentCenter;
  _offLabel.text = NSLocalizedString(@"OFF", nil);
  _offLabel.frame = CGRectMake(60.0, 0.0, 43.0, _backgroundView.frame.size.height);
  [_backgroundView addSubview:_offLabel];


тут _backgroundView - скролл который будет анимированно двигаться, котда будем прикасаться к нему, на нем лежит две текстовые метки: _onLabel и _offLabel которые просто отображают текст и вьюшки с включенной и выключенной лампочками (_ballImage0 и _ballImage1).

А вот и подобрались к самому главному, создаем слой маски из изображения и применяем его к вью:

  UIImage *_maskingImage = [UIImage imageNamed:@"butt_mask"];
  CALayer *_maskingLayer = [CALayer layer];
  _maskingLayer.frame = CGRectMake(1.0, 1.0, self.bounds.size.width-2.0, self.bounds.size.height-2.0);
  [_maskingLayer setContents:(id)[_maskingImage CGImage]];
  maskedView.layer.mask = _maskingLayer;



Регистрируем слушателя на касание внутри нашей компоненты:

UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; [self addGestureRecognizer:singleFingerTap]; [singleFingerTap release];

и обработчик этого одиночного касания (до этого мы все делали в методе init):

- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
 [self setOn:!_on animated:YES];
}


так-же методы установки/получения состояния компоненты:

- (void)setOn:(BOOL)on animated:(BOOL)animated
{
 _on = on;
 _scrollingAnimation = animated;
 [_backgroundView setContentOffset:CGPointMake(on?0.0:_backgroundView.contentSize.width-self.bounds.size.width, 0.0) animated:animated];
 _ballImage0.alpha = _on?1.0f:.0f;
 _ballImage1.alpha = _on?.0f:1.0f;
}

- (void)setOn:(BOOL)on
{
 [self setOn:on animated:NO];
}

- (BOOL)on
{
 return _on;
}



тут в зависимости от состояния меняем прозрачность наших изображений _ballImage0 и _ballImage1 ну и храним состояние в переменной _on

и обработчики перемещения UIScrollView пользователем:

- (void)scrollViewDidScroll:(UIScrollView *)sender
{
 CGFloat koof = sender.contentOffset.x/(sender.contentSize.width-sender.bounds.size.width);
 _ballImage0.alpha = 1.0f-koof;
 _ballImage1.alpha = koof;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
 if (!_scrollingAnimation)
 {
  self.on = _backgroundView.contentOffset.x<1.0;
 }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
 if (!decelerate && !_scrollingAnimation)
 {
  self.on = _backgroundView.contentOffset.x<1.0;
 }
}


Вот и все, проект можно посмотреть на github-е, в нем специально закомментирована строка _backgroundView.bounces = NO;, для более наглядной демонстрации эффекта.

ЗЫ: Вот еще одно интересное применение маски.