更改内部视图的宽度,其中内部视图的宽度取决于外部视图。

时间:2018-10-25 22:17:23

标签: ios swift uiview

如果有人在这里引导我找到正确的解决方案,那将是非常棒的。这是我今天的第二个问题,在这里我很犹豫,但是我正在学习全新的东西(例如创建自己的分段控件等),并且根本不知道从哪里开始。我已经尝试使用有限的知识来进行尽可能多的调试。

这是我在汇总表中遇到的问题。

我有两种看法。内视图的宽度取决于外视图的宽度。因此,如果我更新外部视图的宽度Constraint(使用约束IBoutlet),则外部视图的宽度会发生变化,但内部视图的宽度将与旧视图相同。更改外部视图的宽度约束后,我在外部视图上做了layoutIfNeeded(),但是什么也没发生。

详细信息:

我有一个分段控件(外部视图),选择器的宽度(内部视图)取决于分段控件的总宽度。就像我上面说的,在更改分段控件的总宽度后,选择器的宽度保持不变。选择器的宽度取决于分段控件的宽度。

这是说明我的问题的图像。

enter image description here

您可以看到我的选择器的宽度没有更新。它应该是新分段控件总宽度的一半。

如何更新分段控件的宽度?

基本上,我将分段控件的宽度约束作为VC中的IBOUtlet,然后根据屏幕尺寸增加了它的宽度。但是选择器的宽度保持不变。

这是我用来更改分段控件宽度的代码

 DispatchQueue.main.async {
  self.segmentedWidthControl.constant = UIScreen.main.bounds.width/2
    self.segmentedControl.layoutIfNeeded()
  //self.segmentedControl.updateConstraints() // this doesn't work either
    }

对于自定义分段控件,我遵循了youtube上的教程。 这是代码

@IBDesignable
class SegmentedControl: UIControl{

var buttons = [UIButton]()

var selector: UIView!

var selectSegmentIndex = 0

@IBInspectable
var borderWidth: CGFloat = 0{

    didSet{
        layer.borderWidth = borderWidth
    }
}
@IBInspectable
var borderColor: UIColor = .clear {

    didSet{
        layer.borderColor = borderColor.cgColor
    }
}

override func draw(_ rect: CGRect) {
    layer.cornerRadius = frame.height/2
}

@IBInspectable
var commaSeperatedButtonTitles: String = ""{
    didSet{

        updateView()
    }
}

@IBInspectable
var selectorColor: UIColor = .white{

    didSet{
         updateView()
    }
}

@IBInspectable
var selectorTextColor: UIColor = .white{

    didSet{
        updateView()
    }
}

@IBInspectable
var TextColor: UIColor = .lightGray {

    didSet{
        updateView()

    }
}



func  updateView(){

    buttons.removeAll()

    subviews.forEach { $0.removeFromSuperview()}

    let buttonTitles =  commaSeperatedButtonTitles.components(separatedBy: ",")
    for buttonTitle in buttonTitles{
        let button = UIButton(type: .system)
        button.setTitle(buttonTitle, for: .normal)
        button.setTitleColor(TextColor, for: .normal)
        button.addTarget(self, action: #selector(buttonTapped(button: )), for: .touchUpInside )
        buttons.append(button)
    }

    buttons[0].setTitleColor(selectorTextColor, for: .normal)


    let selectorWidth = frame.width/CGFloat(buttonTitles.count)

    selector = UIView(frame: CGRect(x: 0, y: 0, width: selectorWidth, height: frame.height))
    selector.backgroundColor = selectorColor
    selector.translatesAutoresizingMaskIntoConstraints = false
    selector.layer.cornerRadius = frame.height/2

    addSubview(selector)

    let sv = UIStackView(arrangedSubviews: buttons)
    sv.axis = .horizontal
    sv.alignment = .fill
    sv.translatesAutoresizingMaskIntoConstraints = false
    sv.distribution = .fillEqually

    addSubview(sv)
    sv.topAnchor.constraint(equalTo: topAnchor).isActive = true
    sv.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    sv.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    sv.leftAnchor.constraint(equalTo: leftAnchor).isActive = true



}

@objc func buttonTapped(button:UIButton){
    for (buttonIndex,btn) in buttons.enumerated(){
        btn.setTitleColor(TextColor, for: .normal)
        if(btn == button){
            selectSegmentIndex = buttonIndex
            let selectorStartPosition = frame.width/CGFloat(buttons.count) * CGFloat(buttonIndex)
            UIView.animate(withDuration: 0.3) {
                self.selector.frame.origin.x = selectorStartPosition
            }
            btn.setTitleColor(selectorTextColor, for: .normal)
        }
    }

    sendActions(for: .valueChanged)
}

}

如果您想运行该应用程序,请点击这里。 https://github.com/Rikenm/Auto-Counter-iOS

最后感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

简便的解决方案是覆盖layoutSubviews并更新selector视图的宽度。

override func layoutSubviews() {
    super.layoutSubviews()
    selector.frame.width = frame.width / CGFloat(buttons.count)
}

但是您也可以通过将选择器视图约束为与分段控制器相同的宽度并乘以1/buttons.count

来自动完成此操作

答案 1 :(得分:0)

我强烈建议使用约束和自动布局,而不是显式设置框架。

这是您的自定义类,实际上只有几处更改。我已经评论了我所做的一切:

@IBDesignable
class SegmentedControl: UIControl{

