在iOS应用开发中,自定义视图是一个常见的需求。本文将介绍如何在Xcode中使用Xib和Nib文件来创建和管理自定义的食谱视图。
首先,需要了解Xib和Nib文件的区别。Xib文件是Interface Builder的文件格式,用于定义用户界面。而Nib文件是Xib文件编译后生成的,用于在运行时恢复视图。
1. 打开Xcode,创建一个新的项目,命名为MyRecipes。
2. 进入项目导航器,将ViewController重命名为RecipesViewController。
3. 打开main.storyboard,选择ViewController,然后在Identity Inspector中选择RecipesViewController。
4. 在RecipesViewController中添加UITableView,并确保只有一个Prototype Cell,命名为'cell'。
1. 向项目中添加一个新的Cocoa Touch Class,命名为RecipeItemView。
2. 创建一个名为RecipeItemView的Xib文件,并根据需要更新它。
3. 在Xib文件中,会看到两个同名的文件,一个是Swift扩展,另一个是Xib扩展。Nib文件是在Xib文件编译时由Xcode创建的。
4. 要找到Nib文件,可以在Finder中打开以下路径:~/Library/Developer/Xcode/DerivedData/MyRecipes-.../Build/Products/Debug-iphonesimulator/MyRecipes。
5. 右键点击并选择“Show Package Contents”,将看到Nib文件。
Xib文件与用户界面相关联。在案例中,RecipeItemView.xib与RecipeItemView UIView类相关联。将使用这个视图作为UITableViewCell的内容视图。因此,需要下载Nib文件,将其解析为RecipeItemView类,并将其作为子视图添加到cell的内容视图。
有两种方法可以从Nib文件中获取视图类。
1. 下载Nib文件内容作为数组。Xib文件可以包含一个或多个UIView或UIViewController元素。要获取它们,调用loadNibNamed:
let elementsOfNib = Bundle.main.loadNibNamed("RecipeItemView", owner: nil, options: nil)
函数返回一个可选值。要获取数组中的视图,可以使用索引或类似的方法:
let recipesItemView = elementsOfNib?.first
recipesItemView是一个可选的RecipeItemView类实例,具有初始化的outlets。
2. 使用带有owner参数的loadNibNamed调用:
Bundle.main.loadNibNamed("RecipeItemView", owner: self, options: nil)
这里的self参数是什么?为什么不将结果赋值给任何东西?让一步一步考虑。
打开RecipeItemView.xib文件,并将Placeholders—File’s Owner设置为RecipesViewController。
打开RecipesViewController.swift,添加@IBOutlet var:
class RecipesViewController: UIViewController {
…
@IBOutlet var itemView: RecipeItemView!
}
返回Xib编辑屏幕。选择File’s Owner,按住鼠标右键,拖动到RecipeItemView。
从上下文菜单中选择itemView。从现在开始,将File owner和Xib的元素绑定在一起。
返回loadNibNamed:
Bundle.main.loadNibNamed("RecipeItemView", owner: self, options: nil)
这里的self是RecipesViewController。当这个函数返回时,变量itemView将自动获得值。它是RecipeItemView实例。
将RecipeItemView的titleLabel、imageView和descriptionLabel的高度约束与NSLayoutConstraint的outlets绑定。
打开RecipesViewController,从viewDidLoad方法配置UITableView:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.layoutIfNeeded()
}
打开RecipeItemView.swift,计算高度约束:
func configure(with cell: UITableViewCell, model: RecipeData?, width: CGFloat) {
guard let model else {
return
}
titleLabel.text = model.title
imageView.image = model.image
descriptionLabel.text = model.description
let h1 = model.title?.height(for: width, font: titleLabel.font) ?? 0
let h2 = model.image?.height(for: width) ?? 0
let h3 = model.description?.height(for: width, font: descriptionLabel.font) ?? 0
titleLabelHeight.constant = h1
imageViewHeight.constant = h2
descriptionLabelHeight.constant = h3
let height: CGFloat = h1 + h2 + h3 + 5.0 * 4
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: cell.leadingAnchor),
trailingAnchor.constraint(equalTo: cell.trailingAnchor),
topAnchor.constraint(equalTo: cell.topAnchor),
bottomAnchor.constraint(equalTo: cell.bottomAnchor),
heightAnchor.constraint(equalToConstant: height)
])
}
下一步也是最后一步是准备UITableViewCell。覆盖UITableViewDataSource代理的两个方法:
func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
Bundle.main.loadNibNamed("RecipeItemView", owner: self, options: nil)
// itemView现在已加载。可以使用它。
cell.addSubview(itemView!)
itemView!.configure(with: cell, model: viewModel[indexPath.row], width: tableView.frame.width)
return cell
}
TableViewCell应该根据内容调整其高度。
struct RecipesContentView: View {
@EnvironmentObject var viewModel: RecipesFakeData
var body: some View {
GeometryReader { geometry in
VStack {
NavigationBar()
List {
ForEach(0.. CGFloat {
if let model = viewModel[index], let view = Bundle.main.loadNibNamed("RecipeItemView", owner: nil, options: nil)?.first as? RecipeItemView {
var height = model.title?.height(for: width, font: view.titleLabel.font) ?? 0
height += model.image?.height(for: width) ?? 0
height += model.description?.height(for: width, font: view.descriptionLabel.font) ?? 0
return height + 5.0 * 3 // 参考RecipeItemView.xib布局
}
return 0.0
}
}