如何在stackview和tableviewcell中设置textview的动态高度?

How can I set dynamic height of textview inside of stackview and tableviewcell?

提问人:피우리 提问时间:11/8/2023 最后编辑:HangarRash피우리 更新时间:11/12/2023 访问量:59

问:

我正在开发待办事项应用程序,但我不知道该怎么做。 我已经做了它的每一个解决方案。(isScrollEnabled = false, .sizetofit(), translaste~ =true ...)

如何在stackview和tableviewcell中设置textview的动态高度? 我想将textview的基本高度大于50,然后动态更改textview高度。

我在下面附上了我的代码。

class ToDoTableViewCell: UITableViewCell, UITextViewDelegate {
    
    lazy var backColorView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let memoString: UITextView = {
        let textView = UITextView()
        textView.font = .systemFont(ofSize: 17, weight: .regular)
        textView.layer.cornerRadius = 0
        textView.layer.borderWidth = 1
        textView.layer.borderColor = #colorLiteral(red: 0.2588235438, green: 0.7568627596, blue: 0.9686274529, alpha: 1)
        textView.backgroundColor = .clear
        textView.isS
        return textView
    }()
    
    let dateString: UILabel = {
        let date = UILabel()
        date.font = .systemFont(ofSize: 14, weight: .light)
        date.text = "2021-11-12"
        return date
    }()
    
    let updateButton: UIButton = {
        let button = UIButton()
        button.setTitle("UPDATE", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 9, weight: .bold)
        button.layer.cornerRadius = 10
        button.backgroundColor = .gray
        button.setImage(UIImage(systemName: "pencil"), for: .normal)
        return button
    }()
    
    let totalStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.distribution  = .fill
        stack.alignment = .fill
        stack.spacing = 10
        return stack
    }()
    
    let subStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.distribution  = .fill
        stack.alignment = .fill
        stack.spacing = 0
        return stack
    }()
    
    
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        memoString.delegate = self

    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .default, reuseIdentifier: reuseIdentifier)
        setupStackView()
        setConstraints()
        

    }

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

    func setupStackView() {
        
        self.contentView.addSubview(backColorView)
        
        totalStackView.addArrangedSubview(memoString)
        totalStackView.addArrangedSubview(subStackView)
        
        subStackView.addArrangedSubview(dateString)
        subStackView.addArrangedSubview(updateButton)
        
        self.contentView.addSubview(totalStackView)
        
    }
    
    
    func setConstraints() {
        totalStackView.translatesAutoresizingMaskIntoConstraints = false
        subStackView.translatesAutoresizingMaskIntoConstraints = false
        updateButton.translatesAutoresizingMaskIntoConstraints = false
        memoString.translatesAutoresizingMaskIntoConstraints = false
       
        
        
        
        
        NSLayoutConstraint.activate([
            
            totalStackView.leadingAnchor.constraint(equalTo: backColorView.leadingAnchor, constant: 10),
            totalStackView.trailingAnchor.constraint(equalTo: backColorView.trailingAnchor, constant: -10),
            totalStackView.topAnchor.constraint(equalTo: backColorView.topAnchor, constant: 10),
            totalStackView.bottomAnchor.constraint(equalTo: backColorView.bottomAnchor, constant: -10),
            
            
            subStackView.heightAnchor.constraint(equalToConstant: 30),
            updateButton.widthAnchor.constraint(equalToConstant: 80),
            
            backColorView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 25),
            backColorView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -25),
            backColorView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            backColorView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
            
           memoString.heightAnchor.constraint(greaterThanOrEqualToConstant: 50)
           
 
        ])
    }
}
class ViewController: UIViewController {

    private let tableView = UITableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        subviews()
        constraints()
        setupTableView()
        makeUI()
        makeButton()
    }

    func setupTableView() {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(ToDoTableViewCell.self, forCellReuseIdentifier: "cell")
    }
}

extension ViewController {
    
    func makeUI() {
            let navigationBarAppearance = UINavigationBarAppearance()
            navigationBarAppearance.configureWithOpaqueBackground()
            navigationController?.navigationBar.standardAppearance = navigationBarAppearance
            navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
            navigationController?.navigationBar.tintColor = .blue

            navigationItem.scrollEdgeAppearance = navigationBarAppearance
            navigationItem.standardAppearance = navigationBarAppearance
            navigationItem.compactAppearance = navigationBarAppearance

            navigationController?.setNeedsStatusBarAppearanceUpdate()
                    

            navigationController?.navigationBar.isTranslucent = false
            navigationController?.navigationBar.prefersLargeTitles = true
            //navigationController?.navigationBar.backgroundColor = .white
            title = "메모"
        }
    
