如何使用 .Xib 文件创建 UIView 的水平滚动 stackView

How to create a horizontal scrolling stackView of UIViews using .Xib files

我想使用 .Xib 文件创建 UIView 的水平滚动 stackView,到目前为止,我已经创建了 .Xib 故事板以及与之配套的 cocoaTouch class,但在设置时遇到问题这是我第一次使用 .Xib 文件。

我希望输出看起来像这样:

但是当我 运行 应用程序时它会抛出这个错误:Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x6000029f13d0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key itemImage.' 我不知道为什么会这样,因为我对 .Xib 文件非常不熟悉。

我已将文件所有者的 class 设置为 WhatsHotTile class 以及视图本身。

这里是 WhatsHotTile class:

class WhatsHotTile: UIView {
    @IBOutlet weak var itemName: UILabel!
    @IBOutlet weak var itemImage: UIImageView!
}

这是我尝试创建水平滚动 stackView 的代码:

for shoe in Shoes {
        if shoe.trending == true {
            if let whatsHotTile = Bundle.main.loadNibNamed("WhatsHotTile", owner: nil, options: nil)!.first as? WhatsHotTile {
                whatsHotTile.itemName.text = shoe.name
                whatsHotTile.itemImage.image = shoe.image
                whatsHotStackView.addArrangedSubview(whatsHotTile)
            }
        }
    }

我没有做但可能需要或不需要做的一件事是在 scrollView 内部的水平 stackView 中添加一个空白的 UIView。这是需要完成的事情还是完全以编程方式完成?

由于这是我第一次使用 .Xib 文件,有人可以解释为什么会出现此错误/如何修复它,以及创建 .Xib 文件并在您的代码中使用它们的正确方法是什么,如果是的话将不胜感激。

问题(我相信)是只有一些 Cocoa Touch classes 支持 .xib 文件,UIView 不是其中之一。当我打开 Xcode 并尝试 New File -> Cocoa Touch Class -> Subclass of: UIView 时,它禁用了 also create .xib file 的复选框。一些例子 classes do 允许 .xib 文件是 UITableViewUICollectionView.

由于您试图水平滚动项目,而不是垂直滚动,我猜您不想要 UITableView。这留下了 UICollectionView。这与 UITableView 非常相似,因此使用我已经了解的 table 视图,加上此处链接的 Stack Overflow post:

问:UICollectionView: One Row or Column

答:

和答:

我按照你描述的方式让它工作。这是一个简单的工作示例:

首先,创建一个新的 Cocoa Touch class 文件作为 UICollectionView 的子 class,然后选中 'also create .xib file' 框。我将此子命名为 class 'WhatsHotTile',以接近您的原始代码。

WhatsHotTile .xib 故事板的属性检查器面板中,将其设置为 Reuse Identifier。在我的示例中,我使用了字符串 TileReuseIdentifier.

根据需要设计 .xib 文件:为了重新创建代码,我在 UILabel.

上方放置了 UIImageView

WhatsHotTile.swift 文件将用相同的两个 IBOutlets:

替换代码中 UIView 的子 class
import UIKit

class WhatsHotTile: UICollectionViewCell {
    @IBOutlet weak var itemName: UILabel!
    @IBOutlet weak var itemImage: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

Main.storyboard 中,在视图控制器中放置一个 UICollectionView。它将已经使用原型单元初始化:将此单元的标识符(在属性检查器中)设置为您为 .xib 文件选择的相同标识符(在示例中,它是 TileReuseIdentifier.

ViewController 文件中,为 UICollectionView 创建一个 IBOutlet,并在 viewDidLoad 中,将集合视图的 dataSource 设置为 self(指的是视图控制器)。然后,这是让 .xib 文件与您的代码一起工作的关键步骤register 集合视图的 nib,使用下面示例代码中的语法:

override func viewDidLoad() {
   super.viewDidLoad()
        
   collectionOfShoes.dataSource = self
   collectionOfShoes.register(UINib(nibName: "WhatsHotTile", bundle: nil), forCellWithReuseIdentifier: "TileReuseIdentifier")
}

然后您必须为集合视图实现数据源方法。我通常在扩展中执行此操作(包含在此答案底部的完整视图控制器代码中)。除了这两个必需的方法之外,还要定义 numberOfSections 并将其设置为 'shoes' 数组的长度。然后,将 numberOfItemsInSection 方法设置为 return 1. 结合 viewDidLayoutSubviews 中的以下代码,发生在 viewDidLoad 之后,这将导致集合视图水平滚动垂直的,否则会:

override func viewDidLayoutSubviews() {
    // REFERENCE 1:  
    // REFERENCE 2:  
    let collectionViewFlowControl = UICollectionViewFlowLayout()
    collectionViewFlowControl.itemSize = CGSize(width: 150.0, height: 150.0)
    collectionViewFlowControl.scrollDirection = UICollectionView.ScrollDirection.horizontal
    collectionOfShoes.collectionViewLayout = collectionViewFlowControl
}

完成这些步骤后,您应该会得到与您的原始代码差别不大的东西,它看起来与您作为目标行为提供的图像参考相似。这是 ViewController:

中的完整代码
import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var collectionOfShoes: UICollectionView!
    
    struct Shoe {
        let name: String
        let image: UIImage
    }
    
    var shoes: [Shoe] = [Shoe(name: "Shoe 1", image: UIImage(named: "shoe_1")!), Shoe(name: "Shoe 2", image: UIImage(named: "shoe_2")!), Shoe(name: "Shoe 3", image: UIImage(named: "shoe_3")!), Shoe(name: "Shoe 4", image: UIImage(named: "shoe_1")!), Shoe(name: "Shoe 5", image: UIImage(named: "shoe_2")!), Shoe(name: "Shoe 6", image: UIImage(named: "shoe_3")!), Shoe(name: "Shoe 7", image: UIImage(named: "shoe_1")!), Shoe(name: "Shoe 8", image: UIImage(named: "shoe_2")!), Shoe(name: "Shoe 9", image: UIImage(named: "shoe_3")!)]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionOfShoes.dataSource = self
        collectionOfShoes.register(UINib(nibName: "WhatsHotTile", bundle: nil), forCellWithReuseIdentifier: "TileReuseIdentifier")
    }
    
    override func viewDidLayoutSubviews() {
        // REFERENCE 1:  
        // REFERENCE 2:  
        let collectionViewFlowControl = UICollectionViewFlowLayout()
        collectionViewFlowControl.itemSize = CGSize(width: 150.0, height: 150.0)
        collectionViewFlowControl.scrollDirection = UICollectionView.ScrollDirection.horizontal
        collectionOfShoes.collectionViewLayout = collectionViewFlowControl
    }
}

extension ViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return shoes.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let whatsHotTile = collectionView.dequeueReusableCell(withReuseIdentifier: "TileReuseIdentifier", for: indexPath) as! WhatsHotTile
        whatsHotTile.itemName.text = shoes[indexPath.section].name
        whatsHotTile.itemImage.image = shoes[indexPath.section].image
        return whatsHotTile
    }
}

正在运行的应用程序图片:

感谢 William T.、matt 和 Ilker Baltaci 我在实现水平滚动功能时引用的问题和答案。

我建议在这里使用 UICollectionView 而不是 UIStackView,因为它默认支持动态数据和滚动。