敲击UITapGestureRecognizer后,NSLayoutConsstraint常量不影响视图 [英] NSLayoutConsstraint constant not affecting view after UITapGestureRecognizer was tapped

查看:56
本文介绍了敲击UITapGestureRecognizer后,NSLayoutConsstraint常量不影响视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个视图(self.view),该视图使用 UIView.mask属性.在self.view上,我安装了UIPanGestureRecognizer,因此当我在屏幕上平移时,遮罩会相应地变小和变大.另外,我安装在self.view上的UITapGestureRecognizer上,该UITapGestureRecognizer向屏幕添加了可动画制作的UIImageView,并且它们在UIBezierPath上进行动画设置.我正在更新带约束的面罩尺寸.

i have a view (self.view) that is masked with another view (not a layer) using the UIView.mask property. on self.view i installed a UIPanGestureRecognizer so when i pan across the screen the mask gets smaller and larger accordingly. in addition i installed on self.view a UITapGestureRecognizer which adds animatable UIImageViews to the screen and they are animating across a UIBezierPath. i am updating the mask size with constraints.

问题是我点击屏幕以添加可动画显示的视图后,我对蒙版约束所做的更改不再生效.我在日志中看到我确实确实更改了约束的常量,并且UIPanGestureRecognizer仍在工作.

the problem is that after i tap the screen to add animatable views the changes i make on the mask constraint stop taking affect. i can see in the log that i do indeed change the constant of the constraint and that the UIPanGestureRecognizer is still working.

所以我的意思是遮罩视图约束不再影响其视图.这是为什么?谢谢

so i mean that the mask view constraint stop affecting its view. why is that? thanks

视频插图: https://youtu.be/UtNuc8nicgs

下面是代码:

class UICircle: UIView {
    init() {
        super.init(frame: .zero)
        self.clipsToBounds = true
        self.backgroundColor = .yellow
        self.isUserInteractionEnabled = false
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    var diameterConstraint: NSLayoutConstraint?
    var animating = false

    func updateSize(_ delta: CGFloat, animated: Bool = false) {

        if animating { return }
        if animated {
            animating = true
            diameterConstraint?.constant = UIScreen.main.bounds.height * 2.1

            let duration: TimeInterval = 0.6
            let animation = CABasicAnimation(keyPath: "cornerRadius")
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
            animation.fromValue = self.layer.cornerRadius
            animation.toValue = UIScreen.main.bounds.height * 2.1 / 2
            animation.duration = duration
            self.layer.add(animation, forKey: nil)

            UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
                self.superview?.layoutIfNeeded()
            }, completion: { (success) in
                if success {
                    self.animating = false
                }
            })
        } else {
            let newSize = diameterConstraint!.constant + (delta * 2.85)
            if newSize > 60 && newSize < UIScreen.main.bounds.height * 2.1 {
                diameterConstraint?.constant = newSize
            }
        }

    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        if let superv = superview {
            self.makeSquare()
            self.centerHorizontallyTo(superv)
            let c = NSLayoutConstraint.init(item: self, attribute: .centerY, relatedBy: .equal, toItem: superv, attribute: .bottom, multiplier: 1, constant: -40)
            c.isActive = true
            diameterConstraint = self.constrainHeight(superv.frame.height * 2.1)
        }
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        self.layer.cornerRadius = self.frame.width / 2
    }

}


class ViewController: UIViewController, UIGestureRecognizerDelegate {

    var circle = UICircle()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.init(red: 48/255, green: 242/255, blue: 194/255, alpha: 1)
        self.view.clipsToBounds = true

        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        tap.delegate = self
        self.view.addGestureRecognizer(tap)

