Программирование
Создаём стираемую карту (scratch card) для iOS-приложения
В этой статье будет рассмотрено, как реализовать такое поведение с использованием контекстной графики.
Некоторые из вас, скорее всего, сталкивались с такими картами и лотерейными билетами, у которых есть защитная стираемая пленка. Наверняка многие испытывали приятное чувство ожиемого счастья, большого выигрыша, стирая пленку с помощью монеты :)
В этой статье будет рассмотрено, как реализовать такое поведение с использованием контекстной графики. Готовый проект доступен по ссылке: https://github.com/david8lumen/ScratchCard.
Вот как в итоге будет выглядеть наша реализация:
В данном примере будет использоваться наследник UIImageView. Назовем его ScratchCardImageView.
Так же, для удобства, создадим делегирующие методы. В нашем случае пока достаточно одного метода, который уведомляет делегата о том, что контент под защитной плёнкой полностью отображен.
Зададим несколько свойств и расширений для нашего кастомного UIImageView.
- Создаем структуру с точками x, y и делаем эту структуру Hashable
- Задаем множество erasedPoints, которое будет содержать те точки, которые были стёрты с карты
- Нам так же необходимо хранить последнюю точку на котором завершилось стирание. Это нужно для того чтобы выстроить путь, по которому будет стираться пленка
- innerView — вью которая отображается под стираемой картинкой. В нашем случае это будет label с отображением суммы
- Тип стираемой линии и её ширина. Они будут так же задаваться снаружи, поэтому объявляем их не приватными
- Делегат, которому будет приходить уведомление о полном показе контента под пленкой
- И последнее — необходимо задать isUserInteractionEnabled = true для того, чтобы тачи регистрировались на нашей imageView
Далее нам нужно перейти в сториборд и расположить все необходимые элементы во ViewController’e. Расписывать порядок расположения элементов и их констрейнты занятие скучное, поэтому предлагаю посмотреть как это сделано в готовом проекте. Упомяну лишь иерархию расположения элементов:
- UIView внутри которого UILabel
- 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 происходит вся логика, с помощью которой стирается картинка.
Не прибегая к утомительному описанию каждой строчки, постараюсь объяснить как можно более проще:
- Нам необходимо получить точку той части innerView, относительно которой было нажатие в нашей imageView (let innerViewRelativePoint = convert(currentLocation, to: innerView))
- Создаётся структура ScratchCardPoint на основе ранее полученных координат
- Если данная координата входит в систему координат лейбла и если данная точка ранее не была стёрта — то мы должны положить в множество эту точку с учетом lineWidth, так как мы стираем не по одному поинту, а по тому, что задано в свойстве lineWidth. Эта логика отображена на картинке:
- Если количество стертых точек в множестве больше или равно размеру нашего лейбла — то считаем что наш контент полностью отображен, следовательно отправляем сообщение делегату.
Итоговый метод touchesMoved выглядит следующим образом:
Внутри мы вызываем метод eraseBetween. Здесь и происходит отображение пути от начальной до конечной точки. Происходит это за счет создания bmp контекста. Далее создается объект CGMutablePath в котором указывается начальная и конечная точка.
Получив текущий graphic контекст с помощью UIGraphicsGetCurrentContext() задаем путь по которому необходимо выстроить «отрисовку». Ключевым свойством в данном случае является
context?.setBlendMode(.clear)
который в данном случае делает заданный путь прозрачным.
Осталось не забыть вызывать strokePath() который сделаем данные точки прозрачными.
Итоговый метод eraseBetween(fromPoint: CGPoint, currentPoint: CGPoint) выглядит следующим образом:
На этом всё, теперь можно запустить приложение и проверить результат!
Автор: Григорян Давид