iOS UIB-кнопки в StackView не используются - PullRequest
1 голос
/ 11 апреля 2020

У меня есть кнопки внутри класса ButtonView, чтобы добавить фон и метку. Эти ButtonViews добавляются к UIStackView, который является представлением в PlayOverlay классе. PlayOverlay служит родительским классом для различных типов оверлеев, в этом примере я включил только BeginOverlay.

BeginOverlay, представленный PlaySecVC. Кнопки в BeginOverlay не могут быть нажаты по какой-то причине. Я попробовал UIDebugging в XCode, чтобы увидеть, есть ли перед ними какие-либо представления, а их нет. Это самые передовые виды. Я получаю одну ошибку, когда UIDebugging говорит мне, что ширина ButtonView, высота и x и y неоднозначны. Это потому, что у меня нет никаких ограничений, как показано ниже, так как они выложены в виде стека. Как сделать эти кнопки нажатыми?

ViewController:

import UIKit

fileprivate struct scvc {
    static let overlayWidth: CGFloat = 330
    static let overlayFromCenter: CGFloat = 25
    static let hotspotSize: CGFloat = 30
    static let detailHeight: CGFloat = 214
    static let detailWidth: CGFloat = 500
    static let arrowMargin: CGFloat = 9
    static let arrowSize: CGFloat = 56
    static let zoomRect: CGFloat = 200
    static let overlayHeight: CGFloat = 267
}

enum playState {
    case play
    case shuffle
    case favorites
}

protocol PlaySec: class {
}

class PlaySecVC: UIViewController, PlaySec {

    // MARK: UIComponents
    lazy var scrollView: UIScrollView = {
        let _scrollView = UIScrollView(frame: .zero)
        _scrollView.translatesAutoresizingMaskIntoConstraints = false
        _scrollView.clipsToBounds = false
        //_scrollView.isUserInteractionEnabled = true

        return _scrollView
    }()

    lazy var imageView: UIImageView = {
        let _imageView = UIImageView(frame: .zero)
        _imageView.translatesAutoresizingMaskIntoConstraints = false
        _imageView.contentMode = .scaleAspectFit
        //_imageView.isUserInteractionEnabled = true

        return _imageView
    }()

    lazy var beginOverlay: BeginOverlay = {
        let _beginOverlay = BeginOverlay(frame: .zero)
        _beginOverlay.translatesAutoresizingMaskIntoConstraints = false

        return _beginOverlay
    }()

    lazy var detailView: UIView = {
        let _detailView = UIView(frame: .zero)
        _detailView.translatesAutoresizingMaskIntoConstraints = false
        _detailView.isHidden = true
        //_detailView.isUserInteractionEnabled = false

        return _detailView
    }()

    lazy var leftArrow: UIButton = {
        let _leftArrow = UIButton(frame: .zero)
        _leftArrow.translatesAutoresizingMaskIntoConstraints = false
        _leftArrow.isHidden = false
        _leftArrow.setImage(#imageLiteral(resourceName: "Left-Arrow-Outline"), for: .normal)

        return _leftArrow
    }()

    lazy var rightArrow: UIButton = {
        let _rightArrow = UIButton(frame: .zero)
        _rightArrow.translatesAutoresizingMaskIntoConstraints = false
        _rightArrow.isHidden = false
        _rightArrow.setImage(#imageLiteral(resourceName: "Right-Arrow-Outline"), for: .normal)

        return _rightArrow
    }()

    var state: playState = .play

    // MARK: Setup

    private func setup() {
        let viewController = self
    }

    private func setupConstraints() {
        view.addSubview(scrollView)
        scrollView.addSubview(imageView)
        view.addSubview(detailView)
        view.addSubview(beginOverlay)
        view.addSubview(leftArrow)
        view.addSubview(rightArrow)

        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),

            beginOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            beginOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
            beginOverlay.widthAnchor.constraint(equalToConstant: scvc.overlayWidth),
            beginOverlay.heightAnchor.constraint(equalToConstant: scvc.overlayHeight),

            detailView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            detailView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            detailView.heightAnchor.constraint(equalToConstant: scvc.detailHeight),
            detailView.widthAnchor.constraint(equalToConstant: scvc.detailWidth),

            leftArrow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: scvc.arrowMargin),
            leftArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            leftArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
            leftArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),

