UITableViewCell 内的多个堆栈视图内的多行标签

Posted

技术标签:

【中文标题】UITableViewCell 内的多个堆栈视图内的多行标签【英文标题】:Multilinelabel inside multiple stackviews inside UITableViewCell 【发布时间】:2019-10-21 07:54:18 【问题描述】:

我有如下视图层次结构;

UITableViewCell ->
                  -> UIView -> UIStackView (axis: vertical, distribution: fill)
                    -> UIStackView (axis: horizontal, alignment: top, distribution: fillEqually)
                     -> UIView -> UIStackView(axis:vertical, distribution: fill)
                      -> TwoLabelView

我的问题是标签不能超过一行。我阅读了 SO 中的每一个问题,也尝试了每一种可能性,但都没有奏效。在下面的屏幕截图中,在左上角的框中,应该有两对标签,但其中一个没有显示。

我的问题是如何在第一个框中实现多行(左右两边)?

如果我将顶部堆栈视图分布更改为 fillProportionally,标签会变为多行,但第一个框的最后一个元素与框本身之间会有间隙

我的第一个顶部堆栈视图

//This is the Stackview used just below UITableViewCell
private let stackView: UIStackView = 
let s = UIStackView()
s.distribution = .fill
s.axis = .vertical
s.spacing = 10
s.translatesAutoresizingMaskIntoConstraints = false
return s
()

//This is used to create two horizontal box next to each other
private let myStackView: UIStackView = 
let s = UIStackView()
s.distribution = .fillEqually
s.spacing = 10
s.axis = .horizontal
//s.alignment = .center
s.translatesAutoresizingMaskIntoConstraints = false
return s
()

UILabel 类:

fileprivate class FixAutoLabel: UILabel 

override func layoutSubviews() 
    super.layoutSubviews()
    if(self.preferredMaxLayoutWidth != self.bounds.size.width) 
        self.preferredMaxLayoutWidth = self.bounds.size.width
    




@IBDesignable class TwoLabelView: UIView 

var topMargin: CGFloat = 0.0
var verticalSpacing: CGFloat = 3.0
var bottomMargin: CGFloat = 0.0

@IBInspectable var firstLabelText: String = ""  didSet  updateView()  
@IBInspectable var secondLabelText: String = ""  didSet  updateView()  

fileprivate var firstLabel: FixAutoLabel!
fileprivate var secondLabel: FixAutoLabel!

override init(frame: CGRect) 
    super.init(frame: frame)
    setUpView()


required public init?(coder: NSCoder) 
    super.init(coder:coder)
    setUpView()


override func prepareForInterfaceBuilder() 
    super.prepareForInterfaceBuilder()
    setUpView()


func setUpView() 

    firstLabel = FixAutoLabel()
    firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
    firstLabel.numberOfLines = 0
    firstLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail

    secondLabel = FixAutoLabel()
    secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
    secondLabel.numberOfLines = 1
    secondLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail

    addSubview(firstLabel)
    addSubview(secondLabel)

    // we're going to set the constraints
    firstLabel .translatesAutoresizingMaskIntoConstraints = false
    secondLabel.translatesAutoresizingMaskIntoConstraints = false

    // pin both labels' left-edges to left-edge of self
    firstLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true
    secondLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true

    // pin both labels' right-edges to right-edge of self
    firstLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true
    secondLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true

    // pin firstLabel to the top of self + topMargin (padding)
    firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin).isActive = true

    // pin top of secondLabel to bottom of firstLabel + verticalSpacing
    secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing).isActive = true

    // pin bottom of self to bottom of secondLabel + bottomMargin (padding)
    bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor, constant: bottomMargin).isActive = true

    // call common "refresh" func
    updateView()


func updateView() 

    firstLabel.preferredMaxLayoutWidth = self.bounds.width
    secondLabel.preferredMaxLayoutWidth = self.bounds.width

    firstLabel.text = firstLabelText
    secondLabel.text = secondLabelText

    firstLabel.sizeToFit()
    secondLabel.sizeToFit()

    setNeedsUpdateConstraints()



override open var intrinsicContentSize : CGSize 
    // just has to have SOME intrinsic content size defined
    // this will be overridden by the constraints
    return CGSize(width: 1, height: 1)


UIView -> UIStackView 类

class ViewWithStack: UIView 

let verticalStackView: UIStackView = 
    let s = UIStackView()
    s.distribution = .fillEqually
    s.spacing = 10
    s.axis = .vertical
    s.translatesAutoresizingMaskIntoConstraints = false
    return s
()