    var buttons = [UIButton]()

    var selector: UIView!

    var selectSegmentIndex = 0

    // leading constraint for selector view
    var selectorLeadingConstraint: NSLayoutConstraint!

    @IBInspectable
    var borderWidth: CGFloat = 0{

        didSet{
            layer.borderWidth = borderWidth
        }
    }
    @IBInspectable
    var borderColor: UIColor = .clear {

        didSet{
            layer.borderColor = borderColor.cgColor
        }
    }

    override func draw(_ rect: CGRect) {
        layer.cornerRadius = frame.height/2
    }

    @IBInspectable
    var commaSeperatedButtonTitles: String = ""{
        didSet{

            updateView()
        }
    }

    @IBInspectable
    var selectorColor: UIColor = .white{

        didSet{
            updateView()
        }
    }

    @IBInspectable
    var selectorTextColor: UIColor = .white{

        didSet{
            updateView()
        }
    }

    @IBInspectable
    var TextColor: UIColor = .lightGray {

        didSet{
            updateView()

        }
    }

    // this will update the control in IB
    // when constraints are changed
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        updateView()
    }

    // this will keep the selector corners "round"
    override func layoutSubviews() {
        super.layoutSubviews()
        selector.layer.cornerRadius = selector.frame.height / 2.0
    }

    func  updateView(){

        buttons.removeAll()

        subviews.forEach { $0.removeFromSuperview()}

        let buttonTitles =  commaSeperatedButtonTitles.components(separatedBy: ",")
        for buttonTitle in buttonTitles{
            let button = UIButton(type: .system)
            button.setTitle(buttonTitle, for: .normal)
            button.setTitleColor(TextColor, for: .normal)
            button.addTarget(self, action: #selector(buttonTapped(button: )), for: .touchUpInside )
            buttons.append(button)
        }

        buttons[0].setTitleColor(selectorTextColor, for: .normal)

        // not needed
        //let selectorWidth = frame.width/CGFloat(buttonTitles.count)

        // we're going to use auto-layout, so no need to set a frame
        //selector = UIView(frame: CGRect(x: 0, y: 0, width: selectorWidth, height: frame.height))
        selector = UIView(frame: CGRect.zero)

        selector.backgroundColor = selectorColor
        selector.translatesAutoresizingMaskIntoConstraints = false
        selector.layer.cornerRadius = frame.height/2

        addSubview(selector)

        // constrain selector top to self.top
        selector.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        // constrain selector height to self.height
        selector.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true

        // constrain selector width to self.width
        // with multiplier of 1 / number of buttons
        let m = 1.0 / CGFloat(buttons.count)
        selector.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: m).isActive = true

        // instantiate leading constraint for selector, and
        // keep a reference in var selectorLeadingConstraint
        selectorLeadingConstraint = selector.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0)

        // make it active
        selectorLeadingConstraint.isActive = true

        let sv = UIStackView(arrangedSubviews: buttons)
        sv.axis = .horizontal
        sv.alignment = .fill
        //        sv.distribution = .fillProportionally
        sv.translatesAutoresizingMaskIntoConstraints = false
        sv.distribution = .fillEqually

        addSubview(sv)
        sv.topAnchor.constraint(equalTo: topAnchor).isActive = true
        sv.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        sv.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
        sv.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
    }

    @objc func buttonTapped(button:UIButton){
        for (buttonIndex,btn) in buttons.enumerated(){
            btn.setTitleColor(TextColor, for: .normal)
            if(btn == button){
                selectSegmentIndex = buttonIndex
                let selectorStartPosition = frame.width/CGFloat(buttons.count) * CGFloat(buttonIndex)

                    // update selector's leading constraint, instead of explicit frame
                    //self.selector.frame.origin.x = selectorStartPosition
                    self.selectorLeadingConstraint.constant = selectorStartPosition

                UIView.animate(withDuration: 0.3) {
                    self.layoutIfNeeded()
                }
                btn.setTitleColor(selectorTextColor, for: .normal)
            }
        }

        sendActions(for: .valueChanged)
    }

}

编辑:

这是另一种选择。代替使用UIStackView,而使用约束来布置按钮。现在,“选择器”视图将成为按钮的兄弟,因此您可以使用centerX约束而不是计算.leadingAnchor约束。

