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;, для более наглядной демонстрации эффекта.

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

1 комментарий: