带有签名的图像未按预期定位

问题描述 投票:0回答:1

我正在开发一个 SwiftUI 项目,我想将签名图像叠加在另一个图像之上,并允许用户操纵签名图像的位置、比例和旋转。但是,我在签名图像的定位方面遇到问题,并且它没有出现在我期望的位置。

我尝试设置用于拖动、缩放和旋转签名图像的手势,但它的行为不符合预期。签名图像似乎偏移(将其保存在左上角)并且缩放不正确(无论缩放如何,它总是很小)。

有人可以帮我确定是什么导致了签名图像的定位和缩放问题吗?

相关代码如下:

struct SignatureAddingView: View {
    
    @State var scannedImage: UIImage = UIImage()
    
    @State private var scale: CGFloat = 1
    @State private var scaleAnchor: UnitPoint = .center
    @State private var lastScale: CGFloat = 1
    @State private var offset: CGSize = .zero
    @State private var lastOffset: CGSize = .zero
    @State private var debug = ""
        
    @State private var location: CGPoint = CGPoint(x: 100, y: 100)
    @GestureState private var fingerLocation: CGPoint? = nil
    @GestureState private var startLocation: CGPoint? = nil
    
    @State private var scaling: CGFloat = 1.0 // Add a state variable for scaling
    @State private var rotationAngle: Double = 0.0 // Add a state variable for rotation
        
    var simpleDrag: some Gesture {
        DragGesture()
            .onChanged { value in
                var newLocation = startLocation ?? location // 3
                newLocation.x += value.translation.width
                newLocation.y += value.translation.height
                self.location = newLocation
            }.updating($startLocation) { (value, startLocation, transaction) in
                startLocation = startLocation ?? location // 2
            }
    }
    
    var fingerDrag: some Gesture {
        DragGesture()
            .updating($fingerLocation) { (value, fingerLocation, transaction) in
                fingerLocation = value.location
            }
    }
    
