没有情节提要的自定义单元格中自定义 UITableViewCell 内的多个 UIStackViews 无法正常工作

Multiple UIStackViews inside a custom UITableViewCell in Custom Cell without Storyboard not working

我目前正在我的应用程序中构建一个屏幕,它基本上是一个包含 3 个部分的长 UITableView,每个部分都有不同数量的独特自定义单元格。设置 tableview 工作正常,我在单元格中添加了一些随机文本以确保每个单元格都被正确调用和定位。我已经从我的项目中完全删除了我的故事板,因为它会由于某些原因导致以后出现问题。所以我不能通过故事板做任何事情。

下一步是构建自定义单元格。作为初学者,其中一些对我来说相当复杂。这是我的一个手机:

我想将单元格拆分为多个 UIStackView,一个用于图片和名称,一个用于右侧的统计信息,后者本身将包含两个堆栈视图,其中包含两行统计信息中的每一行。然后,其中每一个都可以包含另一个嵌入式堆栈视图,其中包含两个 uiLabel、编号和描述。最重要的是一个切换按钮。

我似乎无法理解如何定义这一切。正如我所说,我定义了 Tableview 并在我的 cellForRowAt 中调用正确的单元格,如下所示:

if indexPath.row == 0 && indexPath.section == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: StatsOverViewCell.identifier, for: indexPath) as! StatsOverViewCell
        cell.configure()
        return cell
} else if ...

我已经为每个单元格创建了文件,其中之一是 StatsOverViewCell。 在这个文件中,我有一个与 class 同名的标识符。 我还添加了我从我的 tableview 调用的配置函数,我用来在单元格内布置视图的 layoutSubviews 函数,我已经初始化了我需要的每个标签和图像。我已将文件缩减为几个示例以节省您一些时间:

class StatsOverViewCell: UITableViewCell {

//Set identifier to be able to call it later on
static let identifier = "StatsOverViewCell"

let myProfileStackView = UIStackView()
let myImageView = UIImageView()
let myName = UILabel()
let myGenderAndAge = UILabel()

let myStatsStackView = UIStackView()

let oneView = UIStackView()
let oneStat = UILabel()
let oneLabel = UILabel()

let twoStackView = UIStackView()
let twoStat = UILabel()
let twoLabel = UILabel()

//Do this for each of the labels I have in the stats

public func configure() {
    myImageView.image = UIImage(named: "person-icon")
    myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    myImageView.contentMode = .scaleAspectFill
    myName.text = "Name."
    myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    myName.textAlignment = .center
    //Add the Name label to the stackview
    myProfileStackView.addArrangedSubview(myName)
    myProfileStackView.addArrangedSubview(myImageView)
    myName.centerXAnchor.constraint(equalTo: myProfileStackView.centerXAnchor).isActive = true
    
    oneStat.text = "5.187"
    oneStat.font = UIFont(name: "montserrat", size: 18)
    oneLabel.text = "Text"
    oneLabel.font = UIFont(name: "montserrat", size: 14)
}

//Layout in the cell
override func layoutSubviews() {
    super.layoutSubviews()
    contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
    contentView.layer.borderWidth = 1
    
    //Stackview
    contentView.addSubview(myProfileStackView)
    myProfileStackView.axis = .vertical
    myProfileStackView.distribution = .equalSpacing
    myProfileStackView.spacing = 3.5
    myProfileStackView.backgroundColor = .red
    
    myProfileStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 23).isActive = true
    myProfileStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 76).isActive = true
}

如您所见,我在 tableview 中创建单元格时调用的配置方法中将所有 arrangedsubview 添加到 stackview。然后我在布局子视图中设置堆栈视图约束。我没有收到任何错误或任何东西。但是单元格显示完全是空的。

我觉得我忘记了什么,或者我不明白如何使用 uistackviews 创建单元格。我应该在哪里创建 stackviews,我应该在哪里将 arrangedsubviews 添加到这个 stackviews 以及我在 LayoutSubviews 中做什么? 我将非常感谢任何见解。 感谢您的宝贵时间!

你做错了几件事...

  1. 您的 UI 元素应该在 init 中创建和配置,而不是在 configure()layoutSubviews()
  2. 您需要完整的约束才能为您的元素提供正确的布局

看看我对你的手机所做的更改class。它应该让你上路:

class StatsOverViewCell: UITableViewCell {
    
    //Set identifier to be able to call it later on
    static let identifier = "StatsOverViewCell"
    
    let myProfileStackView = UIStackView()
    let myImageView = UIImageView()
    let myName = UILabel()
    let myGenderAndAge = UILabel()
    
    let myStatsStackView = UIStackView()
    
    let oneView = UIStackView()
    let oneStat = UILabel()
    let oneLabel = UILabel()
    
    let twoStackView = UIStackView()
    let twoStat = UILabel()
    let twoLabel = UILabel()
    
