0

我想根据 DragGesture 的值旋转一个stackedImageView(包含图像和形状)。旋转应该以stackedImageView 的中心为锚点旋转。它似乎基于父视图的中心旋转。

放置在屏幕中间时旋转效果很好(location = CGPoint(x: 0, y: 0);但是,当放置在父视图边缘附近时,旋转变得不可预测(例如 CGPoint(x: -80 ,y:-100))。

我认为这是由于.rotateEffect() 中的anchorPoint。如何计算 UnitPoint 到我的旋转stackedImageView 的中心并将其用作rotateEffect 的锚点?有没有办法从 DragGesture 的 startLocation 计算它?

谢谢!

struct ContentView: View {
    @State private var angle: CGFloat = 0
    @State private var lastAngle: CGFloat = 0
    @State private var length : CGFloat = 100
    @State private var location: CGPoint = CGPoint(x: -80, y: -100)
    
    let systemImage = UIImage(systemName: "camera")!

    
    var body: some View {
        
    var body: some View {
        
        VStack {
            
            Text("Rotating View Example")
                .frame(height: 200)
            
            
            rotationView
                .frame(width: 300, height: 300)
                .border(.blue, width: 2)
            
        }
    }
    
    
    private var rotationView: some View {
        
        let imageSize = newSize(100)
        
        return ZStack {
            
            Image(uiImage: systemImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .offset(x: location.x, y: location.y)
            
            
            Circle()
                .foregroundColor(Color.accentColor)
                .frame(width: 20, height: 20)
                .position(x: location.x + imageSize.width, y: location.y + imageSize.height)
            
            
        }
        .rotationEffect(.degrees(Double(self.angle)))
        .gesture(DragGesture()
            .onChanged{ value in
            
            let centerPt = CGPoint(x: value.startLocation.x - imageSize.width / 2, y: value.startLocation.y - imageSize.height / 2)
            let theta = (atan2(value.location.y - centerPt.y, value.location.x - centerPt.x) - atan2(value.startLocation.y - centerPt.y, value.startLocation.x - centerPt.x)) * 180 / .pi
            angle = theta + lastAngle
            
            print("angle: \(angle), centerPt: (\(centerPt.x), \(centerPt.y)), start: (\(value.startLocation.x), \(value.startLocation.y), end: (\(value.location.x), \(value.location.y)")

            }
            .onEnded { v in
                self.lastAngle = self.angle
            }
        )
        .animation(.none)
        .frame(width: imageSize.width, height: imageSize.height)
    }

    
    func newSize(_ maxDimension: CGFloat) -> CGSize {
        
        let imageSize = systemImage.size
        if imageSize.width > imageSize.height {
            return recalculateSize(imageSize: systemImage.size, width: maxDimension)
        } else {
            return recalculateSize(imageSize: systemImage.size, height: maxDimension)
        }

    }
    
    
    func recalculateSize(imageSize: CGSize, width: CGFloat? = nil, height: CGFloat? = nil) -> CGSize {
        
        if let width = width, height == nil {
            
            let newHeight = imageSize.height / (imageSize.width / width)
            return CGSize(width: width, height: newHeight)
            
        } else if let height = height, width == nil {
            
            let newWidth = imageSize.width / (imageSize.height / height)
            return CGSize(width: newWidth, height: height)
            
        }
            
        return imageSize
    }
}
4

1 回答 1

1

您遇到的问题实际上很简单,但它来自对做什么的根本误解.offset().offset()实际上并不移动正在偏移的视图,而是从视图的实际位置移动到显示视图的位置。你发生的事情var rotationView是你在应用旋转之前偏移了视图。旋转总是会使用视图的对位点作为旋转中心。请记住,不管.offset(),这是位于 中心的初始中心ZStack。因此,虽然它看起来像是在使用ZStack's中心,但实际上它在使用的中心rotationView也恰好是ZStack. 如果您删除了偏移量,并更改了ZStack对齐,您将看到这如何改变rotationView.

修复实际上非常简单:将偏移量Image放在rotationView. 这允许旋转在它被偏移之前发生,它看起来就像你所期望的那样。

移动.offset确实会影响 的位置Circle()。为此,您使用.position()which 确实将实际视图移动到屏幕上的绝对位置。现在也将其更改为 a .offset(),因为您希望将Circle()相对定位在 中ZStack,并使其围绕同一中心旋转。

最后一件事。.animation()已弃用,应将其删除。您的所有动画现在都应该是显式的,而不是隐式的,并且除非您明确制作,否则此视图不会动画。

struct ContentView: View {
    @State private var angle: CGFloat = 0
    @State private var lastAngle: CGFloat = 0
    @State private var length : CGFloat = 100
    @State private var location: CGPoint = CGPoint(x: -80, y: -100)
    
    let systemImage = UIImage(systemName: "camera")!

    var body: some View {
        
        VStack {
            
            Text("Rotating View Example")
                .frame(height: 200)
            
            
            rotationView
                .offset(x: location.x, y: location.y)
                .frame(width: 300, height: 300)
                .border(.blue, width: 2)
            
        }
    }
    
    
    private var rotationView: some View {
        
        let imageSize = newSize(100)
        
        return ZStack {
            
            Image(uiImage: systemImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
            
            
            Circle()
                .foregroundColor(Color.accentColor)
                .frame(width: 20, height: 20)
                // This no longer uses .position() which is absolute, but uses .offset
                .offset(x: imageSize.width / 2, y: imageSize.height / 2)
            
            
        }
        .rotationEffect(.degrees(Double(self.angle)))
        .gesture(DragGesture()
            .onChanged{ value in
            
            let centerPt = CGPoint(x: value.startLocation.x - imageSize.width / 2, y: value.startLocation.y - imageSize.height / 2)
            let theta = (atan2(value.location.y - centerPt.y, value.location.x - centerPt.x) - atan2(value.startLocation.y - centerPt.y, value.startLocation.x - centerPt.x)) * 180 / .pi
            angle = theta + lastAngle
            
            print("angle: \(angle), centerPt: (\(centerPt.x), \(centerPt.y)), start: (\(value.startLocation.x), \(value.startLocation.y), end: (\(value.location.x), \(value.location.y)")

            }
            .onEnded { v in
                self.lastAngle = self.angle
            }
        )
        .frame(width: imageSize.width, height: imageSize.height)
    }

    
    func newSize(_ maxDimension: CGFloat) -> CGSize {
        
        let imageSize = systemImage.size
        if imageSize.width > imageSize.height {
            return recalculateSize(imageSize: systemImage.size, width: maxDimension)
        } else {
            return recalculateSize(imageSize: systemImage.size, height: maxDimension)
        }

    }
    
    
    func recalculateSize(imageSize: CGSize, width: CGFloat? = nil, height: CGFloat? = nil) -> CGSize {
        
        if let width = width, height == nil {
            
            let newHeight = imageSize.height / (imageSize.width / width)
            return CGSize(width: width, height: newHeight)
            
        } else if let height = height, width == nil {
            
            let newWidth = imageSize.width / (imageSize.height / height)
            return CGSize(width: newWidth, height: height)
            
        }
            
        return imageSize
    }
}
于 2022-02-05T18:54:13.533 回答