UITableViewCell 中的 Nib 文件加载的 UIView 不拉伸
Nib-file loaded UIView in UITableViewCell does not stretch
我有一个可以通过 nib/xib-file 重用的 UIView。我想加载它并填充一个 UITableViewCell,它将在一个自调整大小的 UITableView 中使用。全部带有自动布局。
大多数效果很好,但加载的 UIView 似乎使用围绕它添加的约束来缩小 UITableViewCell 的 contentView。这对高度有好处,但我不想要宽度。
忽略下面的灰色单元格,这只是一个选中的单元格。
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cellId = "RowView0002"
var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
let subView = RowView(frame: cell!.frame)
cell!.contentView.attachViewWithConstraints(subView)
let _ = subView.viewLoadedFromNibAttached(name: cellId)
}
return cell!
}
override public func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 40.0
tableView.rowHeight = UITableViewAutomaticDimension
}
extension UIView
{
public func attachViewWithConstraints(_ view:UIView)
{
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.layoutAttachAll(to: self)
}
@discardableResult
public func viewLoadedFromNibAttached<T : UIView>(name:String) -> T? {
guard let view = Bundle.main.loadNibNamed(name, owner: self, options: nil)?[0] as? T else {
return nil
}
attachViewWithConstraints(view)
return view
}
public func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
childView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
childView.addConstraints(constraints)
}
在 RowView0002.xib 中,我已将 rootviews 背景设置为红色,添加了一个带有 4 个约束的 UILabel,如您所见,它的边带有一些边距。我都试图将 rootView 设置为 class RowView 以及它的文件所有者。 "works".
知道如何让 contentView 与 UITableView 匹配吗?
*编辑1:
绿色是 UILabel 的背景。红色是 nib 文件的背景。在 运行 应用程序之后,视图继承是:UITableViewCell > ContentView > RowView > NibFileView(红色)> UILabel(绿色)
检查视图层次结构表明所有约束都按预期设置。但是 UITableViewContentView 的约束与看到的总大小相匹配(错误):
self.width = 156.5 @ 1000
看起来问题在于使用 UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
创建的单元格没有关于 table 视图宽度的信息,因此它根据标签的宽度调整自身大小。显然 table 视图/单元格不会强制单元格的内容视图占用其宽度。
您可能想要修改其中的一些 cell-handling 代码。
如果你想从 xib 加载你的单元格,你可以跳过所有有约束的东西。只需实施:
override func viewDidLoad() {
//...
let nib = UINib(nibName: "RowView0002", bundle: NSBundle.main)
tableView.reigsterNib(nib, forCellReuseIdentifier: "RowView0002")
}
非常重要:.xib 文件中的第一个顶级项目必须是 UITableViewCell。 Nibs默认是UIViews,删除IB中的view,在IB的lower-right中从对象库中拖出一个UITableViewCell。然后,如有必要,将其 subclass 设置为您创建的 UITableViewCell subclass。 (您可能还需要在 IB 中设置 reuseIdentifier。)
然后在 tableView(_:cellForRowAt IndexPath:)
中:
guard let cell = tableView.dequeueResuableCell(withIdentifier: "RowView0002", for: indexPath) as? TheNibUITableViewSubclass else { //something went wrong, probably crash }
cell.label.text = //...
return cell
您可能希望将 "RowView0002" 放在某处的常量中。
如果 "RowView0002" 和 RowView
class 都需要成为视图,您可能应该创建 UITableViewCell
的子 class。仅覆盖 init(style:resueIdentifier:) and after calling
super` 在上面的代码中添加您的子视图。希望这对您有所帮助!
我通过添加与 tableViews 宽度匹配的宽度约束来解决它。这是来自 CustomTableViewCell 的代码:
public override func layoutSubviews() {
super.layoutSubviews()
if let width = tableView()?.frame.width, !haveAddedWidthConstraint {
haveAddedWidthConstraint = true
rowView.addWidthConstraint(width: width)
}
}
UIViewExtension:
public func addWidthConstraint(width: CGFloat) {
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width)
widthConstraint.priority = 1000
addConstraint(widthConstraint)
}
UITableViewCellExtension:
func tableView() -> UITableView? {
var currentView: UIView = self
while let superView = currentView.superview {
if superView is UITableView {
return (superView as! UITableView)
}
currentView = superView
}
return nil
}
layoutAttachAll 的完整实现如下。
首先是一些用法示例:
// pin all edge to superview
myView.layoutAttachAll()
// pin all edges (to superview) with margin:
myView.layoutAttachAll(margin: 8.0)
// for child views: pin leading edge to superview's leading edge:
myView.layoutAttachLeading()
// for sibling views: pin leading edge to siblingView's trailing edge:
myView.layoutAttachLeading(to: siblingView)
// for sibling views: pin top edge to siblingView's bottom edge:
myView.layoutAttachTop(to: siblingView)
注意:在使用这些方法附加到超级视图之前,必须将 myView 添加为子视图。此外,必须使用 translatesAutoresizingMaskIntoConstraints = false 设置所有参与视图。
完整实施:
import UIKit
extension UIView {
/// attaches all sides of the receiver to its parent view
func layoutAttachAll(margin : CGFloat = 0.0) {
let view = superview
layoutAttachTop(to: view, margin: margin)
layoutAttachBottom(to: view, margin: margin)
layoutAttachLeading(to: view, margin: margin)
layoutAttachTrailing(to: view, margin: margin)
}
/// attaches the top of the current view to the given view's top if it's a superview of the current view, or to it's bottom if it's not (assuming this is then a sibling view).
/// if view is not provided, the current view's super view is used
@discardableResult
func layoutAttachTop(to: UIView? = nil, margin : CGFloat = 0.0) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = view == superview
let constraint = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: isSuperview ? .top : .bottom, multiplier: 1.0, constant: margin)
superview?.addConstraint(constraint)
return constraint
}
/// attaches the bottom of the current view to the given view
@discardableResult
func layoutAttachBottom(to: UIView? = nil, margin : CGFloat = 0.0, priority: UILayoutPriority? = nil) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: isSuperview ? .bottom : .top, multiplier: 1.0, constant: -margin)
if let priority = priority {
constraint.priority = priority
}
superview?.addConstraint(constraint)
return constraint
}
/// attaches the leading edge of the current view to the given view
@discardableResult
func layoutAttachLeading(to: UIView? = nil, margin : CGFloat = 0.0) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: isSuperview ? .leading : .trailing, multiplier: 1.0, constant: margin)
superview?.addConstraint(constraint)
return constraint
}
/// attaches the trailing edge of the current view to the given view
@discardableResult
func layoutAttachTrailing(to: UIView? = nil, margin : CGFloat = 0.0, priority: UILayoutPriority? = nil) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: isSuperview ? .trailing : .leading, multiplier: 1.0, constant: -margin)
if let priority = priority {
constraint.priority = priority
}
superview?.addConstraint(constraint)
return constraint
}
}
我通过在我的自定义 UITableViewCell class 中公开一个设置 UITableViewCell 的锚约束的方法并从父 UITableView cellForRowAtIndexPath 调用它来解决它 方法。
这是Objective C中的代码。
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourTableViewCellType *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
**[cell expandToParentSize];** // Self-sizing magic!
return cell;
}
// Write below code in "YourTableViewCellType" class. "containerStack" is my UIStackView that holds all controls in the nib UITableViewCell
(void)expandToParentSize
{
[NSLayoutConstraint activateConstraints:@[
[self.containerStack.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.containerStack.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.containerStack.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.containerStack.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
]];
}
我有一个可以通过 nib/xib-file 重用的 UIView。我想加载它并填充一个 UITableViewCell,它将在一个自调整大小的 UITableView 中使用。全部带有自动布局。
大多数效果很好,但加载的 UIView 似乎使用围绕它添加的约束来缩小 UITableViewCell 的 contentView。这对高度有好处,但我不想要宽度。
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cellId = "RowView0002"
var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
let subView = RowView(frame: cell!.frame)
cell!.contentView.attachViewWithConstraints(subView)
let _ = subView.viewLoadedFromNibAttached(name: cellId)
}
return cell!
}
override public func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 40.0
tableView.rowHeight = UITableViewAutomaticDimension
}
extension UIView
{
public func attachViewWithConstraints(_ view:UIView)
{
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.layoutAttachAll(to: self)
}
@discardableResult
public func viewLoadedFromNibAttached<T : UIView>(name:String) -> T? {
guard let view = Bundle.main.loadNibNamed(name, owner: self, options: nil)?[0] as? T else {
return nil
}
attachViewWithConstraints(view)
return view
}
public func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
childView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
childView.addConstraints(constraints)
}
在 RowView0002.xib 中,我已将 rootviews 背景设置为红色,添加了一个带有 4 个约束的 UILabel,如您所见,它的边带有一些边距。我都试图将 rootView 设置为 class RowView 以及它的文件所有者。 "works".
知道如何让 contentView 与 UITableView 匹配吗?
*编辑1: 绿色是 UILabel 的背景。红色是 nib 文件的背景。在 运行 应用程序之后,视图继承是:UITableViewCell > ContentView > RowView > NibFileView(红色)> UILabel(绿色)
检查视图层次结构表明所有约束都按预期设置。但是 UITableViewContentView 的约束与看到的总大小相匹配(错误):
self.width = 156.5 @ 1000
看起来问题在于使用 UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
创建的单元格没有关于 table 视图宽度的信息,因此它根据标签的宽度调整自身大小。显然 table 视图/单元格不会强制单元格的内容视图占用其宽度。
您可能想要修改其中的一些 cell-handling 代码。
如果你想从 xib 加载你的单元格,你可以跳过所有有约束的东西。只需实施:
override func viewDidLoad() {
//...
let nib = UINib(nibName: "RowView0002", bundle: NSBundle.main)
tableView.reigsterNib(nib, forCellReuseIdentifier: "RowView0002")
}
非常重要:.xib 文件中的第一个顶级项目必须是 UITableViewCell。 Nibs默认是UIViews,删除IB中的view,在IB的lower-right中从对象库中拖出一个UITableViewCell。然后,如有必要,将其 subclass 设置为您创建的 UITableViewCell subclass。 (您可能还需要在 IB 中设置 reuseIdentifier。)
然后在 tableView(_:cellForRowAt IndexPath:)
中:
guard let cell = tableView.dequeueResuableCell(withIdentifier: "RowView0002", for: indexPath) as? TheNibUITableViewSubclass else { //something went wrong, probably crash }
cell.label.text = //...
return cell
您可能希望将 "RowView0002" 放在某处的常量中。
如果 "RowView0002" 和 RowView
class 都需要成为视图,您可能应该创建 UITableViewCell
的子 class。仅覆盖 init(style:resueIdentifier:) and after calling
super` 在上面的代码中添加您的子视图。希望这对您有所帮助!
我通过添加与 tableViews 宽度匹配的宽度约束来解决它。这是来自 CustomTableViewCell 的代码:
public override func layoutSubviews() {
super.layoutSubviews()
if let width = tableView()?.frame.width, !haveAddedWidthConstraint {
haveAddedWidthConstraint = true
rowView.addWidthConstraint(width: width)
}
}
UIViewExtension:
public func addWidthConstraint(width: CGFloat) {
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width)
widthConstraint.priority = 1000
addConstraint(widthConstraint)
}
UITableViewCellExtension:
func tableView() -> UITableView? {
var currentView: UIView = self
while let superView = currentView.superview {
if superView is UITableView {
return (superView as! UITableView)
}
currentView = superView
}
return nil
}
layoutAttachAll 的完整实现如下。
首先是一些用法示例:
// pin all edge to superview
myView.layoutAttachAll()
// pin all edges (to superview) with margin:
myView.layoutAttachAll(margin: 8.0)
// for child views: pin leading edge to superview's leading edge:
myView.layoutAttachLeading()
// for sibling views: pin leading edge to siblingView's trailing edge:
myView.layoutAttachLeading(to: siblingView)
// for sibling views: pin top edge to siblingView's bottom edge:
myView.layoutAttachTop(to: siblingView)
注意:在使用这些方法附加到超级视图之前,必须将 myView 添加为子视图。此外,必须使用 translatesAutoresizingMaskIntoConstraints = false 设置所有参与视图。
完整实施:
import UIKit
extension UIView {
/// attaches all sides of the receiver to its parent view
func layoutAttachAll(margin : CGFloat = 0.0) {
let view = superview
layoutAttachTop(to: view, margin: margin)
layoutAttachBottom(to: view, margin: margin)
layoutAttachLeading(to: view, margin: margin)
layoutAttachTrailing(to: view, margin: margin)
}
/// attaches the top of the current view to the given view's top if it's a superview of the current view, or to it's bottom if it's not (assuming this is then a sibling view).
/// if view is not provided, the current view's super view is used
@discardableResult
func layoutAttachTop(to: UIView? = nil, margin : CGFloat = 0.0) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = view == superview
let constraint = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: isSuperview ? .top : .bottom, multiplier: 1.0, constant: margin)
superview?.addConstraint(constraint)
return constraint
}
/// attaches the bottom of the current view to the given view
@discardableResult
func layoutAttachBottom(to: UIView? = nil, margin : CGFloat = 0.0, priority: UILayoutPriority? = nil) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: isSuperview ? .bottom : .top, multiplier: 1.0, constant: -margin)
if let priority = priority {
constraint.priority = priority
}
superview?.addConstraint(constraint)
return constraint
}
/// attaches the leading edge of the current view to the given view
@discardableResult
func layoutAttachLeading(to: UIView? = nil, margin : CGFloat = 0.0) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: isSuperview ? .leading : .trailing, multiplier: 1.0, constant: margin)
superview?.addConstraint(constraint)
return constraint
}
/// attaches the trailing edge of the current view to the given view
@discardableResult
func layoutAttachTrailing(to: UIView? = nil, margin : CGFloat = 0.0, priority: UILayoutPriority? = nil) -> NSLayoutConstraint {
let view: UIView? = to ?? superview
let isSuperview = (view == superview) || false
let constraint = NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: isSuperview ? .trailing : .leading, multiplier: 1.0, constant: -margin)
if let priority = priority {
constraint.priority = priority
}
superview?.addConstraint(constraint)
return constraint
}
}
我通过在我的自定义 UITableViewCell class 中公开一个设置 UITableViewCell 的锚约束的方法并从父 UITableView cellForRowAtIndexPath 调用它来解决它 方法。
这是Objective C中的代码。
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourTableViewCellType *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
**[cell expandToParentSize];** // Self-sizing magic!
return cell;
}
// Write below code in "YourTableViewCellType" class. "containerStack" is my UIStackView that holds all controls in the nib UITableViewCell
(void)expandToParentSize
{
[NSLayoutConstraint activateConstraints:@[
[self.containerStack.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.containerStack.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.containerStack.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.containerStack.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
]];
}