    //Do this for each of the labels I have in the stats
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() {
        myImageView.image = UIImage(named: "person-icon")

        // frame doesn't matter - stack view arrangedSubvies automatically
        //  set .translatesAutoresizingMaskIntoConstraints = false
        //myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        myImageView.contentMode = .scaleAspectFill
        myName.text = "Name."
        
        myName.textAlignment = .center
        
        //Add the Name label to the stackview
        myProfileStackView.addArrangedSubview(myName)
        myProfileStackView.addArrangedSubview(myImageView)
        
        // no need for this
        //myName.centerXAnchor.constraint(equalTo: myProfileStackView.centerXAnchor).isActive = true
        
        oneStat.text = "5.187"
        oneStat.font = UIFont(name: "montserrat", size: 18)
        oneLabel.text = "Text"
        oneLabel.font = UIFont(name: "montserrat", size: 14)
        
        contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
        contentView.layer.borderWidth = 1
        
        //Stackview
        contentView.addSubview(myProfileStackView)
        myProfileStackView.axis = .vertical
        
        // no need for equalSpacing if you're explicitly setting the spacing
        //myProfileStackView.distribution = .equalSpacing
        myProfileStackView.spacing = 3.5
        myProfileStackView.backgroundColor = .red
        
        // stack view needs .translatesAutoresizingMaskIntoConstraints = false
        myProfileStackView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([

            // stack view leading 23-pts from contentView leading
            myProfileStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 23),

            // stack view top 76-pts from contentView top
            myProfileStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 76),
            
            // need something to set the contentView height
            
            // stack view bottom 8-pts from contentView bottom
            myProfileStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
            
            // set imageView width and height
            myImageView.widthAnchor.constraint(equalToConstant: 100.0),
            myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),

        ])
    }
    
    public func configure() {
        // here you would set the properties of your elements, such as
        //  label text
        //  imageView image
        //  colors
        //  etc
    }
}

编辑

这是一个示例单元格 class,它与您发布的图片中的布局很接近。

请注意,需要的约束很少:

NSLayoutConstraint.activate([
        
    // role element 12-pts from top
    myRoleElement.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
    // centered horizontally
    myRoleElement.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
    // it will probably be using intrinsic height and width, but for demo purposes
    myRoleElement.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.4),
    myRoleElement.heightAnchor.constraint(equalToConstant: 40.0),
        
    // stack view 24-pts on each side
    hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
    hStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
    // stack view 20-pts on bottom
    hStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
    // stack view top 20-pts from Role element bottom
    hStack.topAnchor.constraint(equalTo: myRoleElement.bottomAnchor, constant: 20),
        
    // set imageView width and height
    myImageView.widthAnchor.constraint(equalToConstant: 100.0),
    myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),
        
    // we want the two "column" stack views to be equal widths
    hStack.arrangedSubviews[1].widthAnchor.constraint(equalTo: hStack.arrangedSubviews[2].widthAnchor),
        
])

这是完整的单元格 class,包括一个示例“UserStruct”……当然,您会想要调整字体/大小、间距等:

// sample struct for user data
struct UserStruct {
    var profilePicName: String = ""
    var name: String = ""
    var gender: String = ""
    var age: Int = 0
    var statValues: [String] = []
}

class StatsOverViewCell: UITableViewCell {
    
    //Set identifier to be able to call it later on
    static let identifier = "StatsOverViewCell"
    
    // whatever your "role" element is...
    let myRoleElement = UILabel()
    
    let myImageView = UIImageView()
    let myName = UILabel()
    let myGenderAndAge = UILabel()

