Site icon AppTractor

Создаём стираемую карту (scratch card) для iOS-приложения

Некоторые из вас, скорее всего, сталкивались с такими картами и лотерейными билетами, у которых есть защитная стираемая пленка. Наверняка многие испытывали приятное чувство ожиемого счастья, большого выигрыша, стирая пленку с помощью монеты :)

В этой статье будет рассмотрено, как реализовать такое поведение с использованием контекстной графики. Готовый проект доступен по ссылке: https://github.com/david8lumen/ScratchCard.

Вот как в итоге будет выглядеть наша реализация:

В данном примере будет использоваться наследник UIImageView. Назовем его ScratchCardImageView.

Так же, для удобства, создадим делегирующие методы. В нашем случае пока достаточно одного метода, который уведомляет делегата о том, что контент под защитной плёнкой полностью отображен.

Зададим несколько свойств и расширений для нашего кастомного UIImageView.

  1. Создаем структуру с точками x, y и делаем эту структуру Hashable
  2. Задаем множество erasedPoints, которое будет содержать те точки, которые были стёрты с карты
  3. Нам так же необходимо хранить последнюю точку на котором завершилось стирание. Это нужно для того чтобы выстроить путь, по которому будет стираться пленка
  4. innerView — вью которая отображается под стираемой картинкой. В нашем случае это будет label с отображением суммы
  5. Тип стираемой линии и её ширина. Они будут так же задаваться снаружи, поэтому объявляем их не приватными
  6. Делегат, которому будет приходить уведомление о полном показе контента под пленкой
  7. И последнее — необходимо задать isUserInteractionEnabled = true для того, чтобы тачи регистрировались на нашей imageView

Далее нам нужно перейти в сториборд и расположить все необходимые элементы во ViewController’e. Расписывать порядок расположения элементов и их констрейнты занятие скучное, поэтому предлагаю посмотреть как это сделано в готовом проекте. Упомяну лишь иерархию расположения элементов:

  1. UIView внутри которого UILabel
  2. ScratchCardImageView поверх UIView

Все элементы расположены по центру. Итоговое расположение элементов представлено на картинке:

Далее необходимо вынести outlet’s от нашего лейбла и кастомной imageView во ViewController.

Внутри метода viewDidLoad() задаем все необходимые свойства для scratchCard и подписываемся на ScratchCardImageViewDelegate:

Подготовительные работы сделаны, теперь переходим к основной части.

Возвращаемся в класс ScratchCardImageView и переопределяем 2 метода:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

и

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)

В методе touchesBegan нам необходимо запоминать нажатую точку, а в методе touchesMoved происходит вся логика, с помощью которой стирается картинка.

Не прибегая к утомительному описанию каждой строчки, постараюсь объяснить как можно более проще:

  1. Нам необходимо получить точку той части innerView, относительно которой было нажатие в нашей imageView (let innerViewRelativePoint = convert(currentLocation, to: innerView))
  2. Создаётся структура ScratchCardPoint на основе ранее полученных координат
  3. Если данная координата входит в систему координат лейбла и если данная точка ранее не была стёрта — то мы должны положить в множество эту точку с учетом lineWidth, так как мы стираем не по одному поинту, а по тому, что задано в свойстве lineWidth. Эта логика отображена на картинке:
  4. Если количество стертых точек в множестве больше или равно размеру нашего лейбла — то считаем что наш контент полностью отображен, следовательно отправляем сообщение делегату.

Итоговый метод touchesMoved выглядит следующим образом:

Внутри мы вызываем метод eraseBetween. Здесь и происходит отображение пути от начальной до конечной точки. Происходит это за счет создания bmp контекста. Далее создается объект CGMutablePath в котором указывается начальная и конечная точка.

Получив текущий graphic контекст с помощью UIGraphicsGetCurrentContext() задаем путь по которому необходимо выстроить «отрисовку». Ключевым свойством в данном случае является

context?.setBlendMode(.clear)

который в данном случае делает заданный путь прозрачным.

Осталось не забыть вызывать strokePath() который сделаем данные точки прозрачными.

Итоговый метод eraseBetween(fromPoint: CGPoint, currentPoint: CGPoint) выглядит следующим образом:

На этом всё, теперь можно запустить приложение и проверить результат!

Автор: Григорян Давид

Exit mobile version