【问题标题】:NSLayoutConsstraint constant not affecting view after UITapGestureRecognizer was tappedNSLayoutConsstraint 常量在 UITapGestureRecognizer 被点击后不影响视图
【发布时间】:2017-09-28 05:02:02
【问题描述】:

我有一个视图 (self.view),使用 UIView.mask 财产。在self.view 上,我安装了UIPanGestureRecognizer,所以当我在屏幕上平移时,遮罩会相应地变得越来越小。此外,我安装在self.viewUITapGestureRecognizer 上,它将动画UIImageViews 添加到屏幕上,并且它们在UIBezierPath 上进行动画处理。我正在使用约束更新蒙版大小。

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

所以我的意思是遮罩视图约束停止影响其视图。这是为什么?谢谢

视频插图: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
    }


}

【问题讨论】:

  • 您的代码引用了一些不在提供的列表中的自定义自动布局方法(例如image.constrainHeight(dim)makeSquare()) - 您有机会添加它们吗?
  • @richt 我可以看到updateSize 确实被调用了,我确实改变了约束常量的值。另外,我添加了您要求的扩展名。

标签: ios swift autolayout uigesturerecognizer uitapgesturerecognizer


【解决方案1】:

我认为这是因为您将新对象添加到该视图会影响约束,并且它们会中断。我的建议是将圆添加为子视图,使其与其他对象无关。

这是我尝试过的,它成功了

    override func viewDidLoad() {
    super.viewDidLoad()

    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.addSubview(circle)
    self.circle.backgroundColor = UIColor.init(red: 48/255, green: 242/255, blue: 194/255, alpha: 1)
    self.circle.clipsToBounds = true
    self.view.addGestureRecognizer(panGesture)

}

编辑:添加了改变层次结构的图像

点击之前

点击后

点击后你的面具似乎被移除了 - 但我不知道如何解决这个问题,仍然看不出你为什么不能添加子视图的原因

【讨论】:

  • 谢谢,但我现在检查了它并没有掩盖视图,这是最初的建议。约束也不会中断,控制台中没有中断日志。我看不出UIImageViews 与self.view.mask 视图的关系如何。
  • 看看编辑,你会看到发生了什么。你可以用面具来做,但我可能不会帮你。我的方法行之有效,因此您可以将其保留为备份。
  • 谢谢您,您帮助我意识到mask 视图正在以某种方式发布,但仍然不知道为什么。我没有将它添加为子视图,因为它破坏了我想要实现的整个建议,这与 WhatsApp 中的相机完全一样,可以通过平移手势将其关闭到屏幕底部并掩盖圈外的一切。
  • 好的,可能有适合您的技巧。你需要两件东西,第一件是前面的视图,作为封面,第二件是你的圆圈,它应该是应该有 alpha 0 的掩码。只要确保这个视图在前面。您可能会考虑添加一个子视图,您可以在其中添加移动对象和其他子视图,这将是您的蒙版视图,这样您就可以确保它仍然在所有对象之上。这就是我给你的建议
  • 我认为在这种情况下,mask 视图只会掩盖coverView 并让self.view 在其后面可见。 mask 仅适用于分配给它的视图,不是吗?
【解决方案2】:

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

/// 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)
}

【讨论】:

    猜你喜欢
    • 2013-11-04
    • 2016-11-08
    • 2013-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多