如何将拖动功能添加到转换后的视图?

Posted

技术标签:

【中文标题】如何将拖动功能添加到转换后的视图?【英文标题】:How to add drag to move function to a transformed view? 【发布时间】:2016-11-08 01:54:44 【问题描述】:

我制作了一个自定义视图,可以通过拖动圆角按钮来调整大小和旋转。

我需要视图能够拖动以更改其位置。

我尝试了几种方法,但没有一个有效的属性(视图在组合旋转和调整大小动作时表现得很奇怪)。

这样做的正确方法是什么?

根据这个引用苹果文档的答案,What to use to move UIView self.frame or self.transform property? 我不应该将 frame 属性与转换一起使用,所以我假设我需要使用 CGAffineTransform 来更改视图的位置? (我试过这个但效果不好)

import Foundation
import UIKit

class ResizeRotateView: UIImageView, Resizable 

private let buttonWidth: CGFloat = 40
private var themeColor: UIColor = UIColor.magentaColor()


// Rotation Resize
var radian: CGFloat = 0

private var tempAnglePanStart  : Float = 0
private var tempAngleLastPanEnd: Float = 0


// UI Subviews
lazy var cornerButton: UIView = 
    let b = UIView()
    
    b.layer.cornerRadius = self.buttonWidth / 2
    b.layer.borderWidth  = 1
    b.layer.borderColor  = self.themeColor.CGColor
    
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(buttonTouchMoved) )
    b.addGestureRecognizer(panGesture)
    
    return b
()




// Init

override init(frame: CGRect) 
    super.init(frame: frame)
    
    setupSuviews()


required init?(coder aDecoder: NSCoder) 
    fatalError("init(coder:) has not been implemented")



func setupSuviews() 
    addSubview( cornerButton )
    addConstraintsWithFormat("H:[v0(\(buttonWidth))]", views: cornerButton)
    addConstraintsWithFormat("V:[v0(\(buttonWidth))]", views: cornerButton)
    addConstraint(NSLayoutConstraint(item: cornerButton, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
    addConstraint(NSLayoutConstraint(item: cornerButton, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .Top,   multiplier: 1, constant: 0))



override func hitTest(point: CGPoint, withEvent e: UIEvent?) -> UIView?  // This will enable gesture out of self.view's boundary. From ***.

    if let result = super.hitTest(point, withEvent:e) 
        return result
    
    
    for sub in self.subviews.reverse() 
        let pt = self.convertPoint(point, toView:sub)
        if let result = sub.hitTest(pt, withEvent:e) 
            return result
        
    
    return nil








////// Event

func buttonTouchMoved(gestureRecognizer: UIPanGestureRecognizer) 
 
    resize(gestureRecognizer)
    rotate(gestureRecognizer)

 







////// Action

// Resize
func resize(gestureRecognizer: UIPanGestureRecognizer) 
 
    // Temporary set aglet to 0
    self.transform = CGAffineTransformMakeRotation( 0 )

    // Resize
    let locationInSelf = gestureRecognizer.locationInView(self)

    resizeByKeepingAspectRatioByDistanceFromCenter(toLocation: locationInSelf)
    layoutSubviews()

    // Recover angle
    self.transform = CGAffineTransformMakeRotation( radian )
    layoutSubviews()
    




// Rotation
func rotate(gestureRecognizer: UIPanGestureRecognizer) 
    
    guard let superView = self.superview else 
        print("#Error: Super View not found")
        return
    
    
    let angle = self.angle(byLocation: gestureRecognizer.locationInView( superView ) )
    
    
    if gestureRecognizer.state == .Began 
        print("Pan begin:\(angle)")
        tempAnglePanStart = angle - tempAngleLastPanEnd
    
    
    
    let destinationAngle = angle - tempAnglePanStart
    self.transform = CGAffineTransformMakeRotation( destinationAngle.degreesToRadians )
    
    
    if gestureRecognizer.state == .Ended 
        print("Pan ended")
        tempAngleLastPanEnd = destinationAngle
    














////// Helper

func angle( firstPoint: CGPoint, secondPoint: CGPoint ) -> Float 
    
    let dx = firstPoint.x - secondPoint.x
    let dy = firstPoint.y - secondPoint.y
    let angle = atan2(dy, dx).radiansToDegrees
    let angleInFloat = Float( angle.double )
    
    let formattedAngle = angleInFloat < 0 ? angleInFloat + 360.0 : angleInFloat
    
    return formattedAngle



func angle(byLocation location: CGPoint) -> Float 
    
    let targetViewCnter = self.center
    return self.angle(targetViewCnter, secondPoint: location)


func setAngle(angle: Float) 
    let radian = angle.degreesToRadians
    self.transform = CGAffineTransformMakeRotation( radian )
    self.radian = radian


更新

我非常接近。我可以通过保持旋转角度和视图大小来移动。剩下的问题是当我开始拖动时视图会跳动。

import Foundation
import UIKit

class ResizeRorateMovableView: ResizeRotateView 

    var startCenter: CGPoint = CGPointZero
    
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        
        setupSuviews()
        
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureEvent))
        addGestureRecognizer( panGesture )
        userInteractionEnabled = true
    
    
    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    
    
    func panGestureEvent(panGesture: UIPanGestureRecognizer) 
        
        if panGesture.state == .Began 
            self.startCenter = self.center
            return
        
        
        if panGesture.state == .Changed 
            
            let location = panGesture.translationInView(self)
            
            // Rotate first, then move
            var transfrom  = CGAffineTransformMakeRotation( self.radian )
            transfrom      = CGAffineTransformTranslate(transfrom, location.x, location.y)
            self.transform = transfrom

            return
        
        
    

