如果有人在这里引导我找到正确的解决方案,那将是非常棒的。这是我今天的第二个问题,在这里我很犹豫,但是我正在学习全新的东西(例如创建自己的分段控件等),并且根本不知道从哪里开始。我已经尝试使用有限的知识来进行尽可能多的调试。
这是我在汇总表中遇到的问题。
我有两种看法。内视图的宽度取决于外视图的宽度。因此,如果我更新外部视图的宽度Constraint(使用约束IBoutlet),则外部视图的宽度会发生变化,但内部视图的宽度将与旧视图相同。更改外部视图的宽度约束后,我在外部视图上做了layoutIfNeeded(),但是什么也没发生。
详细信息:
我有一个分段控件(外部视图),选择器的宽度(内部视图)取决于分段控件的总宽度。就像我上面说的,在更改分段控件的总宽度后,选择器的宽度保持不变。选择器的宽度取决于分段控件的宽度。
这是说明我的问题的图像。
您可以看到我的选择器的宽度没有更新。它应该是新分段控件总宽度的一半。
如何更新分段控件的宽度?
基本上,我将分段控件的宽度约束作为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
最后感谢您的帮助。
答案 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