Алексей Михайлов в свое блоге рассказал о том, как из одного UIView увидеть другой.
Вопрос: Что еще за “дырка” такая?
Ответ: Условимся, что дыркой будем называть часть UIView, которая будет вырезана для того, чтобы можно было смотреть как бы “сквозь” 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 одновременно