clipped() не оказывает влияния на проверку касаний
Модификатор clipped() в SwiftUI обрезает вью до своих(модификатора) границ, скрывая все, что выходит за пределы этих границ. Но обратите внимание, что обрезание не влияет на проверку касаний (hit testing); обрезанный вью по-прежнему принимает тапы / клики за пределами видимой области.
Протестировано на iOS 16.1 и macOS 13.0.
Пример
У нас есть квадрат 300×300, который затем мы ограничиваем кадром 100×100. Также добавим границу вокруг внешней рамки, чтобы визуализировать вью:
Rectangle()
.fill(.orange.gradient)
.frame(width: 300, height: 300)
// Set view to 100×100 → renders out of bounds
.frame(width: 100, height: 100)
.border(.blue)
Вьюшки в SwiftUI по умолчанию не обрезают свой контент, поэтому весь квадрат размером 300×300 остается видимым. Обратите внимание на синюю рамку, обозначающую границы кадра 100×100:
Теперь давайте добавим .clipped(), чтобы обрезать большой квадрат до кадра 100×100. Я также сделал квадрат кликабельным и добавил кнопку:
VStack {
Button("You can't tap me!") {
buttonTapCount += 1
}
.buttonStyle(.borderedProminent)
Rectangle()
.fill(.orange.gradient)
.frame(width: 300, height: 300)
.frame(width: 100, height: 100)
.clipped()
.onTapGesture {
rectTapCount += 1
}
}
Когда вы запустите этот код, вы обнаружите, что кнопка не нажимается совсем. Это связано с тем, что (необрезанный) квадрат, несмотря на то, что он виден не полностью, закрывает(затеняет) эту кнопку и «крадет» все нажатия.
Пунктирная линия обозначает область нажатия для оранжевого квадрата. Кнопку нельзя нажать, т.к. она закрыта обрезанным вью относительно к hit testing.
Решение: .contentShape()
Модификатор contentShape(_:) определяет область проверки касания для вью. Добавляя .contentShape(Rectangle()) к кадру 100×100, мы ограничиваем проверку касания этой областью, делая кнопку снова доступной для нажатия:
Rectangle()
.fill(.orange.gradient)
.frame(width: 300, height: 300)
.frame(width: 100, height: 100)
.contentShape(Rectangle())
.clipped()
Обратите внимание, что порядок .contentShape(Rectangle()) и .clipped() можно поменять местами. Важно то, что contentShape является (косвенным) родителем модификатора кадра 100×100, который определяет размер области проверки касания.
Видео
Я сделал короткое видео, демонстрирующее эффект:
- Первоначально нажатия на кнопку или даже на окружающее пустое пространство регистрируются как нажатия на квадрат.
- Верхний переключатель дает нам увидеть квадрат перед обрезкой. Тем самым показывая нам его область проверки касаний.
- Второй переключатель добавляет .contentShape(Rectangle()) , ограничивая проверку попаданий видимой(оранжевой) областью. Теперь нажатие кнопки увеличивает счет нажатий на нее.
Полный код доступен на GitHub.
https://oleb.net/media/2022-11-24-clipped-hit-testing-demo.mp4
Итог
Модификатор clipped() не влияет на область проверки касания для обрезанного вью. То же самое верно и для clipShape(_:). Часто рекомендуется комбинировать эти модификаторы с .contentShape(Rectangle()), чтобы синхронизировать логику проверки касания с UI.