Программирование
“Дырявим” вьюхи на Swift
Алексей Михайлов в свое блоге рассказал о том, как из одного UIView увидеть другой.
Вопрос: Что еще за “дырка” такая?
Ответ: Условимся, что дыркой будем называть часть UIView, которая будет вырезана для того, чтобы можно было смотреть как бы “сквозь” UIView.

Рис 1. Дырка
На рисунке 1 представлена иерархия дырки. Cлева направо:
- несколько системных окон UIWindowScene;
- gradientView, градиентная UIView;
- holeView, прозрачная UIView, которая будет задавать размер дырки;
- whiteView, белая UIView с дыркой.
Как такое сделать?
Засунуть в viewDidLoad() функцию makeHole():
func makeHole() {
// Берем путь у whiteView
let path = UIBezierPath(rect: whiteView.bounds)
// Создаем путь у holeView и скругляем его
let pathWithRadius = UIBezierPath(
roundedRect: holeView.frame,
byRoundingCorners: [.allCorners],
cornerRadii: holeView.frame.size)
// Создаем общую геометрию пути
path.append(pathWithRadius)
// Создаем маску этой геометрии и применяем правило заливки evenOdd, которое как раз и делает дырку
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.fillRule = CAShapeLayerFillRule.evenOdd
// Применяем полученную маску к whiteView
whiteView.layer.mask = mask
}
Для тех, кто плохо воспринимает код по снипетам, сразу прикладываю исходный код проекта.
Теперь для создания дырявой UITableView надо создать HoleTableViewCell, которая будет повторять иерархию с первого рисунка и в реализации содержать аналогичную функцию makeHole(). Вызывать ее будем в методе делегата tableView(_:willDisplay:forRowAt:):
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
if let holeCell = cell as? HoleTableViewCell {
holeCell.layoutIfNeeded()
holeCell.makeHole()
}
}

Рис 2. Дырявый UITableView
Отлично! Но по задумке дизайнера нужно двигаться дальше, я посмотрел в фигму и увидел следующий паттерн:

Рис 3. Паттерн выреза
С помощью extension к UIImageView реализуется логическое вычитание XOR. Оно делает дырку в image по заданному паттерну:
extension UIImageView {
func applyGradientPattern(_ pattern: UIImage) {
let size = frame.size
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
UIGraphicsBeginImageContext(size)
self.image!.draw(in: rect)
pattern.draw(in: rect, blendMode: .destinationOut, alpha: 1.0)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.image = newImage
}
}
И вызвав эту функцию в методе делегата tableView(_:willDisplay:forRowAt:):
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
if let gradientHoleCell = cell as? GradientHoleTableViewCell {
gradientHoleCell.layoutIfNeeded()
gradientHoleCell.applyGradientPattern()
gradientHoleCell.makeHole()
}
}
Получил следующий результат:

Рис 4. Градиентно дырявый UITableView
Что произошло простыми словами?
Мы сделали дырку в whiteView размером с holeImageView, а потом дырку в holeImageView по полученному от дизайнера паттерну.
В процессе работы над спринтом естественно всплыло несколько косяков.
Например, если общая сумма высот ячеек меньше высоты экрана без safeArea, то происходило следующее:

Рис 5. Маленькое количество ячеек
Проблема решалась подсчетом высоты контента и
tableView.bounces = false
Вот так, в комбинации элементарных решений удалось реализовать UI, который до этого мной ни разу не встречался в других приложениях
Надеюсь было интересно! )
Тут можно глянуть код проекта.
И я открыт к любым вопросам по этой теме.
P.S. У нас в mileonair, в разделе акций, можно увидеть дырку идущую сквозь UIImageView, UITableView и UICollectionView одновременно
-
Аналитика магазинов3 недели назад
Мобильный рынок Ближнего Востока: исследование Bidease и Sensor Tower выявляет драйверы роста
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.46
-
Видео и подкасты для разработчиков3 недели назад
Разбор кода: iOS-приложение для управления личными финансами на Swift. Часть 1
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.47

