【问题标题】:SwiftUI: How to set anchorPoint of rotateEffect in relevant to the centre of a subview/image instead of the parent viewSwiftUI:如何设置与子视图/图像中心相关的旋转效果锚点而不是父视图
【发布时间】:2022-02-05 09:25:34
【问题描述】:

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

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

我认为这是由于 .rotateEffect() 中的锚点。如何计算 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
    }
}

【问题讨论】:

标签: swiftui anchorpoint draggesture


【解决方案1】:

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

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

移动.offset 确实会影响Circle() 的放置。为此,您使用了.position(),它确实将实际视图移动到屏幕上的绝对位置。现在也将其更改为.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
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多