добавление подвида к представлению, имеющему вид маски, удаляет маску из него

как следует из названия, у меня есть self.view, который я добавляю к его mask (ссылка) свойство другого представления, но когда я добавляю дополнительные представления к self.view с помощью addSubview, маска удаляется. почему это? Благодарность

изначально у меня была эта проблема, и я понял, что представление mask освобождается.

в моем примере я добавляю анимированные UIImageView, которые анимируются через UIBezierPath, когда я нажимаю на экран в addView.

вот код:

protocol UICircleMaskDelegate {

    func circleMaskCompletion()

}

class UICircleMask: UIView {

    var delegate: UICircleMaskDelegate?
    var gestureDelegate: UIGestureRecognizerDelegate?

    init(gestureDelegate: UIGestureRecognizerDelegate? = nil) {
        super.init(frame: .zero)
        self.gestureDelegate = gestureDelegate
        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 = Double((UIScreen.main.bounds.height - self.frame.height / 2.1) / 600)// duration = way / speed
            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
                    self.delegate?.circleMaskCompletion()
                }
            })
        } else {
            let newSize = diameterConstraint!.constant + (delta * 2.85)
            if newSize > 60 && newSize < UIScreen.main.bounds.height * 2.1 {
                diameterConstraint?.constant = newSize
            }
        }

    }

    var panStarted = false

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

    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)
            //
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
            panGesture.delegate = gestureDelegate
            self.superview?.addGestureRecognizer(panGesture)
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.layer.cornerRadius = self.frame.width / 2
    }

}


class ViewController: UIViewController, UIGestureRecognizerDelegate, UICircleMaskDelegate {

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

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

    func circleMaskCompletion() {
//        print("nana")
    }

    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)

        let circleMask = UICircleMask(gestureDelegate: self)
        circleMask.delegate = self
        self.view.mask = circleMask

    }

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

    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)
        self.view.sendSubview(toBack: 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 {

    func turnOffMaskResizing() {
        self.translatesAutoresizingMaskIntoConstraints = false
    }


    @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
    }


}

person user1974368    schedule 01.05.2017    source источник
comment
Пожалуйста, включите какой-нибудь код, который позволит людям воспроизвести вашу проблему на игровой площадке или подобном   -  person jrturton    schedule 01.05.2017
comment
@jrturton добавил код, спасибо   -  person user1974368    schedule 01.05.2017


Ответы (1)


Ваша маска там, просто она в 2,1 раза больше вашего поля зрения, поэтому она всегда закрывает все, чтобы вы могли видеть все. У вас ошибка в коде:

if pan.state == .began {
        if delta > 0 {
            panStarted = true
            circle.updateSize(-delta)
        }

В .began никогда не будет перевода, поэтому этот код никогда не будет обработан, что означает, что больше ничего в вашем обработчике не обрабатывается. Этот код должен просто установить флаг panStarted.

Чтобы маска вступила в силу, вы должны перетащить ее довольно далеко, а когда вы отпустите ее, она снова увеличится в 2,1 раза.

Вы можете легко увидеть это, добавив круг в качестве подпредставления вместо маски. Замена всех значений 2.1 на 0.5 дает такой эффект:

введите здесь описание изображения

Таким образом, нет ничего плохого в вашей маске, но в окружающем коде.

person jrturton    schedule 01.05.2017
comment
Вы видели здесь, что мне удалось добиться желаемого поведения до добавления просмотров в self.view? я связал это в моем предыдущем посте я связал выше. Я использую 2.1, потому что у меня есть круг, центр которого находится внизу self.view, и если я хочу, чтобы он покрывал весь вид, мне нужно, чтобы радиус был таким же высоким, как вид. когда я смотрю на свою иерархию представлений до и после добавления UIImageViews на экран, маска есть до и исчезает после. - person user1974368; 01.05.2017
comment
Даже оставив 2.1, вы все равно можете увидеть, как маска вступает в силу, если вы продвинетесь достаточно далеко и исправите ошибку, о которой я упоминал, но она всегда возвращается, когда панорамирование закончено. Ваша проблема не в исчезающей маске. Маски не исчезают при добавлении подвидов. - person jrturton; 01.05.2017
comment
есть перевод в .begin. поэтому я не понимаю, почему вы говорите, что есть ошибка. и я запрограммировал его, чтобы он возвращался к полному размеру, когда кастрюля закончилась. когда я смотрю на режим отладки View UI Hierarchy до и после добавления представлений на экран, маска была раньше и исчезала после. Я понимаю, что для этого нет логической причины, но это случается со мной. - person user1974368; 01.05.2017
comment
Я запускаю его на игровой площадке, так что, возможно, в этом есть разница, но для меня в начале никогда не было дельты. - person jrturton; 01.05.2017
comment
Честно говоря, я думаю, что вам лучше использовать маску слоя, чем маску вида, кажется, это добавляет столько сложностей, пытаясь использовать автомакет для обработки этого, и иметь несколько анимаций... просто используйте CAShapeLayer с круговым путем, как маску слоя основного вида и анимируйте путь к ней. - person jrturton; 01.05.2017
comment
а, хорошо, нет, панорамирование работает нормально, пока я не коснусь экрана. маска сама изменяет размер, и все в порядке, как в прикрепленном видео: youtube.com/watch?v =UtNuc8nicgs после нажатия по неизвестной причине представление маски больше не отображается в моем представлении. - person user1974368; 01.05.2017
comment
да, я пробовал это, но у меня возникла проблема с изменением размера маски, потому что мне нужно было использовать преобразование масштаба, чтобы изменить ее размер, и это было слишком сложно для меня, также у меня были проблемы с удержанием слоя по центру внизу. - person user1974368; 01.05.2017
comment
Ну, действительно ли маска является частью иерархии представлений? Я заставил его работать после нажатия, заставив макет круга во время метода изменения размера, но это сделало другие вещи немного странными. Я не думаю, что автомакет очень хорошо работает с масками, поэтому я предлагаю использовать слои. - person jrturton; 01.05.2017
comment
да, когда вы назначаете view.mask = maskView, тогда maskView становится прямым подпредставлением view. что значит форсировать раскладку? в методе изменения размера вы добавили self.layoutIfNeeded()? - person user1974368; 01.05.2017
comment
ну, очевидно, представление маски не является частью иерархии представлений, как я уже сказал, хотя в некоторых случаях оно добавляется к подпредставлениям self.view. да, вместо этого я использую многоуровневый подход, спасибо за помощь :) - person user1974368; 02.05.2017