You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中前后对比图片滑块的实现问题排查

SwiftUI 前后对比图片滑块修复方案

问题分析

  • 滑块对齐异常:原代码使用DragGesture的translation计算蒙版宽度,translation是相对偏移量而非绝对位置,导致滑块位置与蒙版区域不匹配。
  • After标签胶囊缺失:After图片的overlay在mask之前添加,蒙版会裁剪整个视图(包括叠加层),右侧胶囊被蒙版切掉。

修复后代码

import SwiftUI

enum BeforeAfterType: String {
    case before = "Before"
    case after = "After"
}

// 补全项目中缺失的自定义扩展(若已有可忽略)
extension Color {
    static let whiteColor = Color.white
}

extension View {
    func horAlig(_ alignment: Alignment, _ offset: CGFloat = 0) -> some View {
        self.frame(maxWidth: .infinity, alignment: alignment)
            .offset(x: offset)
    }
    
    func verAlig(_ alignment: Alignment, _ offset: CGFloat = 0) -> some View {
        self.frame(maxHeight: .infinity, alignment: alignment)
            .offset(y: offset)
    }
    
    func fonts(_ weight: Font.Weight, _ size: CGFloat) -> some View {
        self.font(.system(size: size, weight: weight))
    }
}

enum ARSizesType: CGFloat {
    case smallPaddingSize = 16
    var sizes: CGFloat { self.rawValue }
}

struct ContentView: View {
    // 仅保留核心状态:滑块X轴位置
    @State private var sliderX: CGFloat = 0
    
    let containerWidth: CGFloat = 400
    let containerHeight: CGFloat = 300
    
    var body: some View {
        ZStack(alignment: .center) {
            // Before图片(底层)
            Image("boyPic")
                .resizable()
                .scaledToFill()
                .frame(width: containerWidth, height: containerHeight)
                .clipped()
                .overlay(alignment: .topLeading) {
                    captionView(type: .before)
                        .padding(ARSizesType.smallPaddingSize.sizes)
                }
            
            // After图片(上层,带蒙版)
            Image("boyPicAfter") // 替换为实际的After图片
                .resizable()
                .scaledToFill()
                .frame(width: containerWidth, height: containerHeight)
                .clipped()
                .mask(
                    HStack(spacing: 0) {
                        Spacer()
                        Rectangle()
                            .frame(width: containerWidth - sliderX, height: containerHeight)
                    }
                )
                // 将overlay移到mask之后,避免被蒙版裁剪
                .overlay(alignment: .topTrailing) {
                    captionView(type: .after)
                        .padding(ARSizesType.smallPaddingSize.sizes)
                }
            
            // 滑块视图
            sliderView
        }
        .frame(width: containerWidth, height: containerHeight)
        .onAppear {
            // 初始位置居中
            sliderX = containerWidth / 2
        }
    }
    
    // 复用的标题胶囊视图
    private func captionView(type: BeforeAfterType) -> some View {
        ZStack {
            Capsule()
                .frame(width: 64, height: 30)
                .foregroundStyle(Color.black.opacity(0.6))
            
            Text(type.rawValue)
                .foregroundStyle(Color.whiteColor)
                .fonts(.medium, 13)
        }
    }
    
    // 滑块组件
    private var sliderView: some View {
        ZStack {
            // 垂直分隔线
            Rectangle()
                .fill(Color.white)
                .frame(width: 2, height: containerHeight)
            
            // 滑块按钮(替换为你的自定义图片)
            Image(systemName: "line.horizontal.3")
                .resizable()
                .scaledToFit()
                .foregroundColor(.white)
                .frame(width: 55, height: 35)
                .shadow(radius: 4)
        }
        .position(x: sliderX, y: containerHeight / 2)
        .gesture(
            DragGesture()
                .onChanged { value in
                    // 限制滑块在容器范围内移动
                    sliderX = max(0, min(value.location.x, containerWidth))
                }
        )
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

关键修复点

  1. 简化状态管理:移除冗余状态变量,仅用sliderX同步滑块位置与蒙版宽度,逻辑更清晰。
  2. 修正蒙版计算:直接通过containerWidth - sliderX计算蒙版显示区域,确保滑块位置与蒙版边缘完全对齐。
  3. 调整层级顺序:将After图片的overlay移到mask之后,使标签胶囊不受蒙版裁剪,正常显示在右上角。
  4. 优化拖动逻辑:在拖动时直接限制sliderX的取值范围,避免滑块超出图片边界。
  5. 代码复用:提取captionView函数,消除重复代码,便于后续维护。

注意事项

  • 请将Image("boyPic")和Image("boyPicAfter")替换为项目中实际的前后对比图片。
  • 若项目中已有自定义布局扩展,可直接替换代码中补全的扩展部分。

内容的提问来源于stack exchange,提问作者Олександр Романюк

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 10:34:58