How do I get a UIButton to size itself in a UIStackView?

假装没事ソ 提交于 2020-01-26 04:44:26

问题


I'm creating a stack view as part of a UIControl subclass that successfully lays out n buttons. The problem is, if I inspect the frame of any button, even though I can see their text, it says their width and height are 0.

final class MySegmentedControl: UIControl {

    private var stackView: UIStackView?
    private var selection: UIView? = nil

    var selectedSegmentIndex: Int = 0
    var items: [String] = []

    private var segmentCount: CGFloat {
        CGFloat(items.count)
    }

    private var segmentWidth: CGFloat {
        guard segmentCount > 0 else { return 0 }
        return floor(frame.width / segmentCount)
    }

    init(frame: CGRect, items: [String]) {
        self.items = items.map { $0.uppercased() }
        super.init(frame: frame)
    }

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

    private struct Style {
        static let slateGray40 = UIColor.from(rgb: 0x8A949E)
        static let teal = UIColor.from(rgb: 0x54919F)
    }

    private struct Metrics {
        static let stackViewHeight: CGFloat = 32
        static let barHeight: CGFloat = 4
        static let cornerRadius: CGFloat = 2
    }

    private func createButtons() -> [UIButton] {
        return items.map { item in
            Init(UIButton()) {
                $0.setTitle(item, for: .normal)
                $0.setTitleColor(Style.slateGray40, for: .normal)
                $0.titleLabel?.textAlignment = .center
                $0.titleLabel?.font = UIFont.systemFont(ofSize: 14)
                $0.addTarget(self, action: #selector(buttonTapped(_:)), for: .primaryActionTriggered)
                $0.layer.borderWidth = 1
                $0.layer.borderColor = UIColor.red.cgColor
            }
        }
    }

    func layout() {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.alignment = .fill
        stackView.distribution = .fillEqually
        stackView.spacing = 0
        for (index, button) in createButtons().enumerated() {
            button.frame = CGRect(
                x: self.xPosition(forSegmentIndex: index),
                y: 0,
                width: Metrics.segmentWidth,
                height: Metrics.stackViewHeight
            )
            stackView.addArrangedSubview(button)
        }
        addSubview(stackView)
        stackView.frame = CGRect(
            x: 0,
            y: 0,
            width: frame.width,
            height: Metrics.stackViewHeight
        )
        self.stackView = stackView

        let colorBar = UIView(frame: CGRect(
            x: 0,
            y: 32,
            width: frame.width,
            height: Metrics.barHeight
        ))
        colorBar.backgroundColor = UIColor.from(rgb: 0xffffff).withAlphaComponent(0.1)
        colorBar.clipsToBounds = true
        colorBar.layer.cornerRadius = Metrics.cornerRadius
        addSubview(colorBar)

        let selection = UIView(frame: CGRect(
            x: colorBar.bounds.minX,
            y: colorBar.bounds.minY,
            width: segmentWidth,
            height: Metrics.barHeight
        ))
        selection.clipsToBounds = true
        selection.backgroundColor = Style.teal
        selection.layer.cornerRadius = Metrics.cornerRadius
        colorBar.addSubview(selection)
        self.selection = selection

        stackView.arrangedSubviews.forEach {
            print("Frame", $0.frame)
        }

        didSelectIndex(0)
    }

    @objc func buttonTapped(_ sender: UIButton) {
        print("tapped a thing")

        guard
            let stackView = stackView,
            let senderButtonText = sender.titleLabel?.text,
            let buttons = stackView.arrangedSubviews as? [UIButton]
        else { return }

        if let index = buttons
            .compactMap({ $0.titleLabel?.text })
            .firstIndex(of: senderButtonText) {
                didSelectIndex(index)
            }
    }

    private func didSelectIndex(_ index: Int) {
        setSegmentAsActive(index) // below?
        animateBar(to: index)
    }

    private func setSegmentAsActive(_ index: Int) {
        guard
            index > 0,
            index < Int(segmentCount),
            let stackView = stackView,
            let buttons = stackView.arrangedSubviews as? [UIButton]
        else { return }
        buttons[index].setTitleColor(Style.teal, for: .normal)
    }

    private func animateBar(to index: Int) {
        guard index > 0 && index < items.count else { return }
        UIView.animate(withDuration: 1) {
            self.selection?.frame.origin = CGPoint(
                x: self.xPosition(forSegmentIndex: index),
                y: 0
            )
        }
    }

    private func xPosition(forSegmentIndex index: Int) -> CGFloat {
        guard index > 0 && index < items.count else { return 0 }
        return segmentWidth * CGFloat(index)
    }

}

And then the implementation in a view controller:

// In `viewDidLoad`:

let seg = MySegmentedControl(frame: CGRect(x: 0, y: 100, width: 200, height: 0), items: ["Executive", "Polling"])
view.addSubview(seg)
seg.center = CGPoint(x: view.frame.midX, y: seg.frame.minY)
seg.layout()

How can I make the button have a width and a height so they're tappable?

来源:https://stackoverflow.com/questions/59672144/how-do-i-get-a-uibutton-to-size-itself-in-a-uistackview

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!