    var body: some View {
        VStack {
            GeometryReader { geometry in
                let magnificationGesture = MagnificationGesture()
                    .onChanged{ gesture in
                        scaleAnchor = .center
                        scale = lastScale * gesture
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                
                let dragGesture = DragGesture()
                    .onChanged { gesture in
                        var newOffset = lastOffset
                        newOffset.width += gesture.translation.width
                        newOffset.height += gesture.translation.height
                        offset = newOffset
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                
                ZStack {
                    
                    Image(uiImage: scannedImage)
                        .resizable()
                        .scaledToFit()
                        .position(x: geometry.size.width / 2,
                                  y: geometry.size.height / 2)
                        .scaleEffect(scale, anchor: scaleAnchor)
                        .offset(offset)
                        .gesture(dragGesture)
                        .gesture(magnificationGesture)
                    
                    ZStack {
                        
                        if let image = loadImageFromDocumentDirectory(filename: "signature.png") {
                            
                            ZStack {
                                
                                Rectangle()
                                    .stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
                                    .fill(.blue)
                                
                                Image(uiImage: image)
                                    .resizable()
                                    .scaledToFit()
                                
                            }
                                VStack {
                                    Spacer()
                                    HStack {
                                        
                                        Spacer()
                                        
                                        Circle()
                                            .fill(Color.green) // Change the circle color as needed
                                            .frame(width: 20, height: 20) // Adjust the size of the circle as needed
                                    }
                                    .padding(.trailing, -18)
                                    .padding(.bottom, -13)
                                }
                            )
                            .position(location)
                            .gesture(
                                simpleDrag.simultaneously(with: fingerDrag)
                            )
                            
                        }
                            
                        
                    }
                    .frame(width: 100, height: 100)
                    .rotationEffect(.degrees(Double(rotationAngle)), anchor: .center)
                    .scaleEffect(scaling)
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
            }

        }
        .background(Color.black.opacity(0.3))
        .overlay(
            VStack {
                Spacer()
                Button {
                    saveImageWithSignatures()
                } label: {
                    Text("Save")
                }
            }
        )
        .edgesIgnoringSafeArea(.all)
    }
    
    // Function to combine the original image with the signature overlay
    func combineImages() -> UIImage? {
        // Create a UIGraphicsImageRenderer to draw the combined image
        let renderer = UIGraphicsImageRenderer(size: scannedImage.size)

        let combinedImage = renderer.image { ctx in
            // Draw the original image
            scannedImage.draw(in: CGRect(origin: .zero, size: scannedImage.size))

            // Calculate the position and size of the signature overlay
            let signatureRect = CGRect(x: location.x, y: location.y, width: 100, height: 100)

            // Draw the signature overlay
            if let signatureImage = loadImageFromDocumentDirectory(filename: "signature.png") {
                signatureImage.draw(in: signatureRect)
            }
        }

        return combinedImage
    }

    // Function to save the modified image with the signature overlay
    func saveImageWithSignatures() {
        // Combine the images
        if let combinedImage = combineImages() {
            // Save the combined image to the document directory
            saveImageToDocumentDirectory(image: combinedImage, filename: "modified_img.jpeg")
        }
    }
    
    // Function to save an image to the document directory
    func saveImageToDocumentDirectory(image: UIImage, filename: String) {
        if let data = image.jpegData(compressionQuality: 1.0) {
            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
            let fileURL = documentsDirectory.appendingPathComponent(filename)
            do {
                try data.write(to: fileURL)
                print("Image saved to document directory: \(fileURL)")
            } catch {
                print("Error saving image: \(error)")
            }
        }
    }
    
    func loadImageFromDocumentDirectory(filename: String) -> UIImage? {
        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let fileURL = documentsDirectory.appendingPathComponent(filename)
        
        do {
            let imageData = try Data(contentsOf: fileURL)
            return UIImage(data: imageData)
        } catch {
            print("Error loading image: \(error)")
            return nil
        }
    }
    
// NOTE: - My image displaying logic
    private func fixOffsetAndScale(geometry: GeometryProxy) {
        let newScale: CGFloat = .minimum(.maximum(scale, 1), 4)
        let screenSize = geometry.size
        
        let originalScale = scannedImage.size.width / scannedImage.size.height >= screenSize.width / screenSize.height ?
            geometry.size.width / scannedImage.size.width :
            geometry.size.height / scannedImage.size.height
        
        let imageWidth = (scannedImage.size.width * originalScale) * newScale
        
        var width: CGFloat = .zero
        if imageWidth > screenSize.width {
            let widthLimit: CGFloat = imageWidth > screenSize.width ?
                (imageWidth - screenSize.width) / 2
                : 0

            width = offset.width > 0 ?
                .minimum(widthLimit, offset.width) :
                .maximum(-widthLimit, offset.width)
        }
        
        let imageHeight = (scannedImage.size.height * originalScale) * newScale
        var height: CGFloat = .zero
        if imageHeight > screenSize.height {
            let heightLimit: CGFloat = imageHeight > screenSize.height ?
                (imageHeight - screenSize.height) / 2
                : 0

            height = offset.height > 0 ?
                .minimum(heightLimit, offset.height) :
                .maximum(-heightLimit, offset.height)
        }
        
        let newOffset = CGSize(width: width, height: height)
        lastScale = newScale
        lastOffset = newOffset
        withAnimation() {
            offset = newOffset
            scale = newScale
        }
    }
}


swift swiftui draw
1个回答
0
投票

应用不同转换的顺序很重要。我建议,您先进行缩放,然后进行旋转,然后进行偏移。这样,如果堆栈的任何部分应从缩放中排除(例如按钮的覆盖层),则可以在应用缩放修改器后将它们添加到视图中。尤其重要的是,在旋转之后应用偏移。如果偏移发生在旋转之前,那么它会移动旋转中心,并且图像将不再绕其中点旋转。

我本来期望,转换应该只对签名覆盖进行,而不是对底层图像进行。在上面的代码中,我认为您也在对底层图像应用一些转换。

缩放和旋转可以累积应用,使用上一次调整的结束状态作为下一次调整的开始状态。然而,缩放可能不应该累积应用。

我的理解是用户应该能够通过移动底角来应用缩放。然后可以将拖动位置视为新的角位置,保持中点不变。因此:

缩放因子 = /

如果还涉及偏移和旋转,在确定中点和角点位置时需要考虑这些。

此公式为您提供的缩放因子应作为绝对值应用,而不是通过应用于现有缩放因子进行复合。您可能还想强制执行最小和最大缩放因子,以便图像不会缩小到小得离谱或膨胀得太大。

要查看所有这些工作,请尝试我在对您其他帖子的回答中给出的示例。这是该答案中执行缩放的函数:

private func performScale(dragLocation: CGPoint) {
    let midPoint = midPoint
    let dX = dragLocation.x - midPoint.x
    let dY = dragLocation.y - midPoint.y
    let draggedDiagonalLength = 2 * ((dX * dX) + (dY * dY)).squareRoot()
    let unscaledDiagonalLength = (
            (defaultSignatureWidth * defaultSignatureWidth) +
            (defaultSignatureHeight * defaultSignatureHeight)
        ).squareRoot()
    let draggedScaleFactor = draggedDiagonalLength / unscaledDiagonalLength
    scaleFactor = min(1.5, max(0.5, draggedScaleFactor))
}
© www.soinside.com 2019 - 2024. All rights reserved.