문제

I'm trying to translate a UIView that has been either rotated and/or scaled using touches from the user. I try to translate it with user input as well:

- (void)handleObjectMove:(UIPanGestureRecognizer *)recognizer
{
    static CGPoint lastPoint;
    UIView *moveView = [recognizer view];
    CGPoint newCoord = [recognizer locationInView:playArea];

    // Check if this is the first touch
    if( [recognizer state]==UIGestureRecognizerStateBegan )
    {
        // Store the initial touch so when we change positions we do not snap
        lastPoint = newCoord;
    }

    // Create the frame offsets to use our finger position in the view.
    float dX = newCoord.x;
    float dY = newCoord.y;

    dX-=lastPoint.x;
    dY-=lastPoint.y;

    // Figure out the translation based on how we are scaled
    CGAffineTransform transform = [moveView transform];
    CGFloat xScale = transform.a;
    CGFloat yScale = transform.d;

    dX/=xScale;
    dY/=yScale;

    lastPoint = newCoord;

    [moveView setTransform:CGAffineTransformTranslate( transform, dX, dY )];

    [recognizer setTranslation:CGPointZero inView:playArea];
}

But when I touch and move the view it gets translated in all different weird ways. Can I apply some sort of formula using the rotation values to translate properly?

도움이 되었습니까?

해결책

The best solution I've found with having to use the least amount of math was to store the original translation, rotation, and scaling values separately and redo the transform when they were changed. My solution was to subclass a UIView with the following properties:

@property (nonatomic) CGPoint translation;
@property (nonatomic) CGFloat rotation;
@property (nonatomic) CGPoint scaling;

And the following functions:

- (void)rotationDelta:(CGFloat)delta
{
    [self setRotation:[self rotation]+delta];
}

- (void)scalingDelta:(CGPoint)delta
{
    [self setScaling:
     (CGPoint){ [self scaling].x*delta.x, [self scaling].y*delta.y }];
}

- (void)translationDelta:(CGPoint)delta
{
    [self setTranslation:
     (CGPoint){ [self translation].x+delta.x, [self translation].y+delta.y }];
}

- (void)transformMe
{
    // Start with the translation
    CGAffineTransform transform = CGAffineTransformMakeTranslation( [self translation].x, [self translation].y );
    // Apply scaling
    transform = CGAffineTransformScale( transform, [self scaling].x, [self scaling].y );
    // Apply rotation
    transform = CGAffineTransformRotate( transform, [self rotation] );

    [self setTransform:transform];
}

- (void)setScaling:(CGPoint)newScaling
{
    scaling = newScaling;
    [self transformMe];
}

- (void)setRotation:(CGFloat)newRotation
{
    rotation = newRotation;
    [self transformMe];
}

- (void)setTranslation:(CGPoint)newTranslation
{
    translation = newTranslation;
    [self transformMe];
}

And to use the following in the handlers:

- (void)handleObjectPinch:(UIPinchGestureRecognizer *)recognizer
{
    if( [recognizer state] == UIGestureRecognizerStateEnded
        || [recognizer state] == UIGestureRecognizerStateChanged )
    {
        // Get my stuff
        if( !selectedView )
            return;

        SelectableImageView *view = selectedView;

        CGFloat scaleDelta = [recognizer scale];
        [view scalingDelta:(CGPoint){ scaleDelta, scaleDelta }];

        [recognizer setScale:1.0];
    }
}

- (void)handleObjectMove:(UIPanGestureRecognizer *)recognizer
{
    static CGPoint lastPoint;
    SelectableImageView *moveView = (SelectableImageView *)[recognizer view];
    CGPoint newCoord = [recognizer locationInView:playArea];

    // Check if this is the first touch
    if( [recognizer state]==UIGestureRecognizerStateBegan )
    {
        // Store the initial touch so when we change positions we do not snap
        lastPoint = newCoord;
    }

    // Create the frame offsets to use our finger position in the view.
    float dX = newCoord.x;
    float dY = newCoord.y;

    dX-=lastPoint.x;
    dY-=lastPoint.y;
    lastPoint = newCoord;

    [moveView translationDelta:(CGPoint){ dX, dY }];

    [recognizer setTranslation:CGPointZero inView:playArea];
}

- (void)handleRotation:(UIRotationGestureRecognizer *)recognizer
{
    if( [recognizer state] == UIGestureRecognizerStateEnded
       || [recognizer state] == UIGestureRecognizerStateChanged )
    {
        if( !selectedView )
            return;

        SelectableImageView *view = selectedView;

        CGFloat rotation = [recognizer rotation];
        [view rotationDelta:rotation];

        [recognizer setRotation:0.0];
    }
}

다른 팁

Try Change moveView.center instead of Set (x,y) directly or either "CGAffineTransformTranslate"

Here is the Swift 4/5 version for a transformable UIView

class TransformableImageView: UIView{

var translation:CGPoint = CGPoint(x:0,y:0)

var scale:CGPoint = CGPoint(x:1, y:1)

var rotation:CGFloat = 0


override init (frame : CGRect) {
   super.init(frame: frame)
}


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

func rotationDelta(delta:CGFloat) {
    rotation = rotation + delta
}

func scaleDelta(delta:CGPoint){
    scale = CGPoint(x: scale.x*delta.x, y: scale.y * delta.y)
}

func translationDelta(delta:CGPoint){
    translation = CGPoint(x: translation.x+delta.x, y: translation.y + delta.y)
}



func transform(){
    self.transform = CGAffineTransform.identity.translatedBy(x: translation.x, y: translation.y).scaledBy(x: scale.x, y: scale.y ).rotated(by: rotation )
}

}

I'm leaving this here as I also encountered the same problem. Here is how to do it in swift 2:

Add your top view as subview to your bottom view:

self.view.addSubview(topView)

Then add a PanGesture Recognizer to move on touch:

//Add PanGestureRecognizer to move
    let panMoveGesture = UIPanGestureRecognizer(target: self, action: #selector(YourViewController.moveViewPanGesture(_:)))
    topView.addGestureRecognizer(panMoveGesture)

And the function to move:

//Move function
func moveViewPanGesture(recognizer:UIPanGestureRecognizer)
{
    if recognizer.state == .Changed {
        var center = recognizer.view!.center
        let translation = recognizer.translationInView(recognizer.view?.superview)
        center = CGPoint(x:center.x + translation.x,
                             y:center.y + translation.y)
        recognizer.view!.center = center
        recognizer.setTranslation(CGPoint.zero, inView: recognizer.view)
    }
}

Basically, you need to translate your view based on the bottom view which is its superview not itself. Like this: recognizer.view?.superview Or if you also rotate the bottom view, you may add a view which is not going to have any trasformation, and add your bottom view to that not transforming view (very bottom view) and add your top view to bottom view accordingly as subview. Then you should translate your top view based on the very bottom view.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top