CollectionView 在 StackView 中消失 (Swift)

CollectionView Disappears within StackView (Swift)

我正在尝试实现图中所示的 stackView 排列:,但由于某种原因,包含 collectionView 的顶部堆栈在使用时消失了:a.fill 分布

stackView.distribution = .fill        (stack containing collectionView disappears)
stackView.distribution = .fillEqually (collectionView appears fine in stackView)

几天来我一直在为此苦苦挣扎,您会在我注释掉的部分中看到残留物:设置 compressionResistance/hugging 优先级,尝试更改固有高度,更改 .layout.itemSize of UICollectionViewFlowLayout() 等...在我手中没有任何作用。如果您只是将其粘贴并与空的 UIViewController 相关联,此处的代码将 运行。顶部的 collectionView 堆栈包含一个 pickerView,下面的堆栈是一个 pageControllView、按钮子堆栈和一个 UIView。在 .fillEqually 分布中一切正常,所以这纯粹是一个布局问题。非常感谢!

// CodeStackVC2
// Test of programmatically generated stack views
// Output: nested stack views
// To make it run:
// 1) Assign the blank storyboard VC as CodeStackVC2
// 2) Move the "Is Inital VC" arrow to the blank storyboard

import UIKit

class CodeStackVC2: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate {

  let fruit = ["Apple", "Orange", "Plum", "Qiwi", "Banana"]
  let veg = ["Lettuce", "Carrot", "Celery", "Onion", "Brocolli"]
  let meat = ["Beef", "Chicken", "Ham", "Lamb"]
  let bread = ["Wheat", "Muffin", "Rye", "Pita"]
  var foods = [[String]]()
  let button = ["bread","fruit","meat","veg"]

  var sView = UIStackView()
  let cellId = "cellId"

  override func viewDidLoad() {
    super.viewDidLoad()
    foods = [fruit, veg, meat, bread]
    setupViews()
  }

  //MARK: Views
  lazy var cView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    layout.minimumLineSpacing = 0
    layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
    layout.itemSize = CGSize(width: self.view.frame.width, height: 120)
    let cv = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
     cv.backgroundColor = UIColor.lightGray
    cv.isPagingEnabled = true
    cv.dataSource = self
    cv.delegate = self
    cv.isUserInteractionEnabled = true
    //    var intrinsicContentSize: CGSize {
    //      return CGSize(width: UIViewNoIntrinsicMetric, height: 120)
    //    }
    return cv
  }()

  lazy var pageControl: UIPageControl = {
    let pageC = UIPageControl()
    pageC.numberOfPages = self.foods.count
    pageC.pageIndicatorTintColor = UIColor.darkGray
    pageC.currentPageIndicatorTintColor = UIColor.white
    pageC.backgroundColor = .lightGray
    pageC.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
//    pageC.setContentHuggingPriority(900, for: .vertical)
//    pageC.setContentCompressionResistancePriority(100, for: .vertical)
    return pageC
  }()

  var readerView: UIView = {
    let rView = UIView()
    rView.backgroundColor = UIColor.brown
//    rView.setContentHuggingPriority(100, for: .vertical)
//    rView.setContentCompressionResistancePriority(900, for: .vertical)
    return rView
  }()


  func makeButton(_ name:String) -> UIButton{
    let newButton = UIButton(type: .system)
    let img = UIImage(named: name)?.withRenderingMode(.alwaysTemplate)
    newButton.setImage(img, for: .normal)
    newButton.contentMode = .scaleAspectFit
    newButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleButton)))
    newButton.isUserInteractionEnabled = true
    newButton.backgroundColor = .orange
    return newButton
  }
  //Make a 4-item vertical stackView containing
  //cView,pageView,subStackof 4-item horiz buttons, readerView
  func setupViews(){
    cView.register(FoodCell.self, forCellWithReuseIdentifier: cellId)
    //generate an array of buttons
    var buttons = [UIButton]()
    for i in 0...foods.count-1 {
      buttons += [makeButton(button[i])]
    }
    let subStackView = UIStackView(arrangedSubviews: buttons)
    subStackView.axis = .horizontal
    subStackView.distribution = .fillEqually
    subStackView.alignment = .center
    subStackView.spacing = 20
    //set up the stackView
    let stackView = UIStackView(arrangedSubviews: [cView,pageControl,subStackView,readerView])
    stackView.axis = .vertical
    //.fillEqually works, .fill deletes cView, .fillProportionally & .equalSpacing delete cView & readerView
    stackView.distribution = .fillEqually
    stackView.alignment = .fill
    stackView.spacing = 5
    //Add the stackView using AutoLayout
    view.addSubview(stackView)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
    stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
  }

  func handleButton() {
    print("button pressed")
  }
  //pageControl page changer
  func changePage(sender: AnyObject) -> () {
    let x = CGFloat(pageControl.currentPage) * cView.frame.size.width
    cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
  }

  //MARK: horizontally scrolling Chapter collectionView
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    //    let scrollBarLeft = CGFloat(scrollView.contentOffset.x) / CGFloat(book.chap.count + 1)
    //    let scrollBarWidth = CGFloat( menuBar.frame.width) / CGFloat(book.chap.count + 1)
  }

  func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let index = targetContentOffset.pointee.x / view.frame.width
    pageControl.currentPage = Int(index) //change PageControl indicator
  }

  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return foods.count
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FoodCell
    cell.foodType = foods[indexPath.item]
    return cell
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 120)
  }
}

class FoodCell:UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource {

  var foodType: [String]? {
    didSet {
      pickerView.reloadComponent(0)
      pickerView.selectRow(0, inComponent: 0, animated: true)
    }
  }