        setupCircle()

    }


    func setupCircle() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        panGesture.delegate = self
        self.view.addGestureRecognizer(panGesture)
        self.view.mask = circle
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }


    var panStarted = false
    func handlePan(_ pan: UIPanGestureRecognizer) {
        let delta = pan.translation(in: self.view).y
        if pan.state == .began {
            if delta > 0 {
                panStarted = true
                circle.updateSize(-delta)
            }
        } else if pan.state == .changed {
            if panStarted {
                circle.updateSize(-delta)
            }
        } else if pan.state == .ended || pan.state == .cancelled {
            if panStarted {
                circle.updateSize(self.view.frame.height * 2.1, animated: true)
            }
            panStarted = false
        }
        pan.setTranslation(.zero, in: self.view)
    }

    func handleTap() {
        let num = Int(5 + drand48() * 10)
        (1 ... num).forEach { (_) in
            addView()
        }
    }

    override var prefersStatusBarHidden: Bool {
        get {
            return true
        }
    }

    func addView() {

        var image: UIImageView!
        let dd = drand48()
        if dd < 0.5 {
            image = UIImageView(image: #imageLiteral(resourceName: "heart1"))
        } else {
            image = UIImageView(image: #imageLiteral(resourceName: "heart2"))
        }

        image.isUserInteractionEnabled = false
        image.contentMode = .scaleAspectFit
        let dim: CGFloat = 20 + CGFloat(10 * drand48())
        image.constrainHeight(dim)
        image.constrainWidth(dim)

        let animation = CAKeyframeAnimation(keyPath: "position")
        let duration = Double(1.5 * self.view.frame.width / CGFloat((60 + drand48() * 40))) // duration = way / speed
        animation.path = getPath().cgPath
        animation.duration = duration
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        animation.fillMode = kCAFillModeForwards
        animation.isRemovedOnCompletion = false
        image.layer.add(animation, forKey: nil)

        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + duration + 1) {
            DispatchQueue.main.async {
                image.removeFromSuperview()
            }
        }

        if drand48() < 0.3 {
            UIView.animate(withDuration: 0.2 + 0.1 * drand48() , delay: TimeInterval(drand48() * 1), options: [.curveEaseOut, .repeat, .autoreverse], animations: {
                image.transform = CGAffineTransform.init(scaleX: 1.5, y: 1.5)
            }, completion: nil)
        }

        self.view.addSubview(image)

    }


    func getPath() -> UIBezierPath {

        let path = UIBezierPath()

        let startPoint = CGPoint.init(x: -30, y: self.view.frame.height / 2)
        path.move(to: startPoint)

        let r = CGFloat(400 * drand48())
        let cp1 = CGPoint.init(x: self.view.frame.width * 0.33, y: self.view.frame.height * 0.25 - r)
        let cp2 = CGPoint.init(x: self.view.frame.width * 0.66, y: self.view.frame.height * 0.75 + r)
        let endPoint = CGPoint.init(x: self.view.frame.width + 30, y: self.view.frame.height / 2)

        path.addCurve(to: endPoint, controlPoint1: cp1, controlPoint2: cp2)

        return path

    }

}


extension UIView {

    @discardableResult
    func makeSquare() -> NSLayoutConstraint {
        self.turnOffMaskResizing()
        let constraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0)
        NSLayoutConstraint.activate([constraint])
        return constraint
    }


    @discardableResult
    func centerHorizontallyTo(_ toItem: UIView, padding: CGFloat) -> NSLayoutConstraint {
        self.turnOffMaskResizing()
        let constraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: toItem, attribute: NSLayoutAttribute.centerX, multiplier: 1.0, constant: padding)
        NSLayoutConstraint.activate([constraint])
        return constraint
    }


    @discardableResult
    func constrainHeight(_ height: CGFloat, priority: UILayoutPriority = 1000) -> NSLayoutConstraint {
        self.turnOffMaskResizing()
        let constraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.height, multiplier: 0, constant: height)
        constraint.priority = priority
        NSLayoutConstraint.activate([constraint])
        return constraint
    }



    @discardableResult
    func constrainWidth(_ width: CGFloat) -> [NSLayoutConstraint] {
        self.turnOffMaskResizing()
        let constraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[item(width)]", metrics: ["width" : width], views: ["item" : self])
        NSLayoutConstraint.activate(constraints)
        return constraints
    }


    func turnOffMaskResizing() {
        self.translatesAutoresizingMaskIntoConstraints = false
    }


}

推荐答案

这是我的概念的证明,是从 https ://stackoverflow.com/a/33076583/4284508 .这可以满足您的需求.这有点混乱,所以不要把它当作完成的事情.我使用您的类来获取另一个蒙版的框架和半径,因此您将需要以某种方式摆脱它并以其他方式计算半径和框架.但这会有用

This is proof of my concept, took and reworked CircleMaskView from https://stackoverflow.com/a/33076583/4284508. This does what you need. It is little bit mess, so do not take it as a done thing. I use your class to get frame and radius for the other mask, so you will need to get rid of it somehow and compute radius and frame in some other manner. But it will serve

