我有一个视图(self.view
)被另一个视图(不是图层)掩盖了
UIView.mask
财产。在self.view
我安装了一个UIPanGestureRecognizer
所以当我在屏幕上平移时,屏幕会相应变小和变大。另外,我安装在self.view
和UITapGestureRecognizer
上,可以将动画UIImageView
添加到屏幕上,并在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
}
}
答案 0 :(得分:0)
我认为这是因为你向该视图添加新对象会影响约束,并且它们会中断。我建议将圆圈添加为子视图,以便与其他对象无关。
这是我尝试过的并且有效
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)
}
编辑:添加了您的层次结构中发生变化的图像
之前点击后你的面具似乎被移除 - 但我不知道如何解决这个问题,仍然看不出为什么你不能添加子视图
答案 1 :(得分:0)
这是我的概念的证明,从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)
}