swift 在UIKit中创建一个扩展的水平UIStackView,其中有两种状态的可选按钮

m1m5dgzv  于 2023-04-28  发布在  Swift
关注(0)|答案(1)|浏览(115)

我想创建一个包含5个按钮的水平UIStackView。每个按钮可以处于两种状态之一:选择或未选择。取消选择按钮时,它将显示为仅包含图像的正方形。当一个按钮被选中时,它会同时显示一个图像和一个标签,并将其转换为一个比其他未选中按钮更长的矩形。
当视图初始设置时,第一个按钮应处于选中状态。轻敲任何其他按钮应导致先前选择的按钮收缩回正方形,而新轻敲的按钮扩展以显示标签。如何使用UIKit实现此行为?
下面你可以看到一个实际的图像,直观地解释了我上面描述的内容:

我想用纽扣。changesSelectionAsPrimaryAction = true在iOS 15中引入-以避免创建UIButton的子类。不幸的是,按钮的外观将自动更新为所选状态,其中包括不同的背景颜色和复选标记图像,所以它可能不适合这种情况。我想知道是否有可能在不创建自定义UIButton类的情况下实现这种设计。

0tdrvxhp

0tdrvxhp1#

使用Swift/UIKit(这不适用于SwiftUI)。..
我们可以利用UIStackView在显示和隐藏排列的子视图时自动调整大小。
我们将从UIView子类开始(而不是按钮),它将包含:

  • “背景”视图(带圆角)
  • 水平堆栈视图,包含
  • 图像视图
  • 标签
  • 用于切换其“选定”状态的轻击手势识别器

当我们想要“展开”这个视图时,我们设置label.isHidden = false,而要“折叠”它,我们设置label.isHidden = true
如果我们有五个自定义视图作为“父”水平堆栈视图的子视图,所有的大小调整都将是自动的。
因此,我们的自定义“图像和标签”视图:

class ImageLabelView: UIView {
    
    let imgView = UIImageView()
    let label = UILabel()
    let bkgView = UIView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }
    private func commonInit() {
        
        let stack = UIStackView()
        stack.spacing = 8
        
        [imgView, label].forEach { v in
            stack.addArrangedSubview(v)
        }
        
        bkgView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(bkgView)

        stack.translatesAutoresizingMaskIntoConstraints = false
        addSubview(stack)
        
        let pad: CGFloat = 8.0
        
        NSLayoutConstraint.activate([
            
            bkgView.topAnchor.constraint(equalTo: topAnchor),
            bkgView.leadingAnchor.constraint(equalTo: leadingAnchor),
            bkgView.trailingAnchor.constraint(equalTo: trailingAnchor),
            bkgView.bottomAnchor.constraint(equalTo: bottomAnchor),

            stack.topAnchor.constraint(equalTo: topAnchor, constant: pad),
            stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: pad),
            stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -pad * 2.0),
            stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -pad),
            
            imgView.heightAnchor.constraint(equalToConstant: 32.0),
            imgView.widthAnchor.constraint(equalTo: imgView.heightAnchor),
            
        ])
        
        bkgView.layer.cornerRadius = 12
        bkgView.backgroundColor = .black.withAlphaComponent(0.2)
        
        label.textColor = .white
        label.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        self.clipsToBounds = true
    }
    
    public var selected: Bool = false {
        didSet {
            // we only want to animate things if we are already a subview
            if superview == nil {
                self.label.isHidden = !self.selected
                self.bkgView.alpha = self.selected ? 1.0 : 0.0
            } else {
                self.label.isHidden = !self.selected
                UIView.animate(withDuration: 0.3, animations: {
                    self.bkgView.alpha = self.selected ? 1.0 : 0.0
                })
            }
        }
    }
}

接下来,一个UIView子类(我们称之为WeatherTabView),其中:

  • 包含5 ImageLabelView的水平堆栈视图
  • 用图像和文本填充这5个视图的代码
  • 轻击手势处理程序
  • 和一个“回调闭包”,这样我们就可以通知控制器某个项被选中了

类WeatherTabView:UIView {

public var tapCallback: ((Int) -> ())?

  private let stateStack = UIStackView()

  override init(frame: CGRect) {
      super.init(frame: frame)
      commonInit()
  }
  required init?(coder aDecoder: NSCoder) {
      super.init(coder:aDecoder)
      commonInit()
  }
  private func commonInit() {

      stateStack.distribution = .equalSpacing
      stateStack.alignment = .center

      stateStack.translatesAutoresizingMaskIntoConstraints = false
      addSubview(stateStack)

      let pad: CGFloat = 8.0

      NSLayoutConstraint.activate([
          stateStack.topAnchor.constraint(equalTo: topAnchor, constant: pad),
          stateStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: pad),
          stateStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -pad),
          stateStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -pad),
      ])

      let names: [String] = [
          "Sun", "Storm", "Tornado", "Moon", "Snow"
      ]
      for (i, s) in names.enumerated() {
          guard let img = UIImage(named: "t\(i+1)") else {
              fatalError("Could not load image!")
          }
          let v = ImageLabelView()
          v.imgView.image = img
          v.label.text = s
          v.selected = i == 0
          stateStack.addArrangedSubview(v)
          let tp = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
          v.addGestureRecognizer(tp)
      }

      self.layer.cornerRadius = 20.0
      self.backgroundColor = .black.withAlphaComponent(0.20)

  }

  @objc func gotTap(_ tp: UITapGestureRecognizer) {
      guard let v = tp.view,
            let idx = stateStack.arrangedSubviews.firstIndex(of: v)
      else { return }

      tapCallback?(idx)

      stateStack.arrangedSubviews.forEach { sv in
          if let sv = sv as? ImageLabelView {
              sv.selected = v == sv
          }
          UIView.animate(withDuration: 0.3, animations: {
              self.layoutIfNeeded()
          })
      }
  }

}
和一个简单的视图控制器来演示:

class TestVC: UIViewController {
    
    let weatherTabView = WeatherTabView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .lightGray
        
        weatherTabView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(weatherTabView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            weatherTabView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
            weatherTabView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
            weatherTabView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
        ])
    
        // setup the closure
        weatherTabView.tapCallback = { [weak self] idx in
            print("Item \(idx) was tapped!")
            // do something
        }
        
    }
    
}

运行时看起来像这样:

注意:这是*示例代码!********************************************************************************************************************************************************************************************************************************************************..

相关问题