override init(frame: CGRect) 
    super.init(frame: frame)

    self.translatesAutoresizingMaskIntoConstraints = false
    self.backgroundColor = UIColor.white
    self.layer.cornerRadius = 6.0
    self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)

    addSubview(verticalStackView)
    let lessThan = verticalStackView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: 0)
    lessThan.priority = UILayoutPriority(1000)
    lessThan.isActive = true
    verticalStackView.leftAnchor.constraint(equalTo: self.leftAnchor,constant: 0).isActive = true
    verticalStackView.rightAnchor.constraint(equalTo: self.rightAnchor,constant: 0).isActive = true
    verticalStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true

    verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
    verticalStackView.isLayoutMarginsRelativeArrangement = true


convenience init(orientation: NSLayoutConstraint.Axis,labelsArray: [UIView]) 
    self.init()
    verticalStackView.axis = orientation
    for label in labelsArray 
        verticalStackView.addArrangedSubview(label)
    

required init?(coder: NSCoder) 
    fatalError("init(coder:) has not been implemented")


示例控制器类(这是整个项目的最小化版本):

class ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource 

@IBOutlet weak var tableView: UITableView!
let viewWithStack = BoxView()
override func viewDidLoad() 
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    tableView.delegate = self
    tableView.dataSource = self
    tableView.register(TableViewCell.self, forCellReuseIdentifier: "myCell")
    tableView.rowHeight = UITableView.automaticDimension



func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return 2


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! TableViewCell

    if (indexPath.row == 0) 
        cell.setup(viewWithStack: self.viewWithStack)
     else 
        cell.backgroundColor = UIColor.black
    
    return cell


func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    //return 500
    if ( indexPath.row == 0) 
        return UITableView.automaticDimension
     else 
        return 40
    



编辑 我创建了一个最小的项目,然后我发现我的问题是我的项目实现了 heightForRow 函数,该函数覆盖了 UITableViewAutomaticDimension,因此它为我的组件提供了错误的高度。我想我应该看看如何获​​得组件的高度大小?因为我无法删除解决我的问题的heightForRow 函数。

示例项目链接 https://github.com/emreond/tableviewWithStackView/tree/master/tableViewWithStackViewEx

当您打开视图调试器时,示例项目具有雄心勃勃的布局。我想当我修复它们时,一切都会好起来的。

【问题讨论】:

正如我在您的类似问题 (***.com/questions/58413397/…) 中提到的,仅查看代码片段时很难提供帮助。将minimal reproducible example 放在一起,以便有人可以确切地看到发生了什么。 我正在做一个非常大的项目,我什至无法重现该示例,但我会尝试。 看起来你有很多事情是不必要的。很少需要设置.preferredMaxLayoutWidth 或调用sizeToFit()setNeedsUpdateConstraints()。尝试简化您正在做的事情。 我编辑了我的问题。你能检查一下吗? 为什么要实施heightForRowAt?您可以在下面的示例中看到它是不需要的。 【参考方案1】:

这是一个完整的示例,应该可以满足您的需求(这就是我所说的 minimal reproducible example):

检查此问题的最佳方法是:

创建一个新项目 创建一个新文件,命名为TestTableViewController.swift 将以下代码复制并粘贴到该文件中(替换默认模板代码) 将UITableViewController 添加到情节提要 将其自定义类分配给TestTableViewController 将其嵌入UINavigationControllerUINavigationController设置为Is Initial View Controller 运行应用程序

这是你应该看到的结果:

我根据您发布的内容创建课程(删除了不必要的代码,我假设您的其他单元格按需要工作)。

//
//  TestTableViewController.swift
//
//  Created by Don Mag on 10/21/19.
//

import UIKit

class SideBySideCell: UITableViewCell 

    let horizStackView: UIStackView = 
        let v = UIStackView()
        v.axis = .horizontal
        v.alignment = .fill
        v.distribution = .fillEqually
        v.spacing = 10
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    ()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    

    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    

    override func prepareForReuse() 
        horizStackView.arrangedSubviews.forEach 
            $0.removeFromSuperview()
        
    

    func commonInit() -> Void 

        contentView.backgroundColor = UIColor(white: 0.8, alpha: 1.0)

        contentView.addSubview(horizStackView)

        let g = contentView.layoutMarginsGuide

        NSLayoutConstraint.activate([
            horizStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            horizStackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            horizStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            horizStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
        ])

    

    func addViewWithStack(_ v: ViewWithStack) -> Void 
        horizStackView.addArrangedSubview(v)
    