最大的好处是,现在您可以在显示自定义分段控件 后更改其大小,并且“选择器”的大小和位置将自动更新。例如,如果将控件的宽度设置为屏幕(或其超级视图)宽度的50%,然后旋转设备,以使控件变宽或变窄。

@IBDesignable
class SegmentedControl: UIControl{

    var buttons = [UIButton]()

    var selector: UIView!

    // centerX constraint for selector view
    var selectorCenterXConstraint: NSLayoutConstraint!

    @IBInspectable
    var borderWidth: CGFloat = 0{

        didSet{
            layer.borderWidth = borderWidth
        }
    }
    @IBInspectable
    var borderColor: UIColor = .clear {

        didSet{
            layer.borderColor = borderColor.cgColor
        }
    }

    override func draw(_ rect: CGRect) {
        layer.cornerRadius = frame.height/2
    }

    @IBInspectable
    var commaSeperatedButtonTitles: String = ""{
        didSet{

            updateView()
        }
    }

    @IBInspectable
    var selectorColor: UIColor = .white{

        didSet{
            updateView()
        }
    }

    @IBInspectable
    var selectorTextColor: UIColor = .white{

        didSet{
            updateView()
        }
    }

    @IBInspectable
    var TextColor: UIColor = .lightGray {

        didSet{
            updateView()

        }
    }

    // this will update the control in IB
    // when constraints are changed
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        updateView()
    }

    // this will keep the selector corners "round"
    override func layoutSubviews() {
        super.layoutSubviews()
        selector.layer.cornerRadius = selector.frame.height / 2.0
    }

    func  updateView(){

        buttons.removeAll()

        subviews.forEach { $0.removeFromSuperview()}

        // deactivate centerX constraint if its been initialized
        if selectorCenterXConstraint != nil {
            selectorCenterXConstraint.isActive = false
        }

        // add the selector view first
        selector = UIView(frame: CGRect.zero)

        selector.backgroundColor = selectorColor
        selector.translatesAutoresizingMaskIntoConstraints = false
        selector.layer.cornerRadius = frame.height/2

        addSubview(selector)

        let buttonTitles =  commaSeperatedButtonTitles.components(separatedBy: ",")
        for buttonTitle in buttonTitles{
            let button = UIButton(type: .system)
            button.setTitle(buttonTitle, for: .normal)
            button.setTitleColor(TextColor, for: .normal)
            button.addTarget(self, action: #selector(buttonTapped(button: )), for: .touchUpInside )
            buttons.append(button)
        }

        buttons[0].setTitleColor(selectorTextColor, for: .normal)

        // add each button and set top and height constraints
        buttons.forEach {
            self.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
            $0.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            $0.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
        }

        // constrain first button's leading to self.leading
        // constrain last button's trailing to self.trailing
        NSLayoutConstraint.activate([
            buttons[0].leadingAnchor.constraint(equalTo: self.leadingAnchor),
            buttons[buttons.count - 1].trailingAnchor.constraint(equalTo: self.trailingAnchor),
        ])

        // constrain each button's width to the first button's width
        for i in 1..<buttons.count {
            buttons[i].leadingAnchor.constraint(equalTo: buttons[i - 1].trailingAnchor).isActive = true
            buttons[i].widthAnchor.constraint(equalTo: buttons[0].widthAnchor).isActive = true
        }

        // constrain selector top, height and width to first button's top, height and width
        selector.topAnchor.constraint(equalTo: buttons[0].topAnchor).isActive = true
        selector.heightAnchor.constraint(equalTo: buttons[0].heightAnchor).isActive = true
        selector.widthAnchor.constraint(equalTo: buttons[0].widthAnchor).isActive = true

        // constrain selector's centerX to first button's centerX
        selectorCenterXConstraint = selector.centerXAnchor.constraint(equalTo: buttons[0].centerXAnchor)
        selectorCenterXConstraint.isActive = true

    }

    @objc func buttonTapped(button:UIButton){
        buttons.forEach { btn in
            btn.setTitleColor(TextColor, for: .normal)
            if (btn == button) {

                // deactivate selector's current centerX constraint
                self.selectorCenterXConstraint.isActive = false

                // constrain selector's centerX to selected button's centerX
                self.selectorCenterXConstraint = self.selector.centerXAnchor.constraint(equalTo: btn.centerXAnchor)

                // re-activate selector's centerX constraint
                self.selectorCenterXConstraint.isActive = true

                UIView.animate(withDuration: 0.3) {
                    self.layoutIfNeeded()
                }
                btn.setTitleColor(selectorTextColor, for: .normal)
            }
        }

        sendActions(for: .valueChanged)
    }

}

在此处发布问题的一个技巧-阅读How to create a Minimal, Complete, and Verifiable Example