【问题标题】:iOS Pinch Scale and Two Finger Rotate at same timeiOS 捏缩放和两个手指同时旋转
【发布时间】:2011-12-27 20:52:05
【问题描述】:

这是我的代码:

viewDidLoad:

UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.canvas addGestureRecognizer:pinch];
pinch.delegate = self;

UIRotationGestureRecognizer *twoFingersRotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(pinchRotate:)];
[[self canvas] addGestureRecognizer:twoFingersRotate];

twoFingersRotate.delegate = self;

捏合和旋转的代码:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

-(void)pinchRotate:(UIRotationGestureRecognizer*)rotate
{
    SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;

    switch (rotate.state) 
    {
        case UIGestureRecognizerStateBegan:
        {
            selectedImage.referenceTransform = selectedImage.transform;
            break;
        }
        case UIGestureRecognizerStateChanged:
        {
            selectedImage.transform = CGAffineTransformRotate(selectedImage.referenceTransform, ([rotate rotation] * 55) * M_PI/180);
            break;
        }

        default:
            break;
    }
}

-(void)pinch:(UIPinchGestureRecognizer*)pinch
{
    SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
    [self itemSelected];

    switch (pinch.state) 
    {
        case UIGestureRecognizerStateBegan:
        {
            selectedImage.referenceTransform = selectedImage.transform;
            break;
        }
        case UIGestureRecognizerStateChanged:
        {
            CGAffineTransform transform = CGAffineTransformScale(selectedImage.referenceTransform, pinch.scale, pinch.scale);
            selectedImage.transform = transform;
            break;
        }

        default:
            break;
    }
}

