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

Multilinelabel inside multiple stackviews inside UITableViewCell

我有如下视图层次结构;

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

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

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

如果我将顶部堆栈视图分布更改为 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 Class:

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

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(这是整个项目的最小化版本):

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
    }
}

}

EDIT 我创建了一个最小的项目然后我发现我的问题是我的项目实现了 heightForRow 覆盖 UITableViewAutomaticDimension 的函数所以它给出了错误我的组件的高度。我想我应该看看如何获​​得组件的高度尺寸?因为我无法删除解决我问题的 heightForRow 函数。

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

当您打开视图调试器时,示例项目具有雄心勃勃的布局。我想当我修复它们时,一切都应该没问题。

这里是一个完整的例子,它应该做你想做的事(这就是我所说的minimal reproducible example):

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

  • 创建一个新项目
  • 创建一个新文件,命名为TestTableViewController.swift
  • 将下面的代码复制并粘贴到该文件中(替换默认模板代码)
  • 向故事板添加 UITableViewController
  • 将其自定义 Class 分配给 TestTableViewController
  • 将其嵌入 UINavigationController
  • UINavigationController 设置为 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 {
            [=10=].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")
    }
}