Swift-内容视图的大小与UITableViewCell

时间:2018-08-23 10:10:00

标签: swift uitableview

我有一个带有TableView的iOS应用,该应用的所有UI均使用autolayoutSwift以编程方式完成。

直到最近,所有UI都运行良好,我必须在UIView内添加一个新组件(自定义UITableViewCell),当显示或隐藏新组件时,其单元格高度将发生变化。单元格的高度不正确,因此UITableViewCell内部的视图变得一团糟。

检查“调试视图层次结构”后,我发现UITableViewCell的高度不同于UITableViewCellContentView

组件何时显示:

  • 表格视图单元格具有正确的高度(更长的高度)
  • UITableViewCell的内容视图比预期的要短(如果隐藏了组件,则高度正确)

隐藏组件的时间:

  • 表格视图单元格具有正确的高度(更短的高度)
  • UITableViewCell的内容视图长于预期(如果显示组件,则高度正确)

我不太确定真正的问题是什么。当我切换要显示或不显示的组件时,我将执行以下操作:

// Update the constraints status
var componentIsShown: Bool = .....
xxxConstraints?.isActive = componentIsShown
yyyConstraints?.isActive = !componentIsShown

// Update UI
layoutIfNeeded()
view.setNeedsUpdateConstraints()
tableView.beginUpdates()
tableView.endUpdates()

在我看来,当我切换要显示或不显示的组件时,UITableViewCell会立即更新,但是内容视图使用以前的数据来更新高度。如果是这个问题,我该如何更新内容视图的高度?

如果这不是问题,是否有任何解决建议或进一步调查?

谢谢

===================

于2018-08-29更新:

已随附此问题的代码。 在MyBaseView(带有红色alpha bg的视图)中单击topMainContainerView,将切换是否显示hiddenView。

在ViewController.swift中:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
    return UITableViewAutomaticDimension
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    var cell: MyViewCell

    if let theCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MyViewCell
    {
        cell = theCell
    }
    else
    {
        cell = MyViewCell(reuseIdentifier: "cell")
    }

    cell.setViewCellDelegate(delegate: self)
    return cell
}

MyViewCell.swift

class MyViewCell: MyViewParentCell
{
    var customView: MyView

    required init?(coder aDecoder: NSCoder)
    {
        return nil
    }

    override init(reuseIdentifier: String?)
    {
        customView = MyView()

        super.init(reuseIdentifier: reuseIdentifier)
        selectionStyle = .none
    }

    override func initViews()
    {
        contentView.addSubview(customView)
    }

    override func initLayout()
    {
        customView.translatesAutoresizingMaskIntoConstraints = false
        customView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        customView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        customView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        customView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    }

    override func setViewCellDelegate(delegate: MyViewCellDelegate)
    {
        super.setViewCellDelegate(delegate: delegate)
        customView.delegate = delegate

        customView.innerDelegate = self
    }
}

MyViewParentCell.swift:

protocol MyViewCellDelegate
{
    func reloadTableView()
}

protocol MyViewCellInnerDelegate
{
    func viewCellLayoutIfNeeded()
}

class MyViewParentCell: UITableViewCell
{
    private var delegate: MyViewCellDelegate?
    var innerDelegate: MyViewCellInnerDelegate?

    required init?(coder aDecoder: NSCoder)
    {
        return nil
    }

    init(reuseIdentifier: String?)
    {
        super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier)

        selectionStyle = .none

        initViews()
        initLayout()
    }

    func initViews()
    {
    }

    func initLayout()
    {
    }

    override func layoutSubviews()
    {
    }

    func setViewCellDelegate(delegate: MyViewCellDelegate)
    {
        self.delegate = delegate
    }
}

extension MyViewParentCell: MyViewCellInnerDelegate
{
    func viewCellLayoutIfNeeded()
    {
        print("MyViewParentCell viewCellLayoutIfNeeded")
        setNeedsUpdateConstraints()
        layoutIfNeeded()
    }
}

MyView.swift

class MyView: MyParentView
{
    private var mainView: UIView

    // Variables
    var isViewShow = true

    // Constraint
    private var mainViewHeightConstraint: NSLayoutConstraint?
    private var hiddenViewHeightConstraint: NSLayoutConstraint?

    private var hiddenViewPosYHideViewConstraint: NSLayoutConstraint?
    private var hiddenViewPosYShowViewConstraint: NSLayoutConstraint?

    // Constant:
    let viewSize = UIScreen.main.bounds.width

    // Init
    override init()
    {
        mainView = UIView(frame: CGRect(x: 0, y: 0, width: viewSize, height: viewSize))

        super.init()
    }

    required init?(coder aDecoder: NSCoder)
    {
        return nil
    }

    override func initViews()
    {
        super.initViews()

        topMainContainerView.addSubview(mainView)
    }