            rightArrow.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -1 * scvc.arrowMargin),
            rightArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            rightArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
            rightArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
        ])

    }

    func favorite() {
    }

    func play() {
        state = .play
    }

    func favoritesPlay() {
        play()
        state = .favorites
    }

    func shufflePlay() {
        play()
        state = .shuffle
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
        setupConstraints()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        /*var touch: UITouch? = touches.first

        if (touch?.view != detailView && !detailView.isHidden) {
            detailView.isHidden = true
        }*/
        super.touchesBegan(touches, with: event)
    }
}

Наложение:

fileprivate struct sizeConstants {
    static let pillHeight: CGFloat = 38
    static let pillCornerRadius: CGFloat = sizeConstants.pillHeight / 2
    static let titleFontSize: CGFloat = 13
    static let detailFontSize: CGFloat = 10
    static let imageCenterToLeading: CGFloat = 3
    static let circleDiameter: CGFloat = 66
    static let circleRadius: CGFloat = sizeConstants.circleDiameter / 2
    static let buttonTextHPadding: CGFloat = 4
    static let buttonTextVPadding: CGFloat = 2
    static let badgeSpacing: CGFloat = 5.5
    static let titleBadgeSpacing: CGFloat = 19
    static let badgeImageSize: CGFloat = 32
    static let badgeTextFromCenter: CGFloat = 0
    static let badgeTextToImage: CGFloat = 8
    static let buttonBackgroundToText: CGFloat = 6
    static let circleButtonSize: CGFloat = 48
    static let rectButtonWidth: CGFloat = 36
    static let rectButtonHeight: CGFloat = 39
    static let badgesToButtons: CGFloat = 21.5
}


class ButtonView: UIView {
    lazy var buttonBackgroundView: UIView = {
        let _buttonBackgroundView = UIView(frame: .zero)
        _buttonBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        _buttonBackgroundView.backgroundColor = .black
        _buttonBackgroundView.layer.cornerRadius = sizeConstants.circleRadius

        return _buttonBackgroundView
    }()

    lazy var textBackgroundView: UIView = {
        let _textBackgroundView = UIView(frame: .zero)
        _textBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        _textBackgroundView.backgroundColor = .black
        _textBackgroundView.layer.cornerRadius = _textBackgroundView.frame.height / 2

        return _textBackgroundView
    }()

    lazy var button: UIButton = {
        let _button = UIButton(frame: .zero)
        _button.translatesAutoresizingMaskIntoConstraints = false

        return _button
    }()

    lazy var label: UILabel = {
        let _label = UILabel(frame: .zero)
        _label.translatesAutoresizingMaskIntoConstraints = false
        _label.font = .systemFont(ofSize: 15)
        _label.textColor = .white

        return _label
    }()

    var isRect: Bool = false

    convenience init(rect: Bool) {
        self.init(frame: .zero)
        self.isRect = rect
        setupViews()
    }

    override func updateConstraints() {
        NSLayoutConstraint.activate([
            buttonBackgroundView.topAnchor.constraint(equalTo: topAnchor),
            buttonBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
            buttonBackgroundView.widthAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
            buttonBackgroundView.heightAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),

            button.centerXAnchor.constraint(equalTo: buttonBackgroundView.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: buttonBackgroundView.centerYAnchor),

            textBackgroundView.topAnchor.constraint(equalTo: buttonBackgroundView.bottomAnchor, constant: sizeConstants.buttonBackgroundToText),
            textBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
            textBackgroundView.heightAnchor.constraint(equalTo: label.heightAnchor, constant: sizeConstants.buttonTextVPadding),
            textBackgroundView.widthAnchor.constraint(equalTo: label.widthAnchor, constant: sizeConstants.buttonTextHPadding),

            label.centerXAnchor.constraint(equalTo: centerXAnchor),
            label.centerYAnchor.constraint(equalTo: textBackgroundView.centerYAnchor),
        ])

