如何制作可扩展的 UITableView 标头?

Posted

技术标签:

【中文标题】如何制作可扩展的 UITableView 标头?【英文标题】:How do I make an expandable UITableView header? 【发布时间】:2016-02-28 01:51:39 【问题描述】:

我想首先显示一半的标题。当用户点击标题内的一个按钮时,标题的其余部分会向下滑动并显示出来。当用户再次点击按钮时,标题会滑回原来的大小(1/2 大小)。

但是,当我尝试扩展标题的高度时,它会覆盖 tableView 单元格,而不是向下推。

func prepareHeader()
    let headerHeight = CGFloat(51.0)
    headerView = UIView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: headerHeight))
    headerView.backgroundColor = UIColor.whiteColor()
    headerView.layer.addSublayer(bottomBorder)
    headerView.layer.masksToBounds = true


    let toggler = UIButton(type: .Custom)
    toggler.frame = CGRectMake(0, 0, 40, 40)
    toggler.backgroundColor = FlatGreen()
    toggler.addTarget(self, action: "toggleHeader:", forControlEvents: UIControlEvents.TouchUpInside)
    headerView.addSubview(toggler)

    self.tableView.tableHeaderView = headerView



func toggleHeader(sender: UIButton)
    print("Pushed")
    var newFrame = self.headerView.frame
    newFrame.size.height = CGFloat(100)
    self.headerView.frame = newFrame

【问题讨论】:

【参考方案1】:

我们想要什么

可以动态打开和关闭的表格视图标题。

解决方案 1:没有接口构建器


class ViewController: UIViewController 

    var headerView = UIView()
    var tableView  = UITableView()
    var isHeaderOpen = false
    let HEADER_CLOSED_HEIGHT: CGFloat = 100
    let HEADER_OPEN_HEIGHT: CGFloat = 200

    var headerClosedHeightConstraint: NSLayoutConstraint!
    var headerOpenHeightConstraint: NSLayoutConstraint!

    override func viewDidLoad() 
        super.viewDidLoad()

        // Add the table view to the screen.
        view.addSubview(tableView)

        // Make the table view fill the screen.
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[tableView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["tableView": tableView]))
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[tableView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["tableView": tableView]))

        // Add the header view to the table view.
        tableView.tableHeaderView = headerView
        headerView.backgroundColor = UIColor.redColor()
        headerView.translatesAutoresizingMaskIntoConstraints = false

        // Add a button to the header.
        let button = UIButton(type: .Custom)
        button.setTitle("Toggle", forState: .Normal)
        headerView.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        headerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[button]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
        headerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[button]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
        button.addTarget(self, action: "toggleHeaderAction:", forControlEvents: .TouchUpInside)

        // Make the header full width.
        view.addConstraint(NSLayoutConstraint(item: headerView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0))

        // Create the constraints for the two different header heights.
        headerClosedHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: HEADER_CLOSED_HEIGHT)

        // Create the height constraint for the header's other height for later use.
        headerOpenHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: HEADER_OPEN_HEIGHT)

        closeHeader()   // Close header by default.
    

    func openHeader() 
        headerView.removeConstraint(headerClosedHeightConstraint)
        headerView.addConstraint(headerOpenHeightConstraint)
        updateHeaderSize()
        isHeaderOpen = true
    

    func closeHeader() 
        headerView.removeConstraint(headerOpenHeightConstraint)
        headerView.addConstraint(headerClosedHeightConstraint)
        updateHeaderSize()
        isHeaderOpen = false
    

    func updateHeaderSize() 
        // Calculate the header's new size based on its new constraints and set its frame accordingly.
        let size = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
        var frame = headerView.frame
        frame.size.height = size.height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        self.headerView.layoutIfNeeded()
    

    func toggleHeader() 
        if isHeaderOpen 
            closeHeader()
         else 
            openHeader()
        
    

    func toggleHeaderAction(sender: AnyObject) 
        toggleHeader()
    

解决方案 2:使用 INTERFACE BUILDER


要使UITableView 标头动态更改高度,请尝试以下操作。

在您的故事板中,首先将UITableView 添加到您的视图控制器。然后添加一个UIView 作为UITableView 的第一个孩子(接口生成器自动将其解释为UITableView 的标题视图)。

将插座连接到表格和标题,以便我们稍后在代码中访问它们。

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var headerView: UIView!

接下来,我们在代码中创建两个约束(打开和关闭),稍后我们将添加/删除它们以切换标题的高度。

var headerOpenHeightConstraint: NSLayoutConstraint!
var headerClosedHeightConstraint: NSLayoutConstraint!

我们还要创建一个标志来跟踪标头的打开/关闭状态。另外,我们指定打开的标题高度。

var isHeaderOpen = false
let HEADER_OPEN_HEIGHT: CGFloat = 200

Interface Builder 会自动将我们创建的 header 的尺寸转换为 Auto Layout 约束。由于我们要自己更改标题的高度,我们需要添加自己的替换约束并删除这些自动约束(NSAutoresizingMaskLayoutConstraints)。 在viewDidLoad()中,添加如下内容(默认header高度为闭合高度,HEADER_OPEN_HEIGHT为openheader高度)。

override func viewDidLoad() 
    super.viewDidLoad()

    // The header needs a new width constraint since it will no longer have the automatically generated width.
    view.addConstraint(NSLayoutConstraint(item: headerView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0))

    // Add the height constraint for the default state (closed header).
    headerClosedHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: headerView.bounds.height)
    headerView.addConstraint(headerClosedHeightConstraint)

    // Make the constraint we'll use later to open the header.
    headerOpenHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: HEADER_OPEN_HEIGHT)

    // Finally we disable this so that we don't get Interface Builder's automatically generated constraints
    // messing with our constraints.
    headerView.translatesAutoresizingMaskIntoConstraints = false

最后,在 Interface Builder 中,添加一个按钮并将其连接到视图控制器中的操作。

@IBAction func toggleHeaderAction(sender: AnyObject) 

    // Add/remove the appropriate constraints to toggle the header.
    if isHeaderOpen 
        headerView.removeConstraint(headerOpenHeightConstraint)
        headerView.addConstraint(headerClosedHeightConstraint)
     else 
        headerView.removeConstraint(headerClosedHeightConstraint)
        headerView.addConstraint(headerOpenHeightConstraint)
    

    // Calculate the header's new size based on its new constraints.
    let size = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

    // Give the header its new height.
    var frame = headerView.frame
    frame.size.height = size.height
    headerView.frame = frame
    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()
    tableView.tableHeaderView = headerView

    isHeaderOpen = !isHeaderOpen

【讨论】:

谢谢@paulvs。我实际上并没有为此使用 Interface Builder。有没有办法在代码中做到这一点?我在代码中尝试了你的方法,它关闭了,但是当我切换它时它没有打开。 @TIMEX,我用完全不使用 Interface Builder 的替代解决方案更新了我的答案。【参考方案2】:

有几个想法可以尝试

设置后立即调用self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition .Top:, animated: true),其中 indexPath 是之前位于顶部的单元格?您可以尝试是否设置动画 根据您的描述,我无法判断您是否对新框架的内容大小偏移有问题(也就是单元格实际上在下方,您无法向上滚动到它们)。如果是这样,再次设置self.tableView.headerView 会改善这种情况吗?

【讨论】:

以上是关于如何制作可扩展的 UITableView 标头?的主要内容,如果未能解决你的问题,请参考以下文章

如何制作可滑动的 UItableview 标题以显示像单元格一样的删除按钮

UITableview 标头中的 UITapGesture

旋转后 UITableView 标头中 UISearchBar 的布局混乱

如何以编程方式截取可扩展的uitableview的屏幕截图

如何创建高度由其标签高度确定的 UITableView 标头?

如何手动制作macOS High Sierra可启动安装U盘