How to implement a before-after image slider in SwiftUI? I've searched the entire internet, but the problem remains unsolved. I have an implementation, but it doesn't work correctly. When you start moving the slider, it doesn't align properly with the photo. The second problem is that the mask doesn't work correctly. I have a capsule with text over the photo, and there is a capsule on the 'before' label, but for some reason, it's missing on the 'after' label.
import SwiftUI
struct ContentView: View {
@State private var location: CGPoint = CGPoint(x: 0, y: 0)
@State private var maskWidth: CGFloat = 0.0
@State var startPoint: CGFloat = 0
@State var endPoint: CGFloat = 0
@State var yPoint: CGFloat = 0
var sliderWidth: CGFloat = 30
var containerWidth: CGFloat = 400
var containerHeight: CGFloat = 300
var body: some View {
ZStack {
ZStack {
Image(.boyPic)
.resizable()
.frame(width: containerWidth, height: containerHeight)
.clipped()
.overlay {
ZStack {
Capsule()
.frame(width: 64, height: 30)
.foregroundStyle(Color.black.opacity(0.6))
Text(BeforeAfterType.before.rawValue)
.foregroundStyle(Color.whiteColor)
.fonts(.medium, .smallTitleSize13)
}
.horAlig(.leading)
.verAlig(.top)
.padding(ARSizesType.smallPaddingSize.sizes)
}
Image(.boyPic)
.resizable()
.frame(width: containerWidth, height: containerHeight)
.clipped()
.overlay {
ZStack {
Capsule()
.frame(width: 64, height: 30)
.foregroundStyle(Color.black.opacity(0.6))
Text(BeforeAfterType.after.rawValue)
.foregroundStyle(Color.whiteColor)
.fonts(.medium, .smallTitleSize13)
}
.horAlig(.trailing)
.verAlig(.top)
.padding(ARSizesType.smallPaddingSize.sizes)
}
.mask(mask)
}
Slider
}
.frame(width: containerWidth, height: containerHeight)
.onAppear {
yPoint = containerHeight/2
location = CGPoint(x: containerWidth/2, y: yPoint)
maskWidth = containerWidth/2
endPoint = containerWidth
}
}
var dragAction: some Gesture {
DragGesture()
.onChanged { value in
updateDragView(point: value.location)
updateMaskView(point: value.translation)
}
.onEnded { value in
setInitialPosition()
}
}
var mask: some View {
HStack {
Spacer()
Rectangle()
.mask(Color.black)
.frame(width: maskWidth, height: containerHeight)
}
}
var Slider: some View {
ZStack {
Rectangle()
.fill(Color.white)
.frame(width: 2)
Image(.sliderButton)
.resizable()
.scaledToFill()
.foregroundColor(.white)
.frame(width: 55, height: 35)
.verAlig(.bottom, 50)
}
.position(location)
.gesture(dragAction)
.shadow(radius: 4)
}
func updateDragView(point: CGPoint) {
let locX = point.x
if locX > startPoint && locX < endPoint {
self.location = CGPoint(x: point.x, y: yPoint)
}
}
func updateMaskView(point: CGSize) {
let width = -(point.width)
let newWidth = ((containerWidth/2) + width)
if newWidth > 0 {
maskWidth = ((containerWidth/2) + width)
} else {
setInitialPosition()
}
}
func setInitialPosition() {
withAnimation {
location = CGPoint(x: containerWidth/2, y: yPoint)
maskWidth = containerWidth/2
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Your original code is so long to follow, but here is what you should do:
struct BeforeAfterSlider: View {
@State private var percentage = 0.0
var body: some View {
ZStack { // 👈 Stacks image on top of each other
Image(.before)
Image(.after)
.mask { // 👈 Mask the top one
Rectangle() // 👈 Use the desired shape for the mask
.scaleEffect(
CGSize(
width: percentage, // 👈 Control the width of the mask
height: 1.0
),
anchor: .leading
)
}
}
.overlay(alignment: .bottom) {
Slider(value: $percentage, in: 0...1) // 👈 Connect the mask to your slider
}
}
}