        if (isRect) {
            NSLayoutConstraint.activate([
                button.widthAnchor.constraint(equalToConstant: sizeConstants.rectButtonWidth),
                button.heightAnchor.constraint(equalToConstant: sizeConstants.rectButtonHeight),
            ])
        } else {
            NSLayoutConstraint.activate([
                button.widthAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
                button.heightAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
            ])
        }
        super.updateConstraints()
    }

    private func setupViews() {
        addSubview(buttonBackgroundView)
        addSubview(textBackgroundView)
        addSubview(label)
        addSubview(button)

        label.sizeToFit()

        setNeedsUpdateConstraints()
    }

    func setButtonProps(image: UIImage, text: String, target: Any, selector: Selector) {
        self.button.addTarget(target, action: selector, for: .touchUpInside)
        self.button.setImage(image, for: .normal)
        self.label.text = text
    }
    @objc private func tapped() {
        print("tapped")
    }
}

class PlayOverlay: UIView {

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

    lazy var badgeStackView: UIStackView = {
        let _badgeStackView = UIStackView(frame: .zero)
        _badgeStackView.translatesAutoresizingMaskIntoConstraints = false
        _badgeStackView.axis = .vertical
        _badgeStackView.spacing = sizeConstants.badgeSpacing
        _badgeStackView.distribution = .equalSpacing

        return _badgeStackView
    }()

    lazy var buttonStackView: UIStackView = {
        let _buttonStackView = UIStackView(frame: .zero)
        _buttonStackView.translatesAutoresizingMaskIntoConstraints = false
        _buttonStackView.axis = .horizontal
        _buttonStackView.distribution = .equalSpacing

        return _buttonStackView
    }()

    var vc: PlaySecVC!

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

    override func updateConstraints() {
        NSLayoutConstraint.activate([
            badgeStackView.topAnchor.constraint(equalTo: topAnchor, constant: sizeConstants.titleBadgeSpacing),
            badgeStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            badgeStackView.widthAnchor.constraint(equalTo: widthAnchor),

            buttonStackView.topAnchor.constraint(equalTo: badgeStackView.bottomAnchor, constant: sizeConstants.badgesToButtons),
            buttonStackView.widthAnchor.constraint(equalTo: widthAnchor),
            buttonStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])

        super.updateConstraints()
    }
}

class BeginOverlay: PlayOverlay {

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

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

    private func setupViews() {
        addSubview(badgeStackView)
        addSubview(buttonStackView)

        let shuffleButton = ButtonView(rect: false)
        shuffleButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "SHUFFLE", target: self, selector: #selector(shuffle))

        let favoritesButton = ButtonView(rect: false)
        favoritesButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "FAVORITES", target: self, selector: #selector(favorites))

        let playButton = ButtonView(rect: false)
        playButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "PLAY", target: self, selector: #selector(play))

        buttonStackView.addArrangedSubview(shuffleButton)
        buttonStackView.addArrangedSubview(favoritesButton)
        buttonStackView.addArrangedSubview(playButton)
    }

    @objc private func shuffle() {
        vc.shufflePlay()
    }

    @objc private func favorites() {
        vc.favoritesPlay()
    }

    @objc private func play() {
        vc.play()
    }
}

Ответы [ 2 ]

2 голосов
/ 11 апреля 2020

Я провел некоторое исследование и выяснил, что, поскольку внутри BeginOverlay есть 2 UIStackView, существует неопределенность положения для второго, который содержит 3 UIButton. Изображение ниже может помочь.