  lazy var pickerView: UIPickerView = {
    let pView = UIPickerView()
    pView.frame = CGRect(x:0,y:0,width:Int(pView.bounds.width), height:Int(pView.bounds.height))
    pView.delegate = self
    pView.dataSource = self
    pView.backgroundColor = .darkGray
    return pView
  }()

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

  func setupViews() {
    backgroundColor = .clear
    addSubview(pickerView)
    addConstraintsWithFormat("H:|[v0]|", views: pickerView)
    addConstraintsWithFormat("V:|[v0]|", views: pickerView)
  }
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
  }

  func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    if let count = foodType?.count {
      return count
    } else {
      return 0
    }
  }

  func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    let pickerLabel = UILabel()
    pickerLabel.font = UIFont.systemFont(ofSize: 15)
    pickerLabel.textAlignment = .center
    pickerLabel.adjustsFontSizeToFitWidth = true
    if let foodItem = foodType?[row] {
      pickerLabel.text = foodItem
      pickerLabel.textColor = .white
      return pickerLabel
    } else {
      print("chap = nil in viewForRow")
      return UIView()
    }
  }


}

问题是您有一个固定高度的堆栈视图,其中包含两个没有固有内容大小的视图(cView 和 readerView)。您需要告诉布局引擎应该如何调整这些视图的大小以填充堆栈视图中剩余的 space。

它在您使用 .fillEqually 分布时有效,因为您告诉布局引擎使堆栈视图中的所有四个视图具有相同的高度。这定义了 cView 和 readerView 的高度。

当您使用 .fill 分布时,无法确定 cView 和 readerView 应该有多高。在您添加更多约束之前,布局是不明确的。内容优先级没有任何作用,因为这些视图没有可以拉伸或压缩的固有大小。您需要设置一个没有固有大小的视图的高度,另一个将占用剩余的 space.

问题是collection view应该多高?您希望它与 reader 视图大小相同还是容器视图的某个比例?

例如,假设您的设计要求集合视图占容器视图高度的 25%,reader视图使用剩余的 space(其他两个视图位于它们的自然内在内容大小)。您可以添加以下约束:

NSLayoutConstraint.activate([
  cView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25)
])

一个更简单的例子

将布局减少到最基本的元素。您有一个固定到其父视图的堆栈视图,其中有四个排列的子视图,其中两个没有固有内容大小。例如,这是一个带有两个普通 UIView、一个标签和一个按钮的视图控制器:

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    setupViews()
  }

  private func setupViews() {

    let blueView = UIView()
    blueView.backgroundColor = .blue

    let titleLabel = UILabel()
    titleLabel.text = "Hello"

    let button = UIButton(type: .system)
    button.setTitle("Action", for: .normal)

    let redView = UIView()
    redView.backgroundColor = .red

    let stackView = UIStackView(arrangedSubviews: [blueView, titleLabel, button, redView])
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.spacing = 8
    view.addSubview(stackView)

    NSLayoutConstraint.activate([
      stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      stackView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor),
      stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      blueView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25)
      ])
  }
}

这是使用 25% 的垂直 space 的蓝色视图在纵向 iPhone 上的外观:

UIStackView 可以很好地与 UIView 的排列子视图一起使用,但不能直接与 UICollectionView 一起使用。

我建议您将所有子视图项放入 UIView 中,然后再将它们堆叠到 UIStackView 中,您也可以使用 .fill 分布而不使用固有内容大小,而是使用约束来使您的子视图按需要成比例。

此解决方案还可以在不强制的情况下与自动布局无缝协作 translatesAutoresizingMaskIntoConstraints = false 如果您明白我的意思,这会让您不太适应特征更改。

/通用汽车

  1. 在 xib 或故事板中设置所需控件的顶部、底部、前导和尾随约束。
  2. 提供堆栈.fill 的分配。
  3. 然后在Xib或者storyboard中提供所有栈的高度约束。
  4. 然后在代码中为每个堆栈设置适当的高度。

希望对你有用。

我遇到了同样的问题,对我来说,当我对放置在堆栈视图中的集合视图进行高度和宽度限制时,它就起作用了。

我在使用 Xamarin CollectionView 时遇到过这种行为,并将其追踪到由于 Web api 调用从 MainPage 中删除页面后与 CollectionView 进行的交互。即使阻止它,尽管它在重新加载页面时仍然有问题。我最终决定在页面即将隐藏时清除 collection 列表并保存项目的备份副本,然后在页面显示时,运行 等待 10 毫秒然后重新安装的异步任务这几项。未能清除列表或在重新显示后立即将项目安装到列表中都会导致错误。控制台列表中显示以下内容,并且 CollectionView 似乎将自己标记为在此消息后不再尝试工作:

2022-04-16 19:56:33.760310-0500 .iOS[30135:2117558] UICollectionViewFlowLayout 的行为未定义,因为: 2022-04-16 19:56:33.760454-0500 .iOS[30135:2117558] item宽度必须小于UICollectionView的宽度减去section insets左右值,再减去content insets左值和右值。 2022-04-16 19:56:33.760581-0500 .iOS[30135:2117558] 请检查委托返回的值。 2022-04-16 19:56:33.760754-0500 .iOS[30135:2117558] 相关的UICollectionViewFlowLayout实例为,附加到;层 = ; contentOffset: {0, 0};内容大小:{0, 0}; adjustedContentInset: {0, 0, 0, 0};布局:;数据源:>。 2022-04-16 19:56:33.760829-0500 .iOS[30135:2117558] 在 UICollectionViewFlowLayoutBreakForInvalidSizes 处创建符号断点以在调试器中捕获此问题。