Вопрос: Стой, что еще за “дырка” такая?
Ответ: Условимся, что дыркой будем называть часть UIView, которая будет вырезана для того, чтобы можно было смотреть как-бы “cквозь” UIView
На рисунке 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() }}
Отлично! Но по задумке дизайнера нужно двигаться дальше, я посмотрел в фигму и увидел следующий паттерн:
С помошью 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() }}
Получил следующий результат:
Вопрос: Что произошло простыми словами?
Ответ: Мы сделали дырку в whiteView размером с holeImageView, а потом дырку в holeImageView по полученному от дизайнера паттерну
В процессе работы над спринтом естественно всплыло несколько косяков.
Например, если общая сумма высот ячеек меньше высоты экрана без safeArea, то происходило следующее:
Проблема решалась подсчетом высоты контента и
tableView.bounces = false
Вот так, в комбинации элементарных решений удалось реализовать UI, который до этого мной ни разу не встречался в других приложениях
Надеюсь было интересно!)
И я открыт к любым вопросам по этой теме
P.S. У нас в mileonair, в разделе акций, можно увидеть дырку идущую сквозь UIImageView, UITableView и UICollectionView одновременно