Разработка
Обратное маскирование в SwiftUI с использованием режимов наложения
Если вы уже создаете современные интерфейсы SwiftUI с материалами и глубиной, этот метод стоит иметь в своем арсенале.
SwiftUI предоставляет нам функцию mask(_:), которая хорошо работает, когда нужно отобразить часть представления.
Однако она не предоставляет нам инверсную версию — способ вырезать фигуры из представления и позволить всему, что находится за ними, отображаться.
Давайте рассмотрим чистый и практичный способ создания обратной маски в SwiftUI и разберемся, как это работает.
Желаемый эффект
Представьте себе карточку, залитую цветом, на поверхности которой вырезана фигура.
Карточка видима везде, кроме области, где расположена фигура. Эта область становится прозрачной.
В UIKit вы можете использовать Core Graphics или маски слоев.
В SwiftUI решение заключается в режимах наложения.
Простой модификатор обратной маски
Вот модификатор, который я использую:
extension View {
func reversedMask<M: View>(
alignment: Alignment = .center,
@ViewBuilder mask: () -> M
) -> some View {
overlay(alignment: alignment) {
mask().blendMode(.destinationOut)
}
.compositingGroup()
}
}
Это коротко, но под капотом происходит много всего.
Использование в реальном представлении
В моей демонстрационной сетке каждая ячейка представляет собой закругленный прямоугольник с вырезанным символом:
RoundedRectangle(cornerRadius: 30)
.fill(color)
.reversedMask {
Image(systemName: "heart.fill")
.resizable()
.scaledToFit()
.padding()
}
При изменении символа из карточки вырезается новая фигура. Через отверстие становится виден фоновый градиент.
Этот модификатор становится ещё интереснее, когда вы начинаете накладывать несколько поверхностей друг на друга.
Вот макет в стиле баннера, созданный с помощью ZStack:
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(.ultraThinMaterial)
RoundedRectangle(cornerRadius: 30)
.fill(.purple)
.reversedMask {
RoundedRectangle(cornerRadius: 30)
.blur(radius: 30)
}
}
.frame(maxWidth: .infinity)
.frame(height: 200)
Нижний слой использует ultraThinMaterial, который создает полупрозрачную, похожую на стекло поверхность. Поверх него расположен сплошной синий прямоугольник со скругленными углами. Этот синий слой затем вырезает размытую версию той же скругленной формы.
Поскольку маска размыта, края выреза мягкие. Вместо жесткого отверстия материал под ней плавно просвечивает.
Этот шаблон хорошо подходит для баннеров, заголовков, плавающих карточек и интерактивных элементов управления, где вы хотите, чтобы фон незаметно просвечивал сквозь поверхность.
Как это работает
Чтобы понять это, полезно знать, как режимы наложения работают с отрисовкой.
Когда SwiftUI отрисовывает иерархию представлений, каждый слой отрисовывается по порядку:
- существующее содержимое называется целью
- наложенное поверх него пространство называется исходником
BlendMode.destinationOut сохраняет целевой слой только там, где исходный слой прозрачен.
Это режим, который используется для удаления любого фона, покрытого непрозрачными пикселями исходного слоя.
Если исходник непрозрачен, то цель удаляется.
На практике:
- ваш закругленный прямоугольник — цель
- символ — это исходник
- символ пробивает отверстие в прямоугольнике
В этом и заключается весь трюк.
Почему необходима compositingGroup()
Без compositingGroup() SwiftUI часто применяет режим наложения для каждого слоя отдельно, а не для всей поверхности целиком. Результат может варьироваться от полного отсутствия изменений до частично нарушенной прозрачности.
Функция compositingGroup() заставляет SwiftUI:
- Рендерить представление и его наложение в одном буфере за пределами экрана
- Применять режим наложения внутри этого буфера
Как только все элементы оказываются в одном контексте композиции, вырезка ведет себя согласованно.
Заключение
Конвейер рендеринга SwiftUI выглядит высокоуровневым, но как только вы начинаете использовать режимы наложения и композицию, он становится удивительно выразительным.
Эта обратная маска — хороший тому пример.
Если вы уже создаете современные интерфейсы SwiftUI с материалами и глубиной, этот метод стоит иметь в своем арсенале.
-
Вовлечение пользователей2 недели назад
Большинство приложений терпят неудачу не из-за плохой «идеи»
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2026.3
-
Новости2 недели назад
Видео и подкасты о мобильной разработке 2026.4
-
Видео и подкасты для разработчиков2 недели назад
Изоляционно-плагинная архитектура в Dart-приложениях, переносимость на Flutter
