Dziwne czarne rogi podczas korzystania z funkcji draw (_ rect :)

Dec 07 2020

Jeśli umieszczę kod dla mojej funkcji UIBezierPathwewnątrz draw(_ rect:)funkcji, otrzymam te dziwne, bardzo cienkie czarne rogi wokół widoku i ogona. Podczas przeciągania widoku (np. W kontrolerze widoku prezentowanego) te cienkie linie również zaczynają migotać. Zakładam, że to dziwny błąd renderowania. Czy ktoś wie, czy istnieje sposób, aby to naprawić?

class RenderingView: UIView {
    lazy var backgroundView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 8
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private lazy var shadowView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private lazy var textLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Rendering Bug"
        return label
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setup() {
        backgroundColor = .clear
        backgroundView.backgroundColor = .yellow
        layer.borderWidth = 0
        setupLayout()
    }

    private func setupLayout() {
        [shadowView, backgroundView, textLabel].forEach(addSubview)

        backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        backgroundView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

        shadowView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor).isActive = true
        shadowView.topAnchor.constraint(equalTo: backgroundView.topAnchor).isActive = true
        shadowView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor).isActive = true
        shadowView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor).isActive = true

        textLabel.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 10).isActive = true
        textLabel.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: -10).isActive = true
        textLabel.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 10).isActive = true
        textLabel.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: -10).isActive = true
    }

    override func draw(_ rect: CGRect) {
        shapeBackground()
    }

    private func shapeBackground() {
        let tailLayer = CAShapeLayer()

        let bezierPath = UIBezierPath(roundedRect: CGRect(x: backgroundView.bounds.minX,
                                                          y: backgroundView.bounds.minY,
                                                          width: backgroundView.bounds.width,
                                                          height: backgroundView.bounds.height - 12),
                                      cornerRadius: 8)

        let shadowBezierPath = UIBezierPath(roundedRect: CGRect(x: backgroundView.bounds.minX + 5,
                                                                y: backgroundView.bounds.minY + 10,
                                                                width: backgroundView.bounds.width - 10,
                                                                height: backgroundView.bounds.height - 12 - 10),
                                            cornerRadius: 8)

        [bezierPath, shadowBezierPath].forEach {
            $0.move(to: CGPoint(x: backgroundView.bounds.midX - 12, y: backgroundView.bounds.maxY - 12)) $0.addLine(to: CGPoint(x: backgroundView.bounds.midX, y: backgroundView.bounds.maxY))
            $0.addLine(to: CGPoint(x: backgroundView.bounds.midX + 12, y: backgroundView.bounds.maxY - 12)) $0.fill()
            $0.close()
        }

        tailLayer.path = bezierPath.cgPath
        tailLayer.fillColor = UIColor.white.cgColor

        shadowView.layer.shadowPath = shadowBezierPath.cgPath
        shadowView.layer.cornerRadius = 8

        backgroundView.layer.masksToBounds = true
        backgroundView.layer.mask = tailLayer
    }
}

EDYCJA: Okazało się, że musiałem użyć addClip()na ścieżkach beziera, aby pozbyć się tych rogów

Odpowiedzi

2 Eskils Dec 07 2020 at 23:44

Nie jestem pewien, czy to jest to, do czego zmierzasz, ale wygląda to bezbłędnie, gdy wyłączysz shapeBackground()metodę draw(_ rect:)i wprowadzisz niewielkie modyfikacje.

Zmodyfikowałem niektóre z twoich procedur rysowania wewnątrz shapeBackground()i przeniosłem funkcję layoutSubviews()do obliczania położenia ogona z ramy utworzonej przez ograniczenia. Dodałem także kilka zmiennych dla tailWidth, & tailHeight.

Tak jak to:

class RenderingView: UIView {
    lazy var backgroundView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 8
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private lazy var shadowView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private lazy var textLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "No Rendering Bug"
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setup() {
        backgroundColor = .clear
        backgroundView.backgroundColor = .systemTeal
        //backgroundView.frame.size = CGSize(width: 100, height: 100)
        layer.borderWidth = 0
        setupLayout()
    }
    
    private func setupLayout() {
        [shadowView, backgroundView, textLabel].forEach(addSubview)
        
        backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        backgroundView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        
        shadowView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor).isActive = true
        shadowView.topAnchor.constraint(equalTo: backgroundView.topAnchor).isActive = true
        shadowView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor).isActive = true
        shadowView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor).isActive = true
        
        textLabel.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 10).isActive = true
        textLabel.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: -10).isActive = true
        textLabel.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 10).isActive = true
        textLabel.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: -10).isActive = true
        
        
    }
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        shapeBackground(color: UIColor.systemTeal)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
    }
    
    private func shapeBackground(color: UIColor) {
        let tailLayer = CAShapeLayer()
        tailLayer.name = "tailLayer"
        
        let tailWidth: CGFloat = 16
        let tailHeight: CGFloat = 10
        
        let bezierPath = UIBezierPath()
        let shadowBezierPath = UIBezierPath()
        
        [bezierPath, shadowBezierPath].forEach {
            $0.move(to: CGPoint(x: 0, y: 0)) $0.addLine(to: CGPoint(x: tailWidth / 2, y: tailHeight))
            $0.addLine(to: CGPoint(x: tailWidth, y: 0)) $0.fill()
            $0.close()
        }
        
        tailLayer.path = bezierPath.cgPath
        tailLayer.fillColor = color.cgColor
        
        shadowView.layer.shadowPath = shadowBezierPath.cgPath
        shadowView.layer.cornerRadius = 8
        
        print(backgroundView.bounds.width)
        
        tailLayer.frame = CGRect(x: (backgroundView.bounds.width - tailWidth) / 2,
                                 y: backgroundView.bounds.maxY,
                                 width: tailWidth,
                                 height: tailHeight)
        
        backgroundView.layer.masksToBounds = false
        backgroundView.layer.addSublayer(tailLayer)
    }
}