可拖动和可旋转的图像(蓝色矩形),该图像应始终在拖动时完全覆盖固定框架。
有时,由于旋转约束而突然停止了运动。
旋转时,矩形并不总是在各个方向上平稳移动。goal
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()
}
}
阻力的问题是由拖动位置验证的方式引起的。一旦阻力位置在较小矩形的边界之外,就会忽略运动。同样,通过检查新位置的四个角来测试更新的位置,而不是确定阻力移动的最大和最小限制并将运动限制为这些边界。如果正确应用限制,此检查将是多余的。
将状态变量重新放置
l
带有一个变量以记录拖盘位置:
lastDragOffset
改变阻力手势验证
// @State private var lastDragOffset: CGSize = .zero
@State private var imagePositionAtStartOfDrag: CGPoint?
您可以看到,检查现在是多余的,可以省略。
其他建议的更改:
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
拖动手势只需要附加到蓝色矩形上,而不是整个。实际上iOS 17或更高版本,解决您的部署目标,可以通过删除
GeometryReader
可以消除dobecation警告。
.aspectRatio
ZStack
.onChange
在这里,它与操作更改一起工作: