如何在 Swift 中创建自定义细分控件

How to create a custom segment control in Swift

我想创建一个像屏幕截图中那样工作的段控件。 所选段应根据段标题文本加下划线。我已经搜索过了,但没有找到任何第三方解决方案。 那么如何开发这种类型的段控件呢?

在这里你可以看到底部的线只延伸到选定的线段。

    import UIKit

extension UIView {
    func constraintsEqualToSuperView() {
        if let superview = self.superview {
            NSLayoutConstraint.activate(
                [
                    self.topAnchor.constraint(
                        equalTo: superview.topAnchor
                    ),
                    self.bottomAnchor.constraint(
                        equalTo: superview.bottomAnchor
                    ),
                    self.leadingAnchor.constraint(
                        equalTo: superview.leadingAnchor
                    ),
                    self.trailingAnchor.constraint(
                        equalTo: superview.trailingAnchor
                    )
                ]
            )
        }
    }
}

protocol ButtonsViewDelegate: class {
    func didButtonTap(buttonView: ButtonsView, index: Int)
}

class ButtonsView: UIView {

    fileprivate let stackView         = UIStackView()
    fileprivate var array             = [String]()
    fileprivate var buttonArray       = [UIButton]()
    fileprivate let baseTag = 300
    weak var delegate: ButtonsViewDelegate?

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

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

    fileprivate func setupUI () {
        setupStackView()
        setupButton()
    }


    convenience init(buttons: [String]) {
        self.init()
        array = buttons
        setupUI()
        //selectButton(atIndex: 0)
    }


    fileprivate func setupStackView() {
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.distribution = .fillEqually
        stackView.constraintsEqualToSuperView()
    }

    fileprivate func setupButton() {
        for (i,string) in array.enumerated() {
            let button = UIButton()
            button.setTitle(string.uppercased(), for: .normal)
//            button.backgroundColor = UIColor.lightBackgroundColor().lightened(by: 0.2)
            button.translatesAutoresizingMaskIntoConstraints = false
            button.addTarget(
                self,
                action: #selector(buttonClicked(sender:)),
                for: .touchUpInside
            )
            button.setTitleColor(
                .black,
                for: .normal
            )
            button.titleLabel?.font = UIFont.systemFont(
                ofSize: 14,
                weight: UIFont.Weight.bold
            )

//            let view = UIView.init(frame: CGRect.init(x: 0, y: button.frame.size.height - 1, width: button.frame.size.width, height: 1))

            let view = UIView()
            view.translatesAutoresizingMaskIntoConstraints = false
            view.backgroundColor = UIColor.clear
            view.tag = baseTag + i
            button.addSubview(view)
            NSLayoutConstraint.activate([
                view.leadingAnchor.constraint(
                    equalTo: button.leadingAnchor
                ),
                view.trailingAnchor.constraint(
                    equalTo: button.trailingAnchor
                ),
                view.bottomAnchor.constraint(
                    equalTo: button.bottomAnchor
                ),
                view.heightAnchor.constraint(
                    equalToConstant: 1
                )
            ])

            stackView.addArrangedSubview(button)
            buttonArray.append(button)
        }
    }

    func selectButton(atIndex index: Int) {
        if index <= buttonArray.count {
            buttonClicked(sender: buttonArray[index])
        }
    }

    @objc private func buttonClicked(sender: UIButton) {
        for button in buttonArray {
            if button == sender {
                button.setTitleColor(
                    UIColor.darkGray,
                    for: .normal
                )
               setUpBottomLine(button: button)
            }else{
                button.setTitleColor(
                    .black,
                    for: .normal
                )
               hideBottomLine(button: button)
            }
        }

        if let index = buttonArray.index(of: sender) {
            delegate?.didButtonTap(buttonView: self, index: index)
        }
    }

    private func setUpBottomLine(button: UIButton) {
        if let index = buttonArray.index(of: button) {
            if let view = button.viewWithTag(baseTag + index) {
                view.backgroundColor = UIColor.red
            }
        }
    }

    private func hideBottomLine(button: UIButton) {
        if let index = buttonArray.index(of: button) {
            if let view = button.viewWithTag(baseTag + index) {
                view.backgroundColor = .clear
            }
        }
    }
}

//how to use
let durationBtns = ButtonsView(buttons: [
    NSLocalizedString(
        "day",
        comment: ""
    ),
    NSLocalizedString(
        "week",
        comment: ""
    ),
    NSLocalizedString(
        "month",
        comment: ""
    ),
    NSLocalizedString(
        "year",
        comment: ""
    )
    ])
durationButtons = durationBtns
durationButtons.selectButton(atIndex: 0)
durationBtns.delegate = self

//handle buttton tap
extension viewController: ButtonsViewDelegate {
    func didButtonTap(buttonView: ButtonsView, index: Int) {
        print(index.description)
    }
}

GitHub 中有一个名为 PageMenu 的开源项目。请看,你甚至可以自定义源文件CAPSPageMenu

https://github.com/PageMenu/PageMenu

要更新选择发线的宽度,请启用以下 属性。

menuItemWidthBasedOnTitleTextWidth

代码:

let parameters: [CAPSPageMenuOption] = [ 
... 
.menuItemWidthBasedOnTitleTextWidth(true),
....]

// Initialize scroll menu
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height), pageMenuOptions: parameters)

请在项目中尝试 PageMenuDemoStoryboard demo 并更新 parameters 如上代码所示。