Новый язык дизайна Apple представил эффект светящейся анимированной обводки, которая изящно и динамично подсвечивает формы и компоненты. Давайте рассмотрим, как воссоздать этот эффект в SwiftUI с помощью многоразовых расширений.
Расширение View с помощью фона и наложения
Первый шаг — сделать эффект свечения доступным для применения в любом месте. Предусмотрены два модификатора: один для фона и один для оверлея.
extension View {
@MainActor
func intelligenceBackground<S: InsettableShape>(
in shape: S
) -> some View {
background(
shape.intelligenceStroke()
)
}
@MainActor
func intelligenceOverlay<S: InsettableShape>(
in shape: S
) -> some View {
overlay(
shape.intelligenceStroke()
)
}
}
Обе опции используют один и тот же базовый механизм отрисовки обводки, но обеспечивают гибкость в зависимости от макета.
Добавление светящейся обводки к фигурам
Основная работа выполняется в кастомном методе InsettableShape. Это позволяет отображать любую фигуру, например, Capsule, Rectangle или Circle, со светящейся анимированной рамкой.
extension InsettableShape {
@MainActor
func intelligenceStroke(
lineWidths: [CGFloat] = [6, 9, 11, 15],
blurs: [CGFloat] = [0, 4, 12, 15],
updateInterval: TimeInterval = 0.4,
animationDurations: [TimeInterval] = [0.5, 0.6, 0.8, 1.0],
gradientGenerator: @MainActor @Sendable @escaping () -> [Gradient.Stop] = { .intelligenceStyle }
) -> some View {
IntelligenceStrokeView(
shape: self,
lineWidths: lineWidths,
blurs: blurs,
updateInterval: updateInterval,
animationDurations: animationDurations,
gradientGenerator: gradientGenerator
)
.allowsHitTesting(false)
}
}
Этот метод помещает фактическую логику рендеринга в отдельное представление.
Рендеринг слоёв обводки
IntelligenceStrokeView применяет несколько слоёв градиентной обводки, каждый со своей собственной толщиной линии, размытием и временем анимации. Вместе они создают мягкий светящийся ореол, который анимируется с течением времени.
private struct IntelligenceStrokeView<S: InsettableShape>: View {
let shape: S
let lineWidths: [CGFloat]
let blurs: [CGFloat]
let updateInterval: TimeInterval
let animationDurations: [TimeInterval]
let gradientGenerator: @MainActor @Sendable () -> [Gradient.Stop]
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var stops: [Gradient.Stop] = .intelligenceStyle
var body: some View {
let layerCount = min(lineWidths.count, blurs.count, animationDurations.count)
let gradient = AngularGradient(
gradient: Gradient(stops: stops),
center: .center
)
ZStack {
ForEach(0..<layerCount, id: \.self) { i in
shape
.strokeBorder(gradient, lineWidth: lineWidths[i])
.blur(radius: blurs[i])
.animation(
reduceMotion ? .linear(duration: 0) : easeInOut(duration: animationDurations[i]),
value: stops
)
}
}
.task(id: updateInterval) {
while !Task.isCancelled {
stops = gradientGenerator()
try? await Task.sleep(for: .seconds(updateInterval))
}
}
}
}
Каждая итерация цикла рисует один слой свечения, размытый по-разному для придания глубины. Ограничители градиента периодически обновляются, создавая анимированный эффект «перетекания».
Палитра градиентов
Эффект свечения основан на палитре мягких, но ярких цветов. Их расположение слегка хаотично, чтобы анимация выглядела естественно.
private extension Array where Element == Gradient.Stop {
static var intelligenceStyle: [Gradient.Stop] {
[
Color(red: 188/255, green: 130/255, blue: 243/255),
Color(red: 245/255, green: 185/255, blue: 234/255),
Color(red: 141/255, green: 159/255, blue: 255/255),
Color(red: 255/255, green: 103/255, blue: 120/255),
Color(red: 255/255, green: 186/255, blue: 113/255),
Color(red: 198/255, green: 134/255, blue: 255/255)
]
.map { Gradient.Stop(color: $0, location: Double.random(in: 0...1)) }
.sorted { $0.location < $1.location }
}
}
Пример использования
Добавить свечение просто:
VStack(spacing: 30) {
Text("Some text here")
.padding(22)
.intelligenceBackground(in: .capsule)
Text("Some text here")
.padding(22)
.intelligenceOverlay(in: .rect(cornerRadius: 22))
}
В первом примере свечение располагается за капсулой, а во втором — отображается как наложение поверх прямоугольника.
Заключение
Эта реализация показывает, как объединить несколько обводок, размытий и анимированных градиентов для достижения эффекта свечения, аналогичного интерфейсу Apple Intelligence. Результат работает с любым объектом InsettableShape. Его можно использовать для современной и выразительной подсветки кнопок, карточек или текстовых контейнеров.
Пакет Swift на GitHub: https://github.com/Livsy90/IntelligenceGlow/tree/main