/// Apply a circle mask on a target view. You can customize radius, color and opacity of the mask.
class CircleMaskView {

    private var fillLayer = CAShapeLayer()
    var target: UIView?

    var fillColor: UIColor = UIColor.gray {
        didSet {
            self.fillLayer.fillColor = self.fillColor.cgColor
        }
    }

    var radius: CGFloat? {
        didSet {
            self.draw()
        }
    }

    var opacity: Float = 0.5 {
        didSet {
            self.fillLayer.opacity = self.opacity
        }
    }

    /**
     Constructor

     - parameter drawIn: target view

     - returns: object instance
     */
    init(drawIn: UIView) {
        self.target = drawIn
    }

    /**
     Draw a circle mask on target view
     */
    func draw() {
        guard let target = target else {
            print("target is nil")
            return
        }

        var rad: CGFloat = 0
        let size = target.frame.size
        if let r = self.radius {
            rad = r
        } else {
            rad = min(size.height, size.width)
        }

        let path = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:size.width, height:size.height), cornerRadius: 0.0)
        let circlePath = UIBezierPath(roundedRect: CGRect(x:size.width / 2.0 - rad / 2.0, y:0, width:rad, height:rad), cornerRadius: rad)
        path.append(circlePath)
        path.usesEvenOddFillRule = true

        fillLayer.path = path.cgPath
        fillLayer.fillRule = kCAFillRuleEvenOdd
        fillLayer.fillColor = self.fillColor.cgColor
        fillLayer.opacity = self.opacity
        target.layer.addSublayer(fillLayer)
    }

    func redraw(withCircle circle: UICircle) {
        guard let target = target else {
            print("target is nil")
            return
        }

        var rad: CGFloat = 0
        let size = target.frame.size
        if let r = self.radius {
            rad = r
        } else {
            rad = min(size.height, size.width)
        }

        let path = UIBezierPath(roundedRect: CGRect(x:0, y:0, width:size.width, height:size.height), cornerRadius: 0.0)
        let circlePath = UIBezierPath(roundedRect: circle.frame, cornerRadius: circle.diameterConstraint!.constant)
        path.append(circlePath)
        path.usesEvenOddFillRule = true

        fillLayer.path = path.cgPath
        fillLayer.fillRule = kCAFillRuleEvenOdd
        fillLayer.fillColor = self.fillColor.cgColor
        fillLayer.opacity = self.opacity
        target.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
        target.layer.addSublayer(fillLayer)
    }

    /**
     Remove circle mask
     */


    func remove() {
        self.fillLayer.removeFromSuperlayer()
    }

}


var circle = UICircle()
var circleMask: CircleMaskView?
var subviewC = UIView()

override func viewDidLoad() {
    super.viewDidLoad()
    self.subviewC.clipsToBounds = true

    let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tap.delegate = self
    self.view.addGestureRecognizer(tap)
    view.backgroundColor = UIColor.init(red: 48/255, green: 242/255, blue: 194/255, alpha: 1)
    subviewC.backgroundColor = .clear
    subviewC.frame = view.frame
    self.view.addSubview(subviewC)
    self.view.addSubview(circle)
    circle.backgroundColor = .clear
    setupCircle()
}

func setupCircle() {
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
    panGesture.delegate = self
    self.subviewC.addGestureRecognizer(panGesture)

    circleMask = CircleMaskView(drawIn: subviewC)
    circleMask?.opacity = 1.0
    circleMask?.draw()
}

override func viewDidLayoutSubviews() {
    circleMask?.redraw(withCircle: circle)
}


func handlePan(_ pan: UIPanGestureRecognizer) {

    let delta = pan.translation(in: self.view).y
    if pan.state == .began {
        if delta > 0 {
            panStarted = true
            circle.updateSize(-delta)
            circleMask?.redraw(withCircle: circle)
        }
    } else if pan.state == .changed {
        if panStarted {
            circle.updateSize(-delta)
            circleMask?.redraw(withCircle: circle)
        }
    } else if pan.state == .ended || pan.state == .cancelled {
        if panStarted {
            circle.updateSize(self.view.frame.height * 2.1, animated: true)
            circleMask?.redraw(withCircle: circle)
        }
        panStarted = false
    }
    pan.setTranslation(.zero, in: self.view)
}

这篇关于敲击UITapGestureRecognizer后,NSLayoutConsstraint常量不影响视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