如何使用 .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 文件是 UITableView
和 UICollectionView
.
由于您试图水平滚动项目,而不是垂直滚动,我猜您不想要 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,因为它默认支持动态数据和滚动。
我想使用 .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 文件是 UITableView
和 UICollectionView
.
由于您试图水平滚动项目,而不是垂直滚动,我猜您不想要 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,因为它默认支持动态数据和滚动。