    func makeButton() {
            let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(action))
            navigationItem.rightBarButtonItem = button
        }
    
    @objc func action() {
        
    }
    
    func subviews() {
        view.addSubview(tableView)
    }

    func constraints() {
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return UITableView.automaticDimension
        }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        return cell
    }
 


}

我希望 textview 的基本高度大于 50,然后动态更改 textview 高度。

isScrollEnabled = false, .sizetofit(), translaste~ =true ...

Swift UITableView TextView 堆栈 视图

评论

0赞 Community 11/9/2023
请修剪您的代码,以便更轻松地找到您的问题。请遵循这些准则,以创建最小的可重现示例

答:

1赞 nrurnru 11/9/2023 #1

考虑到内容,您可以获取具有给定宽度的 UITextView 的高度。

func textViewHeight(textView: UITextView) -> CGFloat {
    let width = textView.frame.width
    let fitSize = textView.sizeThatFits(.init(width: width, height: .greatestFiniteMagnitude))
    return fitSize.height
}

在 textViewDidChange(_:) 中调用它获取高度后,您可以更新 textView 的高度约束。

0赞 DonMag 11/12/2023 #2

几件事...

awakeFromNib()仅在从 Storyboard Prototype 或 XIB 加载的单元格上调用,因此我们可以将其删除。在 中设置文本视图的委托。init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)

确实想在文本视图上进行设置。这将使它自动调整其高度以适合文本。.isScrollEnabled = false

当约束设置正确时(正如您所做的那样),就没有必要实现 - 事实上,这会适得其反。heightForRowAt

当单元格的高度在显示时动态更改时,我们希望调用以告诉表视图重新布局其单元格。一种非常常见的方法是在单元中设置一个将“回调”控制器的单元。tableView.performBatchUpdates(nil)Closure

我们可以在单元类中实现,我们将调用该类为 Closure。textViewDidChange


因此,首先,在单元格类中,我们添加以下属性:

var basicClosure: (() -> ())?

并实施:

func textViewDidChange(_ textView: UITextView) {
    // inform the controller that the memo text changed
    basicClosure?()
}

然后在控制器中,我们设置闭包:cellForItemAt

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

    cell.basicClosure = {
        self.tableView.performBatchUpdates(nil)
    }
    
    return cell
}

现在,当您在单元格的文本视图中编辑文本时,其高度(以及它所在的单元格)将自动调整。


不过,我们需要做一些额外的事情,因为我们需要保存编辑后的文本......如果我们不这样做,那么当我们将单元格滚动到视图之外时,任何编辑都将丢失。

因此,让我们将 Closure 更改为:

// Closure so we can inform the controller when the text is edited
public var memoEdited: ((UITableViewCell, String) -> ())?

并在以下位置实现:textViewDidChange

func textViewDidChange(_ textView: UITextView) {
    // inform the controller that the memo text changed
    memoEdited?(self, textView.text ?? "")
}

然后我们将像这样设置该闭包:

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

    // assuming we have a data source with a ",memo" String property
    cell.memoString.text = myData[indexPath.row].memo
    
    cell.memoEdited = { [weak self] theCell, theText in
        guard let self = self,
              let idx = self.tableView.indexPath(for: theCell)
        else { return }
        // update the data with the edited text
        myData[idx.row].memo = theText
        // tell the table view to re-layout its cells
        self.tableView.performBatchUpdates(nil)
    }

    return cell
}

以下是您发布的代码,经过编辑以包含这些更改:

/// basic data structure
struct ToDoStruct {
    var date: Date = Date()
    var memo: String = ""
}

class ToDoTableViewCell: UITableViewCell, UITextViewDelegate {
    
    // Closure so we can inform the controller when the text is edited
    public var memoEdited: ((UITableViewCell, String) -> ())?

    lazy var backColorView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let memoString: UITextView = {
        let textView = UITextView()
        textView.font = .systemFont(ofSize: 17, weight: .regular)
        textView.layer.cornerRadius = 0
        textView.layer.borderWidth = 1
        textView.layer.borderColor = #colorLiteral(red: 0.2588235438, green: 0.7568627596, blue: 0.9686274529, alpha: 1)
        textView.backgroundColor = .clear
        // disable scrolling
        textView.isScrollEnabled = false
        return textView
    }()
    
    let dateString: UILabel = {
        let date = UILabel()
        date.font = .systemFont(ofSize: 14, weight: .light)
        date.text = "2021-11-12"
        return date
    }()
    
    let updateButton: UIButton = {
        let button = UIButton()
        button.setTitle("UPDATE", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 9, weight: .bold)
        button.layer.cornerRadius = 10
        button.backgroundColor = .gray
        button.setImage(UIImage(systemName: "pencil"), for: .normal)
        return button
    }()
    
    let totalStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.distribution  = .fill
        stack.alignment = .fill
        stack.spacing = 10
        return stack
    }()
    
    let subStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.distribution  = .fill
        stack.alignment = .fill
        stack.spacing = 0
        return stack
    }()

    // this is not called when using a cell that is
    //  NOT coming from a Storyboard Prototype or XIB
    //override func awakeFromNib() {
    //  super.awakeFromNib()
    //  // Initialization code
    //  memoString.delegate = self
    //}
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .default, reuseIdentifier: reuseIdentifier)
        setupStackView()
        setConstraints()
        
        memoString.delegate = self
    }
    
    func textViewDidChange(_ textView: UITextView) {
        // inform the controller that the memo text changed
        memoEdited?(self, textView.text ?? "")
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupStackView() {
        
        self.contentView.addSubview(backColorView)
        
        totalStackView.addArrangedSubview(memoString)
        totalStackView.addArrangedSubview(subStackView)
        
        subStackView.addArrangedSubview(dateString)
        subStackView.addArrangedSubview(updateButton)
        
        self.contentView.addSubview(totalStackView)
        
    }
    
    
    func setConstraints() {
        totalStackView.translatesAutoresizingMaskIntoConstraints = false
        subStackView.translatesAutoresizingMaskIntoConstraints = false
        updateButton.translatesAutoresizingMaskIntoConstraints = false
        memoString.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            
            totalStackView.leadingAnchor.constraint(equalTo: backColorView.leadingAnchor, constant: 10),
            totalStackView.trailingAnchor.constraint(equalTo: backColorView.trailingAnchor, constant: -10),
            totalStackView.topAnchor.constraint(equalTo: backColorView.topAnchor, constant: 10),
            totalStackView.bottomAnchor.constraint(equalTo: backColorView.bottomAnchor, constant: -10),
            
            
            subStackView.heightAnchor.constraint(equalToConstant: 30),
            updateButton.widthAnchor.constraint(equalToConstant: 80),
            
            backColorView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 25),
            backColorView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -25),
            backColorView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            backColorView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
            
            memoString.heightAnchor.constraint(greaterThanOrEqualToConstant: 50)
            
            
        ])
    }
}

class TestToDoViewController: UIViewController {
    
    var myData: [ToDoStruct] = []
    
    private let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // generate some sample data
        var d: Date = Date()
        for i in 0..<10 {
            myData.append(ToDoStruct(date: d, memo: "Memo item \(i)"))
            // add 1 day
            d = d.addingTimeInterval(60 * 60 * 24)
        }
        
        tableView.delegate = self
        tableView.dataSource = self
        subviews()
        constraints()
        setupTableView()
        makeUI()
        makeButton()
        
    }
    
    func setupTableView() {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(ToDoTableViewCell.self, forCellReuseIdentifier: "cell")
        
        // we probably want to dismiss the keyboard when scrolling
        tableView.keyboardDismissMode = .onDrag

    }
}

extension TestToDoViewController {
    
    func makeUI() {
        let navigationBarAppearance = UINavigationBarAppearance()
        navigationBarAppearance.configureWithOpaqueBackground()
        navigationController?.navigationBar.standardAppearance = navigationBarAppearance
        navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
        navigationController?.navigationBar.tintColor = .blue
        
        navigationItem.scrollEdgeAppearance = navigationBarAppearance
        navigationItem.standardAppearance = navigationBarAppearance
        navigationItem.compactAppearance = navigationBarAppearance
        
        navigationController?.setNeedsStatusBarAppearanceUpdate()
        
        
        navigationController?.navigationBar.isTranslucent = false
        navigationController?.navigationBar.prefersLargeTitles = true
        //navigationController?.navigationBar.backgroundColor = .white
        title = "메모"
    }
    
    func makeButton() {
        let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(action))
        navigationItem.rightBarButtonItem = button
    }
    
    @objc func action() {
        
    }
    
    func subviews() {
        view.addSubview(tableView)
    }
    
    func constraints() {
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension TestToDoViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    
    // do NOT implement heightForRowAt
    //func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    //  return UITableView.automaticDimension
    //}
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ToDoTableViewCell

        let df = DateFormatter()
        df.dateFormat = "YYYY-MM-dd"
        
        cell.dateString.text = df.string(from: myData[indexPath.row].date)

        cell.memoString.text = myData[indexPath.row].memo
        
        cell.memoEdited = { [weak self] theCell, theText in
            guard let self = self,
                  let idx = self.tableView.indexPath(for: theCell)
            else { return }
            // update the data with the edited text
            myData[idx.row].memo = theText
            // tell the table view to re-layout its cells
            self.tableView.performBatchUpdates(nil)
        }

        return cell
    }
    
}

您的单元格中有一个标有“更新”的按钮,但不清楚您想用它做什么。例如,如果您想使用它来将编辑后的“备忘录”保存到存储中,则需要添加另一个 Closure 来处理“更新”按钮点击。