如何平滑地将旋转的矩形拖到Swiftui的固定框架内? 我正在从事一个Swiftui项目: 固定框架(黄色矩形) 可拖动且可旋转的图像(蓝色矩形),应始终在拖动时完全覆盖固定框架。 cu ...

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

可拖动和可旋转的图像(蓝色矩形),该图像应始终在拖动时完全覆盖固定框架。

    电流行为
  1. 旋转使用滑块正常工作。

旋转时始终覆盖框架的图像大小。
    dragging起作用,但旋转时感觉却很瘦。
  1. 有时,由于旋转约束而突然停止了运动。

    旋转时,矩形并不总是在各个方向上平稳移动。
  2. goal
  3. 我也想要平滑的拖动行为,即使图像矩形旋转,请确保:

      图像矩形始终停留在固定框架内。
    • 运动仍然是自然而连续的(不卡住或抖动)。
    • 图像的边缘始终完全覆盖固定框架。
    • 电流代码
    我的Swiftui实施是:
struct RotatingDraggingRectView: View { @State private var rectData: RectData = RectData( frameSize: CGSize(width: 100, height: 160), framePosition: CGPoint(x: 200, y: 200), imageSize: CGSize(width: 200, height: 150), ImageRotation: .zero, ImagePosition: CGPoint(x: 200, y: 300) ) @State private var lastDragOffset: CGSize = .zero var body: some View { VStack { GeometryReader { geometry in ZStack { Rectangle() .fill(Color.yellow) .frame(width: rectData.frameSize.width, height: rectData.frameSize.height) .position(rectData.framePosition) Rectangle() .fill(Color.blue.opacity(0.5)) .frame(width: rectData.imageSize.width, height: rectData.imageSize.height) .rotationEffect(rectData.ImageRotation) .position(rectData.ImagePosition) } .background(.red) .frame(width: geometry.size.width,height: geometry.size.width) .position(CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)) } .gesture(dragGesture()) // Rotation Slider Slider(value: $rectData.ImageRotation.degrees, in: 0...180, step: 1) .padding() .onChange(of: rectData.ImageRotation) { _ in adjustRectSizeForRotation() } } .onAppear { adjustRectSizeForRotation() } } func adjustRectSizeForRotation() { let angleRadians = rectData.ImageRotation.radians let absCos = abs(cos(angleRadians)) let absSin = abs(sin(angleRadians)) let requiredWidth = (rectData.frameSize.width * absCos) + (rectData.frameSize.height * absSin) let requiredHeight = (rectData.frameSize.width * absSin) + (rectData.frameSize.height * absCos) // Calculate the scaling factor to maintain aspect ratio let scaleFactorWidth = requiredWidth / rectData.imageSize.width let scaleFactorHeight = requiredHeight / rectData.imageSize.height let scaleFactor = max(scaleFactorWidth, scaleFactorHeight) rectData.imageSize.width *= scaleFactor * 1.01 rectData.imageSize.height *= scaleFactor * 1.01 rectData.ImagePosition = rectData.framePosition } // MARK: - Drag Gesture func dragGesture() -> some Gesture { DragGesture() .onChanged { value in let newX = rectData.ImagePosition.x + value.translation.width - lastDragOffset.width let newY = rectData.ImagePosition.y + value.translation.height - lastDragOffset.height if isFullyCoveringRect(newPosition: CGPoint(x: newX, y: rectData.ImagePosition.y)){ rectData.ImagePosition.x = newX } if isFullyCoveringRect(newPosition: CGPoint(x: rectData.ImagePosition.x, y: newY)){ rectData.ImagePosition.y = newY } lastDragOffset = value.translation } .onEnded { _ in lastDragOffset = .zero } } // MARK: - Coverage Check private func isFullyCoveringRect(newPosition: CGPoint) -> Bool { let rect1Corners = getRotatedCorners(size: rectData.imageSize, position: newPosition, rotation: rectData.ImageRotation) let rect2Corners = getCorners(size: rectData.frameSize, position: rectData.framePosition) return rect2Corners.allSatisfy { point in isPointInsideRotatedRect(point: point, rectCorners: rect1Corners) } } func isPointInsideRotatedRect(point: CGPoint, rectCorners: [CGPoint]) -> Bool { let AB = CGVector(dx: rectCorners[1].x - rectCorners[0].x, dy: rectCorners[1].y - rectCorners[0].y) let AD = CGVector(dx: rectCorners[3].x - rectCorners[0].x, dy: rectCorners[3].y - rectCorners[0].y) let AP = CGVector(dx: point.x - rectCorners[0].x, dy: point.y - rectCorners[0].y) let ABdotAB = AB.dx * AB.dx + AB.dy * AB.dy let ADdotAD = AD.dx * AD.dx + AD.dy * AD.dy let APdotAB = AP.dx * AB.dx + AP.dy * AB.dy let APdotAD = AP.dx * AD.dx + AP.dy * AD.dy return (0 <= APdotAB && APdotAB <= ABdotAB) && (0 <= APdotAD && APdotAD <= ADdotAD) } // MARK: - Get Corners of a Rectangle func getCorners(size: CGSize, position: CGPoint) -> [CGPoint] { let halfWidth = size.width / 2 let halfHeight = size.height / 2 return [ CGPoint(x: position.x - halfWidth, y: position.y - halfHeight), CGPoint(x: position.x + halfWidth, y: position.y - halfHeight), CGPoint(x: position.x + halfWidth, y: position.y + halfHeight), CGPoint(x: position.x - halfWidth, y: position.y + halfHeight) ] } func getRotatedCorners(size: CGSize, position: CGPoint, rotation: Angle) -> [CGPoint] { let halfWidth = size.width / 2 let halfHeight = size.height / 2 let angleRadians = rotation.radians let cosTheta = cos(angleRadians) let sinTheta = sin(angleRadians) let localCorners = [ CGPoint(x: -halfWidth, y: -halfHeight), CGPoint(x: halfWidth, y: -halfHeight), CGPoint(x: halfWidth, y: halfHeight), CGPoint(x: -halfWidth, y: halfHeight) ] // Rotate and translate corners to the global coordinate system return localCorners.map { corner in let rotatedX = corner.x * cosTheta - corner.y * sinTheta let rotatedY = corner.x * sinTheta + corner.y * cosTheta let point = CGPoint( x: rotatedX + position.x, y: rotatedY + position.y ) return point } } } // MARK: - Data Models struct RectData { var frameSize: CGSize var framePosition: CGPoint var imageSize: CGSize var ImageRotation: Angle var ImagePosition: CGPoint } // MARK: - Preview struct RotatingDraggingRectView_Previews1: PreviewProvider { static var previews: some View { RotatingDraggingRectView() } }

阻力的问题是由拖动位置验证的方式引起的。一旦阻力位置在较小矩形的边界之外,就会忽略运动。同样,通过检查新位置的四个角来测试更新的位置,而不是确定阻力移动的最大和最小限制并将运动限制为这些边界。如果正确应用限制,此检查将是多余的。
    可以从最大允许的阻力长度计算拖曳运动的最小和最大限制。下图说明了如何计算此长度:
  • 基于最大阻力长度,可以计算最大x和y阻力偏移量。同样,可以调整不沿旋转轴的运动,以使其与轴对齐。 此处是如何将此验证集成到代码中:

将状态变量重新放置

l

带有一个变量以记录拖盘位置:

lastDragOffset
swift math swiftui
1个回答
0
投票

改变阻力手势验证

// @State private var lastDragOffset: CGSize = .zero
@State private var imagePositionAtStartOfDrag: CGPoint?

您可以看到,检查现在是多余的,可以省略。 其他建议的更改:

diagram

DragGesture() .onChanged { value in let xBegin: CGFloat let yBegin: CGFloat if let imagePositionAtStartOfDrag { xBegin = imagePositionAtStartOfDrag.x yBegin = imagePositionAtStartOfDrag.y } else { imagePositionAtStartOfDrag = rectData.ImagePosition xBegin = rectData.ImagePosition.x yBegin = rectData.ImagePosition.y } let angleRadians = rectData.ImageRotation.radians let cosAngle = cos(angleRadians) let sinAngle = sin(angleRadians) let A = (rectData.frameSize.width * abs(cosAngle)) let B = (rectData.frameSize.height * abs(sinAngle)) let maxDragLen = rectData.imageSize.width - (A + B) let dxMax = maxDragLen * cosAngle let dyMax = maxDragLen * sinAngle let minX = rectData.framePosition.x - abs(dxMax / 2) let maxX = rectData.framePosition.x + abs(dxMax / 2) let minY = rectData.framePosition.y - abs(dyMax / 2) let maxY = rectData.framePosition.y + abs(dyMax / 2) let xDrag = min(maxX, max(minX, xBegin + value.translation.width)) let yDrag = min(maxY, max(minY, yBegin + value.translation.height)) let dxDrag = xDrag - xBegin let dyDrag = yDrag - yBegin let dx: CGFloat let dy: CGFloat if dxMax == 0 || dyMax == 0 { dx = dxDrag dy = dyDrag } else { let ratio = dxMax / dyMax let dxAdjusted = dyDrag * ratio let dyAdjusted = dxDrag / ratio if abs(dxDrag - dxAdjusted) < abs(dyDrag - dyAdjusted) { dx = dxAdjusted dy = dyDrag } else { dx = dxDrag dy = dyAdjusted } } let newX = xBegin + dx let newY = yBegin + dy // if isFullyCoveringRect(newPosition: CGPoint(x: newX, y: newY)){ rectData.ImagePosition.x = newX rectData.ImagePosition.y = newY // } } .onEnded { _ in imagePositionAtStartOfDrag = nil }

,无需将内容包裹在

isFullyCoveringRect

中。为了达到平方比例,其中内容的高度与屏幕宽度相同,可以使用修饰符
    body
  1. 拖动手势只需要附加到蓝色矩形上,而不是整个。
    
    实际上iOS 17或更高版本,解决您的部署目标,可以通过删除
  2. GeometryReader
.
可以消除dobecation警告。
    .aspectRatio
  1. 也在
  2. ZStack
-如果将蓝色矩形缩放为1.01的原因是为阻力运动增加了一些灵活性,那么这是不再需要的。
.onChange

在这里,它与操作更改一起工作:

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.