UI Debugging Image

0 голосов
/ 21 апреля 2020

Вот место исправления. Протестировано с Xcode 11.4 / iOS 13.4

    lazy var buttonStackView: UIStackView = {
        let _buttonStackView = UIStackView(frame: .zero)
        _buttonStackView.translatesAutoresizingMaskIntoConstraints = false
        _buttonStackView.axis = .horizontal
        _buttonStackView.distribution = .fillEqually // << here !!!

        return _buttonStackView
    }()

demo

Вот полный протестированный модуль (для сравнения, на всякий случай, если я что-то изменил) , Только что создал один проект iOS проекта из шаблона и назначил класс контроллера в раскадровке.

fileprivate struct scvc {
    static let overlayWidth: CGFloat = 330
    static let overlayFromCenter: CGFloat = 25
    static let hotspotSize: CGFloat = 30
    static let detailHeight: CGFloat = 214
    static let detailWidth: CGFloat = 500
    static let arrowMargin: CGFloat = 9
    static let arrowSize: CGFloat = 56
    static let zoomRect: CGFloat = 200
    static let overlayHeight: CGFloat = 267
}

enum playState {
    case play
    case shuffle
    case favorites
}

protocol PlaySec: class {
}

class PlaySecVC: UIViewController, PlaySec {

    // MARK: UIComponents
    lazy var scrollView: UIScrollView = {
        let _scrollView = UIScrollView(frame: .zero)
        _scrollView.translatesAutoresizingMaskIntoConstraints = false
        _scrollView.clipsToBounds = false
        //_scrollView.isUserInteractionEnabled = true

        return _scrollView
    }()

    lazy var imageView: UIImageView = {
        let _imageView = UIImageView(frame: .zero)
        _imageView.translatesAutoresizingMaskIntoConstraints = false
        _imageView.contentMode = .scaleAspectFit
        //_imageView.isUserInteractionEnabled = true

        return _imageView
    }()

    lazy var beginOverlay: BeginOverlay = {
        let _beginOverlay = BeginOverlay(frame: .zero)
        _beginOverlay.translatesAutoresizingMaskIntoConstraints = false
        return _beginOverlay
    }()

    lazy var detailView: UIView = {
        let _detailView = UIView(frame: .zero)
        _detailView.translatesAutoresizingMaskIntoConstraints = false
        _detailView.isHidden = true
        //_detailView.isUserInteractionEnabled = false

        return _detailView
    }()

    lazy var leftArrow: UIButton = {
        let _leftArrow = UIButton(frame: .zero)
        _leftArrow.translatesAutoresizingMaskIntoConstraints = false
        _leftArrow.isHidden = false
        _leftArrow.setImage(UIImage(systemName: "arrow.left")!, for: .normal)

        return _leftArrow
    }()

    lazy var rightArrow: UIButton = {
        let _rightArrow = UIButton(frame: .zero)
        _rightArrow.translatesAutoresizingMaskIntoConstraints = false
        _rightArrow.isHidden = false
        _rightArrow.setImage(UIImage(systemName: "arrow.right")!, for: .normal)

        return _rightArrow
    }()

    var state: playState = .play

    // MARK: Setup

    private func setup() {
//        let viewController = self
        self.beginOverlay.vc = self
    }

    private func setupConstraints() {
        view.addSubview(scrollView)
        scrollView.addSubview(imageView)
        view.addSubview(detailView)
        view.addSubview(beginOverlay)
        view.addSubview(leftArrow)
        view.addSubview(rightArrow)

        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),

            beginOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            beginOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
            beginOverlay.widthAnchor.constraint(equalToConstant: scvc.overlayWidth),
            beginOverlay.heightAnchor.constraint(equalToConstant: scvc.overlayHeight),

            detailView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            detailView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            detailView.heightAnchor.constraint(equalToConstant: scvc.detailHeight),
            detailView.widthAnchor.constraint(equalToConstant: scvc.detailWidth),