class TestTableViewController: UITableViewController 

    let sideBySideReuseID = "sbsID"

    override func viewDidLoad() 
        super.viewDidLoad()

        // register custom SideBySide cell for reuse
        tableView.register(SideBySideCell.self, forCellReuseIdentifier: sideBySideReuseID)

        tableView.separatorStyle = .none

    

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 10
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

        if indexPath.row == 0 

            let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell

            let twoLabelView1 = TwoLabelView()
            twoLabelView1.firstLabelText = "Text for first label on left-side."
            twoLabelView1.secondLabelText = "10.765,00TL"

            let twoLabelView2 = TwoLabelView()
            twoLabelView2.firstLabelText = "Text for second-first label on left-side."
            twoLabelView2.secondLabelText = "10.765,00TL"

            let twoLabelView3 = TwoLabelView()
            twoLabelView3.firstLabelText = "Text for the first label on right-side."
            twoLabelView3.secondLabelText = "10.765,00TL"

            let leftStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView1, twoLabelView2])
            let rightStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView3])

            cell.addViewWithStack(leftStackV)
            cell.addViewWithStack(rightStackV)

            return cell

        

        // create ViewWithStack using just a simple label
        let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell

        let v = UILabel()
        v.text = "This is row \(indexPath.row)"
        let aStackV = ViewWithStack(orientation: .vertical, labelsArray: [v])
        cell.addViewWithStack(aStackV)

        return cell

    



@IBDesignable class TwoLabelView: UIView 

    var topMargin: CGFloat = 0.0
    var verticalSpacing: CGFloat = 3.0
    var bottomMargin: CGFloat = 0.0

    @IBInspectable var firstLabelText: String = ""  didSet  updateView()  
    @IBInspectable var secondLabelText: String = ""  didSet  updateView()  

    fileprivate var firstLabel: UILabel = 
        let v = UILabel()
        return v
    ()

    fileprivate var secondLabel: UILabel = 
        let v = UILabel()
        return v
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)
        setUpView()
    

    required public init?(coder: NSCoder) 
        super.init(coder:coder)
        setUpView()
    

    override func prepareForInterfaceBuilder() 
        super.prepareForInterfaceBuilder()
        setUpView()
    

    func setUpView() 

        firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
        firstLabel.numberOfLines = 0

        secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
        secondLabel.numberOfLines = 1

        addSubview(firstLabel)
        addSubview(secondLabel)

        // we're going to set the constraints
        firstLabel .translatesAutoresizingMaskIntoConstraints = false
        secondLabel.translatesAutoresizingMaskIntoConstraints = false

        // Note: recommended to use Leading / Trailing rather than Left / Right
        NSLayoutConstraint.activate([

            // pin both labels' left-edges to left-edge of self
            firstLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
            secondLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),

            // pin both labels' right-edges to right-edge of self
            firstLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
            secondLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),

            // pin firstLabel to the top of self + topMargin (padding)
            firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin),

            // pin top of secondLabel to bottom of firstLabel + verticalSpacing
            secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing),

            // pin bottom of self to >= (bottom of secondLabel + bottomMargin (padding))
            bottomAnchor.constraint(greaterThanOrEqualTo: secondLabel.bottomAnchor, constant: bottomMargin),

        ])

    

    func updateView() -> Void 
        firstLabel.text = firstLabelText
        secondLabel.text = secondLabelText
    



class ViewWithStack: UIView 

    let verticalStackView: UIStackView = 
        let s = UIStackView()
        s.distribution = .fill
        s.spacing = 10
        s.axis = .vertical
        s.translatesAutoresizingMaskIntoConstraints = false
        return s
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)

        self.translatesAutoresizingMaskIntoConstraints = false
        self.backgroundColor = UIColor.white
        self.layer.cornerRadius = 6.0
//      self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)

        addSubview(verticalStackView)

        NSLayoutConstraint.activate([

            // constrain to all 4 sides
            verticalStackView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
            verticalStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
            verticalStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
            verticalStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),

        ])

        verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
        verticalStackView.isLayoutMarginsRelativeArrangement = true

    

    convenience init(orientation: NSLayoutConstraint.Axis, labelsArray: [UIView]) 
        self.init()
        verticalStackView.axis = orientation
        for label in labelsArray 
            verticalStackView.addArrangedSubview(label)
        
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

【讨论】:

以上是关于UITableViewCell 内的多个堆栈视图内的多行标签的主要内容,如果未能解决你的问题,请参考以下文章

检测 UITableViewCell 子视图内的触摸

如何使控件占据堆栈内的实际大小

容器视图内的 UIPageViewController 内的堆栈视图的自动布局?

水平堆栈视图内的标签宽度相等?

UITableView 单元内的 UIScrollView 内的空白 UITableView

UITableViewCell 位置问题内的 UIView - 已更新