Когда вам нужно визуально сравнить два изображения, экрана или состояния, слайдер «до и после» часто оказывается наиболее интуитивно понятным решением. В этой статье мы рассмотрим, как создать многоразовое представление сравнения со слайдером полностью на SwiftUI — без использования UIKit или внешних библиотек.
Обзор концепции
Основная идея заключается в наложении двух представлений друг на друга и использовании маски для отображения только части верхнего слоя. Перетаскиваемый разделитель определяет, какая часть верхнего представления останется видимой.
Компоненты такого типа часто используются для:
- Сравнения отредактированных и исходных фотографий
- Демонстрации изменений в интерфейсе
- Визуализации изменений между состояниями (светлая/тёмная темы, анимация и т. д.)
Структура представления
Мы определим универсальный контейнер SliderComparisonView<Left: View, Right: View>, который принимает два представления SwiftUI — по одному для каждого слоя.
public var body: some View {
GeometryReader { geometry in
ZStack {
Color.clear
.overlay {
lhs()
}
Color.clear
.overlay {
rhs()
}
.mask {
Rectangle()
.offset(x: dividerLocation + geometry.size.width / 2)
}
dividerView()
.offset(x: dividerLocation)
}
.gesture(
DragGesture()
.onChanged { gesture in
dividerLocation = min(
max(gesture.location.x - geometry.size.width / 2, -geometry.size.width / 2),
geometry.size.width / 2
)
}
)
}
.ignoresSafeArea()
}
Основная идея проста:
- Объедините оба представления в ZStack
- Замаскировать верхний (справа) слой прямоугольником
- Отрегулировать маску и положение разделителя в зависимости от движения перетаскивания
Маскирование правого представления
Маска определяет, какая часть верхнего слоя будет видна.
Color.clear
.overlay {
rhs()
}
.mask {
Rectangle()
.offset(x: dividerLocation + geometry.size.width / 2)
}
Вот что происходит:
Rectangle()действует как «окно», через которое виден верхний вид- Горизонтальное перемещение маски
(offset(x:)) скрывает или открывает часть правого вида - Смещение вычисляется относительно центра контейнера, поэтому
dividerLocationможет быть положительным или отрицательным
Создание разделителя
Сам разделитель является визуальным и интерактивным. Он указывает место разделения и предоставляет возможность перетаскивания.
private func dividerView() -> some View {
Rectangle()
.fill(dividerColor)
.frame(width: dividerWidth)
.overlay {
Circle()
.fill(indicatorColor)
.frame(width: indicatorWidth)
.overlay {
indicatorImage
.resizable()
.scaledToFit()
.frame(width: indicatorImageWidth)
.foregroundColor(indicatorImageColor)
}
}
Этот небольшой круг со стрелкой направления даёт пользователям чёткое указание на взаимодействие.
Обработка жестов
Логика жестов обеспечивает плавное горизонтальное перетаскивание в пределах области просмотра:
.onChanged { gesture in
dividerLocation = min(
max(gesture.location.x - geometry.size.width / 2, -geometry.size.width / 2),
geometry.size.width / 2
)
}
Это фиксирует положение разделителя по ширине представления, предотвращая его выход за пределы видимой области.
Пример использования
Вы можете использовать представление сравнения с любым содержимым SwiftUI, а не только с изображениями:
SliderComparisonView {
Image(.winter)
} rhs: {
Image(.spring)
}
SliderComparisonView(
lhs: { Text("Some Text").font(.title).fontWeight(.heavy).foregroundColor(.gray) },
rhs: { Text("Some Text").font(.title).fontWeight(.heavy).foregroundColor(.blue) }
)
Заключение
Инкапсулировав логику в повторно используемый компонент SliderComparisonView, вы можете добавить этот компонент в любой проект SwiftUI — будь то для визуального сравнения, демонстрации переходов или креативного взаимодействия с пользовательским интерфейсом.
Проект на GitHub: https://github.com/Livsy90/LivsyComparisonSliderView/