            leftArrow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: scvc.arrowMargin),
            leftArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            leftArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
            leftArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),

            rightArrow.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -1 * scvc.arrowMargin),
            rightArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            rightArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
            rightArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
        ])

    }

    func favorite() {
    }

    func play() {
        state = .play
    }

    func favoritesPlay() {
        play()
        state = .favorites
    }

    func shufflePlay() {
        play()
        state = .shuffle
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
        setupConstraints()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        /*var touch: UITouch? = touches.first

        if (touch?.view != detailView && !detailView.isHidden) {
            detailView.isHidden = true
        }*/
        super.touchesBegan(touches, with: event)
    }
}

fileprivate struct sizeConstants {
    static let pillHeight: CGFloat = 38
    static let pillCornerRadius: CGFloat = sizeConstants.pillHeight / 2
    static let titleFontSize: CGFloat = 13
    static let detailFontSize: CGFloat = 10
    static let imageCenterToLeading: CGFloat = 3
    static let circleDiameter: CGFloat = 66
    static let circleRadius: CGFloat = sizeConstants.circleDiameter / 2
    static let buttonTextHPadding: CGFloat = 4
    static let buttonTextVPadding: CGFloat = 2
    static let badgeSpacing: CGFloat = 5.5
    static let titleBadgeSpacing: CGFloat = 19
    static let badgeImageSize: CGFloat = 32
    static let badgeTextFromCenter: CGFloat = 0
    static let badgeTextToImage: CGFloat = 8
    static let buttonBackgroundToText: CGFloat = 6
    static let circleButtonSize: CGFloat = 48
    static let rectButtonWidth: CGFloat = 36
    static let rectButtonHeight: CGFloat = 39
    static let badgesToButtons: CGFloat = 21.5
}


class ButtonView: UIView {
    lazy var buttonBackgroundView: UIView = {
        let _buttonBackgroundView = UIView(frame: .zero)
        _buttonBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        _buttonBackgroundView.backgroundColor = .black
        _buttonBackgroundView.layer.cornerRadius = sizeConstants.circleRadius

        return _buttonBackgroundView
    }()

    lazy var textBackgroundView: UIView = {
        let _textBackgroundView = UIView(frame: .zero)
        _textBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        _textBackgroundView.backgroundColor = .black
        _textBackgroundView.layer.cornerRadius = _textBackgroundView.frame.height / 2

        return _textBackgroundView
    }()

    lazy var button: UIButton = {
        let _button = UIButton(frame: .zero)
        _button.translatesAutoresizingMaskIntoConstraints = false

        return _button
    }()

    lazy var label: UILabel = {
        let _label = UILabel(frame: .zero)
        _label.translatesAutoresizingMaskIntoConstraints = false
        _label.font = .systemFont(ofSize: 15)
        _label.textColor = .white

        return _label
    }()

    var isRect: Bool = false

    convenience init(rect: Bool) {
        self.init(frame: .zero)
        self.isRect = rect
        setupViews()
    }

    override func updateConstraints() {
        NSLayoutConstraint.activate([
            buttonBackgroundView.topAnchor.constraint(equalTo: topAnchor),
            buttonBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
            buttonBackgroundView.widthAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
            buttonBackgroundView.heightAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),

            button.centerXAnchor.constraint(equalTo: buttonBackgroundView.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: buttonBackgroundView.centerYAnchor),

            textBackgroundView.topAnchor.constraint(equalTo: buttonBackgroundView.bottomAnchor, constant: sizeConstants.buttonBackgroundToText),
            textBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
            textBackgroundView.heightAnchor.constraint(equalTo: label.heightAnchor, constant: sizeConstants.buttonTextVPadding),
            textBackgroundView.widthAnchor.constraint(equalTo: label.widthAnchor, constant: sizeConstants.buttonTextHPadding),

            label.centerXAnchor.constraint(equalTo: centerXAnchor),
            label.centerYAnchor.constraint(equalTo: textBackgroundView.centerYAnchor),
        ])

