UILabel displays colors beneath it

48 Views Asked by At

I have a non-transparent UIView with UILabel of white non-transparent color. I display it as a subview in some view with border. And I can see that border in my UILabel's letters...

"y" letter has blended color with my border

By the way, I've tried to overlap content in my superview and it's been rendered absolutely fine - so the issue is existing only with the border.

I assume there's something wrong on the render layer when it blends border of the superview and letters of the UILabel

Code for TagView and it's superview:

import UIKit
import PlaygroundSupport

enum TagType {
    case minimal
    case rounded
    case circle
}

class TagView: UIView {
    var tagType: TagType = .rounded { didSet { updateUI() }}
    var isSelected = false { didSet { updateUI() }}

    private(set) lazy var label: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        label.clipsToBounds = true
        label.font = .systemFont(ofSize: 15, weight: .semibold)
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    private var leadingLabelAnchor: NSLayoutConstraint?
    private var trailingLabelAnchor: NSLayoutConstraint?
    
    private var topLabelAnchor: NSLayoutConstraint?
    private var bottomLabelAnchor: NSLayoutConstraint?

    var constantValue: CGFloat {
        tagType == .circle ? 12 : 8
    }
    
    var verticalInsetTop: CGFloat {
        tagType == .minimal ? 3 : 5
    }
    
    var verticalInsetBottom: CGFloat {
        tagType == .minimal ? 3 : 7
    }

    private func setup() {
        clipsToBounds = true
        addSubview(label)
        leadingLabelAnchor = label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: constantValue)
        trailingLabelAnchor = label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -constantValue)
        leadingLabelAnchor?.priority = .defaultHigh
        leadingLabelAnchor?.isActive = true
        trailingLabelAnchor?.priority = .defaultHigh
        trailingLabelAnchor?.isActive = true
        
        topLabelAnchor = label.topAnchor.constraint(equalTo: topAnchor, constant: verticalInsetTop)
        topLabelAnchor?.isActive = true
        
        bottomLabelAnchor = label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalInsetBottom)
        bottomLabelAnchor?.isActive = true
        
        layer.borderColor = UIColor.blue.cgColor
        layer.borderWidth = 1
        updateUI()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        updateUI()
    }
    
    private func updateUI() {
        leadingLabelAnchor?.constant = constantValue
        trailingLabelAnchor?.constant = -constantValue
        
        topLabelAnchor?.constant = verticalInsetTop
        bottomLabelAnchor?.constant = -verticalInsetBottom
        
        backgroundColor = isSelected ? .blue : .clear
        label.backgroundColor = backgroundColor
        label.textColor = isSelected ? .white : .blue
        
        switch tagType {
        case .minimal:
            layer.cornerRadius = 10
        case .rounded:
            layer.cornerRadius = 12
        case .circle:
            layer.cornerRadius = bounds.height / 2
        }
    }

    func configure(with text: String) {
        label.text = text
    }
}

final class MyControl: UIControl {
    private lazy var tagView: TagView = {
        let view = TagView()
        return view
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setup() {
        tagView.translatesAutoresizingMaskIntoConstraints = false
        tagView.isSelected = true
        backgroundColor = .clear
        layer.cornerRadius = 10
        layer.borderWidth = 2
        layer.borderColor = UIColor.blue.cgColor
        
        addSubview(tagView)
        
        NSLayoutConstraint.activate(
            [
                tagView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
                tagView.topAnchor.constraint(equalTo: topAnchor, constant: -16)
            ]
        )
        
        tagView.configure(with: "7 dayyyyy")
    }
}

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let label = UILabel()
        label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
        label.text = "Hello World!"
        label.textColor = .black
        
        let myControl = MyControl()
        myControl.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(myControl)
        
        NSLayoutConstraint.activate([
            myControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myControl.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            myControl.widthAnchor.constraint(equalToConstant: 300),
            myControl.heightAnchor.constraint(equalToConstant: 50),
        ])
        
        view.addSubview(label)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
2

There are 2 best solutions below

0
Evgenii Shishko On

Only workaround I found for now, not to apply to parent view's layer border width and color, but to add additional subview with border to this parent views so the tag view is not a subview of a new background bordered view:

final class MyControl: UIControl {
...
    private func setup() {
        // layer.cornerRadius = 10
        // layer.borderWidth = 2
        // layer.borderColor = UIColor.blue.cgColor

        let backgroundView = UIView()
        backgroundView.backgroundColor = .white
        backgroundView.translatesAutoresizingMaskIntoConstraints = false

        backgroundView.layer.cornerRadius = 10
        backgroundView.layer.borderWidth = 2
        backgroundView.layer.borderColor = UIColor.blue.cgColor
        
        addSubview(backgroundView)
        addSubview(tagView)

    }
}
0
clawesome On

You're drawing on top of your view. If you add a subview and do the drawing on it, you won't have the overlapping issue.

    final class MyControl: UIControl {
        private lazy var borderView: UIView = {
            let view = UIView()
            return view
        }()
      
        private lazy var tagView: TagView = {
            let view = TagView()
            return view
        }()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private func setup() {
            borderView.translatesAutoresizingMaskIntoConstraints = false
            borderView.backgroundColor = .clear
            borderView.layer.cornerRadius = 10
            borderView.layer.borderWidth = 2
            borderView.layer.borderColor = UIColor.blue.cgColor
            addSubview(borderView)
          
            tagView.translatesAutoresizingMaskIntoConstraints = false
            tagView.isSelected = true
            backgroundColor = .clear
            
            addSubview(tagView)
            
            NSLayoutConstraint.activate(
                [
                    borderView.leadingAnchor.constraint(equalTo: leadingAnchor),
                    borderView.trailingAnchor.constraint(equalTo: trailingAnchor),
                    borderView.topAnchor.constraint(equalTo: topAnchor),
                    borderView.bottomAnchor.constraint(equalTo: bottomAnchor),
                  
                    tagView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
                    tagView.topAnchor.constraint(equalTo: topAnchor, constant: -16)
                ]
            )
            
            tagView.configure(with: "7 dayyyyy")
        }
    }