【问题标题】:Multiple UIStackViews inside a custom UITableViewCell in Custom Cell without Storyboard not working没有故事板的自定义单元格中的自定义 UITableViewCell 中的多个 UIStackViews 不起作用
【发布时间】:2021-12-24 00:45:14
【问题描述】:

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

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

我想将单元格拆分为多个 UIStackViews,一个用于图片和名称,一个用于右侧的统计信息,它本身将包含两个堆栈视图,其中包含两行统计信息中的每一行。然后,它们中的每一个都可以包含另一个嵌入的 stackview,其中包含两个 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。 在这个文件中,我有一个与类同名的标识符。 我还添加了我从 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
}

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

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

【问题讨论】:

  • 我可能想补充一点,我也正确注册了所有单元格。问题实际上只是一个单元格的样式,它具有互锁的 uistackviews 而没有故事板。

标签: ios swift xcode uitableview uistackview


【解决方案1】:

你做错了一些事情......

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

看看我对你的单元格所做的更改。它应该能让你上路:

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

编辑

这是一个与您发布的图片中的布局非常接近的示例单元格类。

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

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),
        
])

这是完整的单元格类,包括一个示例“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
            }
        }
    }
    
}

和一个示例表视图控制器:

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

这是它在运行时的样子:

【讨论】:

  • 非常感谢@DonMag,我得到了它的工作,或者说,感谢您的输入显示一些东西。我想我误解了一些功能,但现在更有意义了。虽然我不得不说,为这么多相互联锁的堆栈视图设置约束并非易事。再次感谢!
  • @JanL - 堆栈视图旨在布局其排列的子视图......您应该需要非常非常少的约束来获得您在帖子中显示的布局。
  • @JanL - 为了向您展示需要多少约束,我整理了一个与您的布局接近的快速示例单元格。请参阅我的答案的编辑
  • 我印象深刻,这非常接近我需要的布局。我会克服这些限制,看看你做了什么不同的事情。出于某种原因,我的一些约束与其他约束发生冲突,因此被忽略了。感谢一百万您所做的工作,我真的可以用它来了解约束和堆栈视图!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-08
  • 2014-02-24
  • 2012-09-26
  • 2012-05-31
  • 2012-01-26
  • 1970-01-01
相关资源
最近更新 更多