        if (isRect) {
            NSLayoutConstraint.activate([
                button.widthAnchor.constraint(equalToConstant: sizeConstants.rectButtonWidth),
                button.heightAnchor.constraint(equalToConstant: sizeConstants.rectButtonHeight),
            ])
        } else {
            NSLayoutConstraint.activate([
                button.widthAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
                button.heightAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
            ])
        }
        super.updateConstraints()
    }

    private func setupViews() {
        addSubview(buttonBackgroundView)
        addSubview(textBackgroundView)
        addSubview(label)
        addSubview(button)

        label.sizeToFit()

        setNeedsUpdateConstraints()
    }

    func setButtonProps(image: UIImage, text: String, target: Any, selector: Selector) {
        self.button.addTarget(target, action: selector, for: .touchUpInside)
        self.button.setImage(image, for: .normal)
        self.label.text = text
    }
    @objc private func tapped() {
        print("tapped")
    }
}

class PlayOverlay: UIView {

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

    lazy var badgeStackView: UIStackView = {
        let _badgeStackView = UIStackView(frame: .zero)
        _badgeStackView.translatesAutoresizingMaskIntoConstraints = false
        _badgeStackView.axis = .vertical
        _badgeStackView.spacing = sizeConstants.badgeSpacing
        _badgeStackView.distribution = .equalSpacing

        return _badgeStackView
    }()

    lazy var buttonStackView: UIStackView = {
        let _buttonStackView = UIStackView(frame: .zero)
        _buttonStackView.translatesAutoresizingMaskIntoConstraints = false
        _buttonStackView.axis = .horizontal
        _buttonStackView.distribution = .fillEqually

        return _buttonStackView
    }()

    var vc: PlaySecVC!

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

    override func updateConstraints() {
        NSLayoutConstraint.activate([
            badgeStackView.topAnchor.constraint(equalTo: topAnchor, constant: sizeConstants.titleBadgeSpacing),
            badgeStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            badgeStackView.widthAnchor.constraint(equalTo: widthAnchor),

            buttonStackView.topAnchor.constraint(equalTo: badgeStackView.bottomAnchor, constant: sizeConstants.badgesToButtons),
            buttonStackView.widthAnchor.constraint(equalTo: widthAnchor),
            buttonStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])

        super.updateConstraints()
    }
}

class BeginOverlay: PlayOverlay {

    override init(frame: CGRect) {
        super.init(frame: .zero)
        self.setupViews()
    }

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

    private func setupViews() {
        addSubview(badgeStackView)
        addSubview(buttonStackView)

        let shuffleButton = ButtonView(rect: false)
        shuffleButton.setButtonProps(image: UIImage(systemName: "shuffle")!/* replaced with empty image for demo */, text: "SHUFFLE", target: self, selector: #selector(shuffle))

        let favoritesButton = ButtonView(rect: false)
        favoritesButton.setButtonProps(image: UIImage(systemName: "bookmark")!/* replaced with empty image for demo */, text: "FAVORITES", target: self, selector: #selector(favorites))

        let playButton = ButtonView(rect: false)
        playButton.setButtonProps(image: UIImage(systemName: "play")!/* replaced with empty image for demo */, text: "PLAY", target: self, selector: #selector(play))

        buttonStackView.addArrangedSubview(shuffleButton)
        buttonStackView.addArrangedSubview(favoritesButton)
        buttonStackView.addArrangedSubview(playButton)
    }

    @objc private func shuffle() {
        vc.shufflePlay()
    }

    @objc private func favorites() {
        vc.favoritesPlay()
    }

    @objc private func play() {
        vc.play()
    }
}

Примечание: как уже упоминалось, лучше рассмотреть все ограничения и исправить неоднозначности времени выполнения.

...