    override func initLayout()
    {
        super.initLayout()

        //
        mainView.translatesAutoresizingMaskIntoConstraints = false
        mainView.topAnchor.constraint(equalTo: topMainContainerView.topAnchor).isActive = true
        mainView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor).isActive = true
        mainView.leadingAnchor.constraint(equalTo: topMainContainerView.leadingAnchor).isActive = true
        mainView.widthAnchor.constraint(equalToConstant: viewSize).isActive = true
        mainView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true
        mainViewHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: viewSize)
        mainViewHeightConstraint?.isActive = true
    }

    override func toggle()
    {
        isViewShow = !isViewShow
        print("toggle: isViewShow is now (\(isViewShow))")
        setViewHidden()
    }

    private func setViewHidden()
    {
        UIView.animate(withDuration: 0.5) {
            if self.isViewShow
            {
                self.hiddenViewBottomConstraint?.isActive = false
                self.hiddenViewTopConstraint?.isActive = true
            }
            else
            {
                self.hiddenViewTopConstraint?.isActive = false
                self.hiddenViewBottomConstraint?.isActive = true
            }

            self.layoutIfNeeded()
            self.needsUpdateConstraints()
            self.innerDelegate?.viewCellLayoutIfNeeded()
            self.delegate?.reloadTableView()
        }
    }
}

MyParentView.swift

class MyParentView: MyBaseView
{
    var delegate: MyViewCellDelegate?
    var innerDelegate: MyViewCellInnerDelegate?

    override init()
    {
        super.init()
    }

    required init?(coder aDecoder: NSCoder)
    {
        return nil
    }

    override func initViews()
    {
        super.initViews()
    }

    override func initLayout()
    {
        super.initLayout()        
        translatesAutoresizingMaskIntoConstraints = false
    }
}

MyBaseView.swift

class MyBaseView: UIView
{
    var topMainContainerView: UIView

    var hiddenView: UIView

    var bottomActionContainerView: UIView
    var bottomSeparator: UIView

    var hiddenViewTopConstraint: NSLayoutConstraint?
    var hiddenViewBottomConstraint: NSLayoutConstraint?

    // Layout constratint
    var descriptionWidthConstraint: NSLayoutConstraint?
    var moreMainTopAnchorConstraint: NSLayoutConstraint?
    var moreMainBottomAnchorConstraint: NSLayoutConstraint?
    var separatorTopAnchorToActionBarConstraint: NSLayoutConstraint?
    var separatorTopAnchorToPartialCommentConstraint: NSLayoutConstraint?

    // Constant
    let paddingX: CGFloat = 10
    let InnerPaddingY: CGFloat = 9

    init()
    {
        topMainContainerView = UIView()

        hiddenView = UIView()

        bottomActionContainerView = UIView()
        bottomSeparator = UIView()

        super.init(frame: .zero)

        initViews()
        initLayout()
    }

    required init?(coder aDecoder: NSCoder)
    {
        return nil
    }

    func initViews()
    {
        let borderColor = UIColor.gray

        backgroundColor = UIColor(red: 211/255.0, green: 211/255.0, blue: 1, alpha: 1)

        topMainContainerView.backgroundColor = UIColor.red.withAlphaComponent(0.7)
        let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
        topMainContainerView.addGestureRecognizer(gesture)

        // Hidden View
        hiddenView.backgroundColor = UIColor.yellow
        hiddenView.layer.cornerRadius = 50

        // Action
        bottomActionContainerView.backgroundColor = UIColor.blue
        bottomSeparator.backgroundColor = borderColor

        // Add hiddenView first, so it will hide behind main view
        addSubview(hiddenView)
        addSubview(topMainContainerView)
        addSubview(bottomActionContainerView)
        addSubview(bottomSeparator)
    }

    func initLayout()
    {
        // MARK: Main
        topMainContainerView.translatesAutoresizingMaskIntoConstraints = false
        topMainContainerView.topAnchor.constraint(equalTo: topAnchor, constant: 30).isActive = true
        topMainContainerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
        topMainContainerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
        topMainContainerView.heightAnchor.constraint(equalToConstant: 150).isActive = true

        // Hidden View
        hiddenView.translatesAutoresizingMaskIntoConstraints = false
        hiddenViewTopConstraint = hiddenView.topAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
        hiddenViewTopConstraint?.isActive = true
        hiddenView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
        hiddenView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
        hiddenViewBottomConstraint = hiddenView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
        hiddenViewBottomConstraint?.isActive = false
        hiddenView.heightAnchor.constraint(equalToConstant: 100).isActive = true

        // MARK: Bottom
        bottomActionContainerView.translatesAutoresizingMaskIntoConstraints = false
        bottomActionContainerView.topAnchor.constraint(equalTo: hiddenView.bottomAnchor, constant: InnerPaddingY).isActive = true
        bottomActionContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        bottomActionContainerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        bottomActionContainerView.heightAnchor.constraint(equalToConstant: 32).isActive = true

        // MARK: Separator
        bottomSeparator.translatesAutoresizingMaskIntoConstraints = false

        separatorTopAnchorToPartialCommentConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
        separatorTopAnchorToActionBarConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
        separatorTopAnchorToPartialCommentConstraint?.isActive = false
        separatorTopAnchorToActionBarConstraint?.isActive = true
        bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        bottomSeparator.heightAnchor.constraint(equalToConstant: 10).isActive = true
        bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

        self.hiddenViewBottomConstraint?.isActive = false
        self.hiddenViewTopConstraint?.isActive = true
    }

    @objc func toggle()
    {
    }
}

2 个答案:

答案 0 :(得分:0)

contentView的布局未更新。您应该尝试

cell.contentView.layoutIfNeeded()

尝试并分享结果。

答案 1 :(得分:0)

我终于发现我应该在MyViewParentCell.swift中调用super.layoutSubviews()或直接删除该函数来解决该问题。

override func layoutSubviews()
{
    super.layoutSubviews()
}