我正在做一个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
}
}
}
1条答案
按热度按时间epggiuax1#
应用不同变换的顺序很重要。我建议,您将其设置为缩放,然后是旋转,然后是偏移。这样,如果堆栈的任何部分应该从缩放中排除(例如按钮的覆盖),那么这些可以在应用缩放修改器后添加到视图中。尤其重要的是,在旋转之后应用偏移。如果偏移在旋转之前,则它会移动旋转中心,并且图像将不再围绕其中点旋转。
我会期望,变换应该只对签名覆盖,而不是对底层图像。在上面的代码中,我认为你也在对底层图像进行一些转换。
缩放和旋转可以累积应用,将上一次调整的结束状态用作下一次调整的开始状态。然而,缩放可能不应累积应用。
我的理解是,用户应该能够通过移动底部角落来应用缩放。然后,拖动位置可以被视为新的角位置,保持中点不变。因此:
scalingFactor = /
如果还涉及偏移和旋转,则在确定中点和拐角位置时需要考虑这些。
此公式提供的比例因子应作为绝对值应用,而不是通过应用于现有比例因子进行复合。您可能还希望强制执行最小和最大缩放因子,以便图像不会缩小到可笑的小尺寸或膨胀过大。
要查看所有这些工作,请尝试我在answer to your other post中给出的示例。下面是执行缩放的答案中的函数: