敲击UITapGestureRecognizer后,NSLayoutConsstraint常量不影响视图 [英] NSLayoutConsstraint constant not affecting view after UITapGestureRecognizer was tapped
问题描述
我有一个视图(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 UIImageView
s 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屋!