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

SwiftUI中如何将图片遮罩矩形从黑色半透明改为目标样式?

问题描述

现有SwiftUI代码运行正常,但需要将当前黑色透明度0.3的遮罩矩形替换为下图的样式(中间透明、四周深色遮罩,带白色边框及边角标记)。

原代码如下:

struct CropFrame: Shape {
    let isActive: Bool
    func path(in rect: CGRect) -> Path {
        guard isActive else { return Path(rect) } // full rect for non active

        let size = CGSize(width: UIScreen.screenWidth * 0.7, height: UIScreen.screenHeight/5)
        let origin = CGPoint(x: rect.midX - size.width / 2, y: rect.midY - size.height / 2)
        return Path(CGRect(origin: origin, size: size).integral)
    }
}

struct CropImage: View {

    @State private var currentPosition: CGSize = .zero
    @State private var newPosition: CGSize = .zero
    @State private var clipped = false

    var body: some View {
        VStack {
            ZStack {
                Image("test_pic")
                    .resizable()
                    .scaledToFit()
                    .offset(x: self.currentPosition.width, y: self.currentPosition.height)

                Rectangle()
                    .fill(Color.black.opacity(0.3))
                    .frame(width: UIScreen.screenWidth * 0.7 , height: UIScreen.screenHeight/5)
                    .overlay(Rectangle().stroke(Color.white, lineWidth: 3))
            }
            .clipShape(
                CropFrame(isActive: clipped)
            )
            .gesture(DragGesture()
                .onChanged { value in
                    self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
            }
            .onEnded { value in
                self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)

                self.newPosition = self.currentPosition
            })


            Button (action : { self.clipped.toggle() }) {
                Text("Crop Image")
                    .padding(.all, 10)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .shadow(color: .gray, radius: 1)
                    .padding(.top, 50)
            }
        }
    }
}

目标样式:
目标遮罩样式


解决方案

要实现目标遮罩样式,需修改遮罩逻辑,采用反向遮罩(四周深色填充、中间挖空),同时添加边框和边角标记,具体修改如下:

1. 自定义反向遮罩Shape

替换原CropFrame,创建生成"四周填充、中间挖空"路径的Shape:

struct CropMask: Shape {
    let cropRect: CGRect
    
    func path(in rect: CGRect) -> Path {
        var path = Path(rect) // 覆盖整个屏幕区域
        // 通过evenOdd规则减去中间裁剪区域,实现反向遮罩
        path.addPath(Path(cropRect), windingRule: .evenOdd)
        return path
    }
}

2. 重构CropImage视图

移除原黑色矩形遮罩,改用自定义CropMask实现深色背景,同时添加边框和边角标记:

struct CropImage: View {
    @State private var currentPosition: CGSize = .zero
    @State private var newPosition: CGSize = .zero
    @State private var clipped = false
    
    // 裁剪区域尺寸
    private let cropSize = CGSize(width: UIScreen.main.bounds.width * 0.7, height: UIScreen.main.bounds.height / 5)
    
    var body: some View {
        VStack {
            ZStack {
                // 可拖动的图片
                Image("test_pic")
                    .resizable()
                    .scaledToFit()
                    .offset(x: currentPosition.width, y: currentPosition.height)
                
                if clipped {
                    // 1. 深色遮罩层(反向遮罩)
                    Color.black.opacity(0.7)
                        .mask(CropMask(cropRect: cropRect(in: UIScreen.main.bounds)))
                    
                    // 2. 白色边框
                    Rectangle()
                        .stroke(Color.white, lineWidth: 2)
                        .frame(size: cropSize)
                    
                    // 3. 边角标记
                    CropCorners()
                        .frame(size: cropSize)
                }
            }
            .gesture(DragGesture()
                .onChanged { value in
                    currentPosition = CGSize(width: value.translation.width + newPosition.width, height: value.translation.height + newPosition.height)
                }
                .onEnded { value in
                    newPosition = currentPosition
                })
            
            Button(action: { clipped.toggle() }) {
                Text("Crop Image")
                    .padding(.all, 10)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .shadow(color: .gray, radius: 1)
                    .padding(.top, 50)
            }
        }
    }
    
    // 计算裁剪区域的位置和尺寸
    private func cropRect(in screenRect: CGRect) -> CGRect {
        CGRect(
            x: screenRect.midX - cropSize.width / 2,
            y: screenRect.midY - cropSize.height / 2,
            width: cropSize.width,
            height: cropSize.height
        ).integral
    }
}

// 边角标记视图
struct CropCorners: View {
    var body: some View {
        ZStack {
            // 左上角标记
            Rectangle()
                .fill(Color.white)
                .frame(width: 20, height: 3)
                .offset(x: -cropSize.width/2 + 10, y: -cropSize.height/2 + 1.5)
            Rectangle()
                .fill(Color.white)
                .frame(width: 3, height: 20)
                .offset(x: -cropSize.width/2 + 1.5, y: -cropSize.height/2 + 10)
            
            // 右上角标记
            Rectangle()
                .fill(Color.white)
                .frame(width: 20, height: 3)
                .offset(x: cropSize.width/2 - 10, y: -cropSize.height/2 + 1.5)
            Rectangle()
                .fill(Color.white)
                .frame(width: 3, height: 20)
                .offset(x: cropSize.width/2 - 1.5, y: -cropSize.height/2 + 10)
            
            // 左下角标记
            Rectangle()
                .fill(Color.white)
                .frame(width: 20, height: 3)
                .offset(x: -cropSize.width/2 + 10, y: cropSize.height/2 - 1.5)
            Rectangle()
                .fill(Color.white)
                .frame(width: 3, height: 20)
                .offset(x: -cropSize.width/2 + 1.5, y: cropSize.height/2 - 10)
            
            // 右下角标记
            Rectangle()
                .fill(Color.white)
                .frame(width: 20, height: 3)
                .offset(x: cropSize.width/2 - 10, y: cropSize.height/2 - 1.5)
            Rectangle()
                .fill(Color.white)
                .frame(width: 3, height: 20)
                .offset(x: cropSize.width/2 - 1.5, y: cropSize.height/2 - 10)
        }
    }
    
    private var cropSize: CGSize {
        CGSize(width: UIScreen.main.bounds.width * 0.7, height: UIScreen.main.bounds.height / 5)
    }
}

// 扩展View,方便通过CGSize设置frame
extension View {
    func frame(size: CGSize) -> some View {
        self.frame(width: size.width, height: size.height)
    }
}

3. 代码说明

  • CropMask通过evenOdd缠绕规则实现反向遮罩,让中间裁剪区域透明,四周显示深色背景
  • CropCorners单独实现四个边角的白色标记,配合边框还原目标样式
  • 移除原clipShape逻辑,改为在clipped为true时显示遮罩、边框和边角标记,逻辑更清晰

内容的提问来源于stack exchange,提问作者stefanosn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 10:05:40