我的轮换单独使用效果很好,而我的比例单独使用效果很好,但它们不能一起使用。一个总是有效,另一个则无效。当我实现 shouldRecognizeSimultaneouslyWithGestureRecognizer 时,这两个手势似乎相互对抗并产生不良结果。我错过了什么? (是的,我已经实现了<UIGestureRecognizerDelegate>

【问题讨论】:

    标签: iphone ios objective-c ipad cocoa-touch


    【解决方案1】:

    每次调用pinch: 时,您只需根据捏合识别器的比例计算变换。每次调用pinchRotate: 时,您只需根据旋转识别器的旋转计算变换。您永远不会将缩放和旋转组合到一个变换中。

    这是一种方法。给自己一个新的实例变量,_activeRecognizers

    NSMutableSet *_activeRecognizers;
    

    viewDidLoad中初始化它:

    _activeRecognizers = [NSMutableSet set];
    

    使用一种方法作为两个识别器的操作:

    - (IBAction)handleGesture:(UIGestureRecognizer *)recognizer
    {
        SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
    
        switch (recognizer.state) {
            case UIGestureRecognizerStateBegan:
                if (_activeRecognizers.count == 0)
                    selectedImage.referenceTransform = selectedImage.transform;
                [_activeRecognizers addObject:recognizer];
                break;
    
            case UIGestureRecognizerStateEnded:
                selectedImage.referenceTransform = [self applyRecognizer:recognizer toTransform:selectedImage.referenceTransform];
                [_activeRecognizers removeObject:recognizer];
                break;
    
            case UIGestureRecognizerStateChanged: {
                CGAffineTransform transform = selectedImage.referenceTransform;
                for (UIGestureRecognizer *recognizer in _activeRecognizers)
                    transform = [self applyRecognizer:recognizer toTransform:transform];
                selectedImage.transform = transform;
                break;
            }
    
            default:
                break;
        }
    }
    

    你需要这个辅助方法:

    - (CGAffineTransform)applyRecognizer:(UIGestureRecognizer *)recognizer toTransform:(CGAffineTransform)transform
    {
        if ([recognizer respondsToSelector:@selector(rotation)])
            return CGAffineTransformRotate(transform, [(UIRotationGestureRecognizer *)recognizer rotation]);
        else if ([recognizer respondsToSelector:@selector(scale)]) {
            CGFloat scale = [(UIPinchGestureRecognizer *)recognizer scale];
            return CGAffineTransformScale(transform, scale, scale);
        }
        else
            return transform;
    }
    

    如果您只允许旋转和缩放,则此方法有效。 (我什至测试过!)

    如果要添加平移,请使用单独的操作方法,只需调整selectedImage.center。尝试使用 selectedImage.transform 进行旋转和缩放平移要复杂得多。

    【讨论】:

    • 你能给我一个代码示例,说明如何同时应用这两者吗?我的 UIRotationGestureRecognizer 没有“缩放”属性,我的 UIPinchGestureRecognizer 没有“旋转”属性。而且我不确定我会使用哪个仿射来同时做这两个。
    • 效果很好。有了这些知识,我会好很多谢谢!
    • 很高兴我能帮上忙。顺便说一句,我改变了关于如何进行平移的最后一点。此外,您可以将手势识别器放入您的 XIB 中,然后您不必使用 viewDidLoad 中的代码创建它们。只需将识别器拖到视图上,然后通过从识别器到文件所有者的控制拖动来连接识别器的委托和操作出口。
    • handleGesture 函数来自哪里?
    • 什么是SMImage?是第三方吗?
    【解决方案2】:

    带有平移、旋转和捏合功能的 Swift 3

    // MARK: - Gesturies
    
        func transformUsingRecognizer(_ recognizer: UIGestureRecognizer, transform: CGAffineTransform) -> CGAffineTransform {
    
            if let rotateRecognizer = recognizer as? UIRotationGestureRecognizer {
                return transform.rotated(by: rotateRecognizer.rotation)
            }
    
            if let pinchRecognizer = recognizer as? UIPinchGestureRecognizer {
                let scale = pinchRecognizer.scale
                return transform.scaledBy(x: scale, y: scale)
            }
    
            if let panRecognizer = recognizer as? UIPanGestureRecognizer {
                let deltaX = panRecognizer.translation(in: imageView).x
                let deltaY = panRecognizer.translation(in: imageView).y
                return transform.translatedBy(x: deltaX, y: deltaY)
            }
    
            return transform
        }
    
        var initialTransform: CGAffineTransform?
    
        var gestures = Set<UIGestureRecognizer>(minimumCapacity: 3)
    
        @IBAction func processTransform(_ sender: Any) {
    
            let gesture = sender as! UIGestureRecognizer
    
            switch gesture.state {
    
            case .began:
                if gestures.count == 0 {
                    initialTransform = imageView.transform
                }
                gestures.insert(gesture)
    
            case .changed:
                if var initial = initialTransform {
                    gestures.forEach({ (gesture) in
                        initial = transformUsingRecognizer(gesture, transform: initial)
                    })
                    imageView.transform = initial
                }
    
            case .ended:
                gestures.remove(gesture)
    
            default:
                break
            }
        }
    
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    
            return true
        }
    

    【讨论】:

    • 你测试了吗?
    • @RoiMulia 当然。在真正的应用程序中使用它(还没有在产品中)。怎么了?
    • 嘿迈克,很抱歉给您带来压力。什么都没有。只是想问一下它是否已经过测试以确保它可以安全使用(以防我错过了一些最终场景)
    • 这不起作用我可以确认。如果视图被旋转,平移手势在 Y 方向不能正常工作。
    • 晚了几年,但这对我来说非常有效。为我省去了很多麻烦。
    【解决方案3】:

    为此,您需要实现手势委托shouldRecognizeSimultaneouslyWithGestureRecognizer 并同时放置您想要识别的手势。

    // ensure that the pinch and rotate gesture recognizers on a particular view can all recognize simultaneously
    // prevent other gesture recognizers from recognizing simultaneously
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        // if the gesture recognizers's view isn't one of our views, don't allow simultaneous recognition
        if (gestureRecognizer.view != firstView && gestureRecognizer.view != secondView)
            return NO;
    
        // if the gesture recognizers are on different views, don't allow simultaneous recognition
        if (gestureRecognizer.view != otherGestureRecognizer.view)
            return NO;
    
        // if either of the gesture recognizers is the long press, don't allow simultaneous recognition
        if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
            return NO;
    
        return YES;
    }
    

    此代码需要修改为您想要同时进行手势识别器的视图。上面的代码就是你需要的。

    【讨论】:

    • 他已经有一个shouldRecognizeSimultaneouslyWithGestureRecognizer: 总是返回YES。
    【解决方案4】:

    本示例不使用手势识别器,而是直接计算变换矩阵。它还可以正确处理一到两个手指的转换。

    class PincherView: UIView {
    
        override var bounds :CGRect {
            willSet(newBounds) {
                oldBounds = self.bounds
            } didSet {
                self.imageLayer.position = ┼self.bounds
                self._adjustScaleForBoundsChange()
            }
        }
        var oldBounds :CGRect
    
        var touch₁  :UITouch?
        var touch₂  :UITouch?
        var p₁      :CGPoint?  // point 1 in image coordiate system
        var p₂      :CGPoint?  // point 2 in image coordinate system
        var p₁ʹ     :CGPoint?  // point 1 in view coordinate system
        var p₂ʹ     :CGPoint?  // point 2 in view coordinate system
    
        var image   :UIImage? {
            didSet {self._reset()}
        }
        var imageLayer :CALayer
        var imageTransform :CGAffineTransform {
            didSet {
                self.backTransform = self.imageTransform.inverted()
                self.imageLayer.transform = CATransform3DMakeAffineTransform(self.imageTransform)
            }
        }
        var backTransform  :CGAffineTransform
        var solutionMatrix :HXMatrix?
    
        required init?(coder aDecoder: NSCoder) {
            self.oldBounds = CGRect.zero
            let layer = CALayer();
            self.imageLayer = layer
            self.imageTransform = CGAffineTransform.identity
            self.backTransform = CGAffineTransform.identity
    
            super.init(coder: aDecoder)
    
            self.oldBounds = self.bounds
            self.isMultipleTouchEnabled = true
            self.layer.addSublayer(layer)
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            for touch in touches {
                let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
                let p = pʹ.applying(self.backTransform)
                if self.touch₁ == nil {
                    self.touch₁ = touch
                    self.p₁ʹ = pʹ
                    self.p₁ = p
                } else if self.touch₂ == nil {
                    self.touch₂ = touch
                    self.p₂ʹ = pʹ
                    self.p₂ = p
                }
            }
            self.solutionMatrix = self._computeSolutionMatrix()
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            for touch in touches {
                let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
                if self.touch₁ == touch {
                    self.p₁ʹ = pʹ
                } else if self.touch₂ == touch {
                    self.p₂ʹ = pʹ
                }
            }
    
            CATransaction.begin()
            CATransaction.setValue(true, forKey:kCATransactionDisableActions)
            // Whether you're using 1 finger or 2 fingers
            if let q₁ʹ = self.p₁ʹ, let q₂ʹ = self.p₂ʹ {
                self.imageTransform = self._computeTransform(q₁ʹ, q₂ʹ)
            } else if let q₁ʹ = (self.p₁ʹ != nil ? self.p₁ʹ : self.p₂ʹ) {
                self.imageTransform = self._computeTransform(q₁ʹ, CGPoint(x:q₁ʹ.x + 10, y:q₁ʹ.y + 10))
            }
            CATransaction.commit()
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            for touch in touches {
                if self.touch₁ == touch {
                    self.touch₁ = nil
                    self.p₁ = nil
                    self.p₁ʹ = nil
                } else if self.touch₂ == touch {
                    self.touch₂ = nil
                    self.p₂ = nil
                    self.p₂ʹ = nil
                }
            }
            self.solutionMatrix = self._computeSolutionMatrix()
        }
    
        //MARK: Private Methods
    
        private func _reset() {
            guard
                let image = self.image,
                let cgimage = image.cgImage else {
                    return
            }
    
            let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
            imageLayer.contents = cgimage;
            imageLayer.bounds = r
            imageLayer.position = ┼self.bounds
            self.imageTransform = self._initialTransform()
        }
    
        private func _normalizeTransform() -> CGAffineTransform {
            let center = ┼self.bounds
            return CGAffineTransform(translationX: center.x, y: center.y)
        }
    
        private func _backNormalizeTransform() -> CGAffineTransform {
            return self._normalizeTransform().inverted();
        }
    
        private func _initialTransform() -> CGAffineTransform {
            guard let image = self.image, let cgimage = image.cgImage else {
                return CGAffineTransform.identity;
            }
            let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
            let s = r.scaleIn(rect: self.bounds)
            return CGAffineTransform(scaleX: s, y: s)
        }
    
        private func _adjustScaleForBoundsChange() {
            guard let image = self.image, let cgimage = image.cgImage else {
                return
            }
            let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
            let oldIdeal = r.scaleAndCenterIn(rect: self.oldBounds)
            let newIdeal = r.scaleAndCenterIn(rect: self.bounds)
            let s = newIdeal.height / oldIdeal.height
            self.imageTransform = self.imageTransform.scaledBy(x: s, y: s)
        }
    
        private func _computeSolutionMatrix() -> HXMatrix? {
            if let q₁ = self.p₁, let q₂ = self.p₂ {
                return _computeSolutionMatrix(q₁, q₂)
            } else if let q₁ = self.p₁, let q₁ʹ = self.p₁ʹ {
                let q₂ = CGPoint(x: q₁ʹ.x + 10, y: q₁ʹ.y + 10).applying(self.backTransform)
                return _computeSolutionMatrix(q₁, q₂)
            } else if let q₂ = self.p₂, let q₂ʹ = self.p₂ʹ {
                let q₁ = CGPoint(x: q₂ʹ.x + 10, y: q₂ʹ.y + 10).applying(self.backTransform)
                return _computeSolutionMatrix(q₂, q₁)
            }
            return nil
        }
    
        private func _computeSolutionMatrix(_ q₁:CGPoint, _ q₂:CGPoint) -> HXMatrix {
            let x₁ = Double(q₁.x)
            let y₁ = Double(q₁.y)
            let x₂ = Double(q₂.x)
            let y₂ = Double(q₂.y)
            let A = HXMatrix(rows: 4, columns: 4, values:[
                x₁, -y₁, 1, 0,
                y₁,  x₁, 0, 1,
                x₂, -y₂, 1, 0,
                y₂,  x₂, 0, 1
            ])
            return A.inverse()
        }
    
        private func _computeTransform(_ q₁ʹ:CGPoint, _ q₂ʹ:CGPoint) -> CGAffineTransform {
            guard let solutionMatrix = self.solutionMatrix else {
                return CGAffineTransform.identity
            }
    
            let B = HXMatrix(rows: 4, columns: 1, values: [
                Double(q₁ʹ.x),
                Double(q₁ʹ.y),
                Double(q₂ʹ.x),
                Double(q₂ʹ.y)
                ])
            let C = solutionMatrix ⋅ B
    
            let  U = CGFloat(C[0,0])
            let  V = CGFloat(C[1,0])
            let tx = CGFloat(C[2,0])
            let ty = CGFloat(C[3,0])
    
            var  t :CGAffineTransform = CGAffineTransform.identity
            t.a  =  U; t.b  = V
            t.c  = -V; t.d  = U
            t.tx = tx; t.ty = ty
    
            return t
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-25
      • 1970-01-01
      • 2018-12-02
      • 1970-01-01
      相关资源
      最近更新 更多