【问题讨论】:

问题是大多数应用程序使用平移手势来移动,而不是像你正在做的那样旋转。考虑到许多用户习惯于(a)平移手势移动视图,(b)捏手势调整视图大小,以及(c)旋转手势旋转视图。使用您正在编码的内容,您可能几乎需要一种完全不同的编码机制——例如开始/结束旋转的独特动作和开始/结束移动的另一个独特动作。 即使一个事件触发的转换是不同的,但实际的转换是由相同的逻辑完成的,所以我想这并不重要。我也打算实现(a),(b),(c),但是平移旋转的一个优点是可以用一根手指完成,用户可以用一只手操作任务。 【参考方案1】:

它可能会跳跃,因为用户不一定触摸中心,但您将中心设置为他们拖动的位置。如果你开始拖动靠近中心,它不会像你开始拖动靠近边缘那样跳跃。要修复,当手势开始时,获取从触摸到当前中心的距离,并在进行平移时使用该偏移量。

【讨论】:

我很难通过使用 CGAffineTransform 翻译来做到这一点。如果可以,请您发布代码吗? 奇怪的跳跃似乎与中心无关。即使我在离中心很近的地方点击,视图跳到中心的距离也比触摸点要远得多。 如果你能把你目前在样例项目中的代码贴出来,我来看看。 我尝试了多种方法,但似乎我发布的代码最接近解决方案。顺便说一句,我想出了一个添加超级视图来处理移动功能的想法。在这种情况下,我不必被 CAAffineTransform 的东西所困扰。如果这不起作用,我会再次发布情况。 添加超级视图来处理移动功能确实工作得很好!有时间我会贴出代码。无论如何,谢谢你的帮助,我真的很感激。【参考方案2】:

我只需要添加一个超级视图来实现移动功能。

这种情况下,我可以将旋转功能和移动功能完全分开,非常容易处理。

视图 A。实现了移动功能。它在下面是可见的,以便于理解,但它的颜色应该是清晰的。我触摸了仅在 ViewB(子视图)上启用的移动功能。

视图 B。实现了旋转功能。它是 ViewA 的子视图

【讨论】:

以上是关于如何将拖动功能添加到转换后的视图?的主要内容,如果未能解决你的问题,请参考以下文章

从列表视图的顶部到底部拖动项目时如何提高速度?

如何将 UIImage 从滚动视图拖动到视图

OS X:如何将鼠标拖动事件通过视图传递给超级视图

tiff格式如何转换成jpg格式

如何在我自己的应用程序中获取拖动到 bin 的主屏幕行为?

如何检查拖动视图的位置是不是位于另一个视图之上