    var statValueLabels: [UILabel] = []

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() {
        
        // create 6 Value and 6 text labels
        // assuming you have 6 "Text" strings, but for now
        //  we'll use "Text A", "Text B", etc
        let tmp: [String] = [
            "A", "B", "C",
            "D", "E", "F",
        ]

        var statTextLabels: [UILabel] = []

        for i in 0..<6 {

            var lb = UILabel()
            lb.font = UIFont.systemFont(ofSize: 16, weight: .regular)
            lb.textAlignment = .center
            lb.textColor = .white
            lb.text = "0"
            statValueLabels.append(lb)
            
            lb = UILabel()
            lb.font = UIFont.systemFont(ofSize: 13, weight: .regular)
            lb.textAlignment = .center
            lb.textColor = .lightGray
            lb.text = "Text \(tmp[i])"
            statTextLabels.append(lb)
            
        }
        
        // name and Gender/Age label properties
        myName.textAlignment = .center
        myGenderAndAge.textAlignment = .center
        myName.font = UIFont.systemFont(ofSize: 15, weight: .regular)
        myGenderAndAge.font = UIFont.systemFont(ofSize: 15, weight: .regular)
        myName.textColor = .white
        myGenderAndAge.textColor = .white

        // placeholder text
        myName.text = "Name"
        myGenderAndAge.text = "(F, 26)"
        
        myImageView.contentMode = .scaleAspectFill
        
        // create the "Profile" stack view
        let myProfileStackView = UIStackView()
        myProfileStackView.axis = .vertical
        myProfileStackView.spacing = 2

        //Add imageView, name and gender/age labels to the profile stackview
        myProfileStackView.addArrangedSubview(myImageView)
        myProfileStackView.addArrangedSubview(myName)
        myProfileStackView.addArrangedSubview(myGenderAndAge)
        
        // create horizontal stack view to hold
        //  Profile stack + two "column" stack views
        let hStack = UIStackView()

        // add Profile stack view
        hStack.addArrangedSubview(myProfileStackView)
        
        var j: Int = 0

        // create two "column" stack views
        //  each with three "label pair" stack views
        for _ in 0..<2 {
            let columnStack = UIStackView()
            columnStack.axis = .vertical
            columnStack.distribution = .equalSpacing

            for _ in 0..<3 {
                let pairStack = UIStackView()
                pairStack.axis = .vertical
                pairStack.spacing = 4
                pairStack.addArrangedSubview(statValueLabels[j])
                pairStack.addArrangedSubview(statTextLabels[j])
                columnStack.addArrangedSubview(pairStack)
                j += 1
            }
            
            hStack.addArrangedSubview(columnStack)
        }
        
        // whatever your "Roles" element is...
        //  here, we'll simulate it with a label
        myRoleElement.text = "Role 1 / Role 2"
        myRoleElement.textAlignment = .center
        myRoleElement.textColor = .white
        myRoleElement.backgroundColor = .systemTeal
        myRoleElement.layer.cornerRadius = 8
        myRoleElement.layer.borderWidth = 1
        myRoleElement.layer.borderColor = UIColor.white.cgColor
        myRoleElement.layer.masksToBounds = true
        
        // add Role element and horizontal stack view to contentView
        contentView.addSubview(myRoleElement)
        contentView.addSubview(hStack)
        
        myRoleElement.translatesAutoresizingMaskIntoConstraints = false
        hStack.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            
            // role element 12-pts from top
            myRoleElement.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
            // centered horizontally
            myRoleElement.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            // it will probably be using intrinsic height and width, but for demo purposes
            myRoleElement.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.4),
            myRoleElement.heightAnchor.constraint(equalToConstant: 40.0),
            
            // stack view 24-pts on each side
            hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
            hStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
            // stack view 20-pts on bottom
            hStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
            // stack view top 20-pts from Role element bottom
            hStack.topAnchor.constraint(equalTo: myRoleElement.bottomAnchor, constant: 20),
            
            // set imageView width and height
            myImageView.widthAnchor.constraint(equalToConstant: 100.0),
            myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),
            
            // we want the two "column" stack views to be equal widths
            hStack.arrangedSubviews[1].widthAnchor.constraint(equalTo: hStack.arrangedSubviews[2].widthAnchor),
            
        ])

        //contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
        contentView.backgroundColor = UIColor(red: 0x3f / 255.0, green: 0x45 / 255.0, blue: 0x4b / 255.0, alpha: 1.0)
        contentView.layer.borderWidth = 1
        contentView.layer.borderColor = UIColor.lightGray.cgColor
        
        // since we're setting the image view to explicit 100x100 size,
        //  we can make it round here
        myImageView.layer.cornerRadius = 50
        myImageView.layer.masksToBounds = true
    }
    
    public func configure(_ user: UserStruct) {
        // here you would set the properties of your elements

        // however you're getting your profile image
        var img: UIImage!
        if !user.profilePicName.isEmpty {
            img = UIImage(named: user.profilePicName)
        }
        if img == nil {
            img = UIImage(named: "person-icon")
        }
        if img != nil {
            myImageView.image = img
        }
        
        myName.text = user.name
        myGenderAndAge.text = "(\(user.gender), \(user.age))"
        
        // probably want error checking to make sure we have 6 values
        if user.statValues.count == 6 {
            for (lbl, s) in zip(statValueLabels, user.statValues) {
                lbl.text = s
            }
        }
    }
    
}

和示例 table 视图控制器:

class UserStatsTableViewController: UITableViewController {
    
    var myData: [UserStruct] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.register(StatsOverViewCell.self, forCellReuseIdentifier: StatsOverViewCell.identifier)
        
        // generate some sample data
        //      I'm using Female "pro1" and Male "pro2" images
        for i in 0..<10 {
            var user = UserStruct(profilePicName: i % 2 == 0 ? "pro2" : "pro1",
                                  name: "Name \(i)",
                                  gender: i % 2 == 0 ? "F" : "M",
                                  age: Int.random(in: 21...65))
            var vals: [String] = []
            for _ in 0..<6 {
                let v = Int.random(in: 100..<1000)
                vals.append("\(v)")
            }
            user.statValues = vals
            myData.append(user)
        }
    }
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: StatsOverViewCell.identifier, for: indexPath) as! StatsOverViewCell
        let user = myData[indexPath.row]
        cell.configure(user)
        return cell
    }
}

这是 运行 时间的样子: