如何让tableFooterView始终位于UITableView的底部

2023-11-29

我有一个UITableView具有可变数量的部分。每个部分都有不同数量的单元格,并且每个部分都有页眉和页脚。我的UITableView还有一个tableFooterView我想始终将其保留在屏幕底部,除非表格太大而无法在屏幕上显示,然后tableFooterView应显示在最后一部分下方。我想要完成的任务如下所示:

我想要的示例,场景 1

我想要的示例,场景 2

然而,目前tableFooterView始终位于最后一部分的正下方,因此当只有两个部分时,它看起来像这样:

我目前拥有的示例

我正在寻找一种方法,使其在每种可能的情况下始终保持在底部。我一直在四处寻找,因为苹果不支持自动布局tableFooterView,我还没有找到解决办法。类似情况替换tableFooterView with a sectionFooter在最后一部分,但我不能这样做,因为我已经这样做了sectionFooters.

有没有人可以帮助我或指出我正确的方向?有几点需要考虑:

  • 它必须是一个tableFooterView;
  • 用户可以将部分添加到UITableView和行到各部分,因此 tableFooterView 应更新其位置

我如何设置tableFooterView眼下:

class CustomView: UITableViewDelegate, UITableViewDataSource {

    var myTableFooter: UIView = {

        let myTableFooter = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
        myTableFooter.backgroundColor = .red
        myTableFooter.isUserInteractionEnabled = true
        return myTableFooter

    }()

    override init(frame: CGRect) {

        super.init(frame: frame)
        setupViews()

        MyTableView.tableFooterView = myTableFooter

    }

}

EDIT:尝试过scrollViewDidScroll按照建议的方法,但没有成功:

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if(scrollView == myTableView) {

        let neededHeight = myTableView.frame.height - 50 - view.safeAreaInsets.bottom
        let currentHeight = myTableView.contentSize.height - 50

        let heightDifference = neededHeight - currentHeight

        if(heightDifference > 0) {

            myTableView.tableFooterView?.transform = CGAffineTransform(translationX: 0, y: heightDifference)

        }

    }

}

一种方法是:

  • 使用扩展来定义“自调整大小的非滚动”表视图
  • 嵌入表视图和普通视图UIView对于“容器”视图中的“页脚”视图
  • 将容器视图嵌入到滚动视图中,高度与滚动视图相同,但优先级较低
  • 将页脚视图限制在容器视图的底部,并且>=到表格视图的底部

所以,tableView的“自动高度”+footer view的高度决定了container view的高度,从而决定了.contentSize的滚动视图。页脚视图将“粘”到容器视图的底部。当滚动视图有足够的内容时,它会“下推”页脚视图。

Example:

enter image description here

enter image description here

这是创建它的代码。一切都是通过代码完成的...不需要 IBOutlets,所以只需创建一个新的视图控制器并将其类分配给PennyWiseViewController:

//
//  PennyWiseViewController.swift
//
//  Created by Don Mag on 5/14/19.
//

import UIKit

final class ContentSizedTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }

}

class MyOneLabelCell: UITableViewCell {

    // very simple one-label tableView cell

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.numberOfLines = 0
        return v
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        contentView.addSubview(theLabel)

        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
            theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
            theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
            theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
            ])

    }

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

}

class PennyWiseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let theContainerView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let theScrollView: UIScrollView = {
        let v = UIScrollView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let theTableView: ContentSizedTableView = {
        let v = ContentSizedTableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.isScrollEnabled = false
        return v
    }()

    let theFooterView: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .red
        v.textColor = .white
        v.text = "The Footer View"
        v.textAlignment = .center
        return v
    }()

    // start with 3 sections
    // selecting the row in the first section allows adding sections
    // selecting the row in the second section allows deleting sections
    var numSections = 3

    let reuseID = "MyOneLabelCell"

    override func viewDidLoad() {
        super.viewDidLoad()

        theTableView.dataSource = self
        theTableView.delegate = self

        theTableView.register(MyOneLabelCell.self, forCellReuseIdentifier: reuseID)

        // add the views
        view.addSubview(theScrollView)
        theScrollView.addSubview(theContainerView)
        theContainerView.addSubview(theTableView)
        theContainerView.addSubview(theFooterView)

        // this will allow the container height to be at least the height of the scroll view
        // when enough content is added to the container, it will grow
        let containerHeightConstraint = theContainerView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, multiplier: 1.0)
        containerHeightConstraint.priority = .defaultLow

        NSLayoutConstraint.activate([

            // constrain scrollView to all 4 sides (safe-area)
            theScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            theScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            theScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            theScrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),

            // constrain containerView to all 4 sides of scrollView
            theContainerView.topAnchor.constraint(equalTo: theScrollView.topAnchor),
            theContainerView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor),
            theContainerView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor),
            theContainerView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor),

            theContainerView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor),

            // constrain tableView to top/leading/trailing of constainerView
            theTableView.topAnchor.constraint(equalTo: theContainerView.topAnchor),
            theTableView.leadingAnchor.constraint(equalTo: theContainerView.leadingAnchor),
            theTableView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor),

            // constrain footerView >= 20 from bottom of tableView
            theFooterView.topAnchor.constraint(greaterThanOrEqualTo: theTableView.bottomAnchor, constant: 20.0),

            theFooterView.leadingAnchor.constraint(equalTo: theContainerView.leadingAnchor, constant: 0.0),
            theFooterView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor, constant: 0.0),
            theFooterView.bottomAnchor.constraint(equalTo: theContainerView.bottomAnchor, constant: 0.0),

            theFooterView.heightAnchor.constraint(equalToConstant: 150.0),

            containerHeightConstraint,

            ])

    }


    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return numSections
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return section < 2 ? 1 : 2
    }

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

        switch indexPath.section {
        case 0:
            cell.theLabel.text = "Add a section"
        case 1:
            cell.theLabel.text = "Delete a section"
        default:
            cell.theLabel.text = "\(indexPath)"
        }

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        switch indexPath.section {
        case 0:
            numSections += 1
            tableView.reloadData()
        case 1:
            if numSections > 2 {
                numSections -= 1
                tableView.reloadData()
            }
        default:
            print("\(indexPath) was selected")
        }

    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Section \(section) Header"
    }

    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "Section \(section) Footer"
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何让tableFooterView始终位于UITableView的底部 的相关文章

随机推荐

  • Django - 使用 PostgreSQL 和 Elasticsearch 进行全文搜索

    我有一个Django and Django REST Framework供电的 RESTful API 与PostgreSQLDB后端 支持对特定模型进行过滤 现在我想添加全文搜索功能 是否可以使用Elasticsearch进行全文搜索 然
  • 通过发送 SOAP 请求来实现 Air API

    我有一个 php 网站 这里我需要实现机票搜索和预订功能 为了做到这一点 我使用了 ARZOO 网站的付费 API 我从 ARZOO 获取了所有文档 我已阅读整个文档 医生说 Accessing this service requires
  • MPAndroidChart - 如何最好地将 X 轴值设置为字符串/日期?

    我对此有点迷失MPAndroid图表库 我开始了初学者的例子here 它建议使用 getValueX 和 getValueY 方法创建一个对象数组 列表 然后将其添加为条目 如下所示 List
  • 如何编写“sed”脚本,用另一个文件的内容替换两个标记之间的文本

    我正在尝试写一个sed用另一个文件的内容替换两个标记之间的文本的脚本 假设我有以下带有标记的文件 and index html p Old content p 我想用该文件的内容替换随附的文本 snippet html p New cont
  • 理解@interface声明中“(Private)”的这种用法

    我见过一些这样写的代码 interface AViewController Private 我想知道是否是这样 Private 提交到 App Store 时意味着什么 一般而言 这意味着什么 这是一个名为 私人 的类别 看看类别和扩展Ob
  • TimeSlider 插件和传单 - 标记未按顺序出现

    更新了一个JSFIDDLE 链接 我正在使用 LeafletJS 构建带有时间轴滑块的网络地图 我正在使用LeafletSlider插件显示一组基于名为的 GEOJSON 属性的标记DATE START 这是我的数据对象的示例 var ca
  • PDF 发送意图上的 Android SecurityException

    我在执行期间遇到以下异常ACTION SEND数据类型的意图application pdf java lang SecurityException Permission Denial starting Intent act android
  • ios - 在 WkWebView 中禁用 Youtube 自动播放

    我在用着WKWebView打开pages in Youtube 问题是 打开后他们开始播放视频并进入全屏 这是不想要的行为 视频未嵌入 它是带有描述 评论等的整个页面 有办法阻止他们玩吗 有用 请阅读评论 import UIKit impo
  • 您是否必须在 Redis 脚本中提前声明您的密钥?

    我的计划是将一些现有的 Redis 键存储在哈希中 稍后从 Redis Lua 脚本中获取并执行操作 我读到 最佳实践是在调用时提供脚本中使用的所有键EVAL 我的问题是 运行运行时没有提供任何密钥的脚本是否安全EVAL 但对从以下位置获取
  • `DS.attr()` 中的嵌套对象不受 `DS.rollbackAttributes()` 影响

    我有一个模型User如下 import DS from ember data const attr Model DS export default Model extend name attr string properties attr
  • templateUrl 更改时 AngularJS Modal 不显示

    到目前为止 我所拥有的是 Angular UI 示例 控制器 var ModalDemoCtrl function scope modal scope open function var modalInstance modal open t
  • 从托管代码中挂钩 LoadLibrary 调用

    我们希望挂钩对 LoadLibrary 的调用 以便下载未找到的程序集 我们有一个 ResolveAssembly 处理程序来处理托管程序集 但我们还需要处理非托管程序集 我们尝试通过 Microsoft Windows 的编程应用程序 中
  • 动态改变JTree中特定节点的图标

    我已经看过很多在树实例化期间更改节点图标的示例 但我想要一种稍后动态更改单个节点图标的方法 因此 在我的主代码中 我将自定义渲染器添加到我的树中 Icon I want to set nodes to later ImageIcon che
  • 在 Groovy 中执行 Unix cat 命令?

    Hallo 我想从 Groovy 程序执行类似 cat path to file1 path to file2 gt path to file3 的内容 我尝试了 cat path to file1 path to file2 gt pat
  • Android - 访问在线数据库SQlite

    我需要从 Android 应用程序打开 读取项目并将其插入到在线 SQLite 数据库中 我知道网址 用户名和密码 在 JavaSE 中我会执行以下操作 Class forName com mysql jdbc Driver Connect
  • 再次使用java进行字符串比较

    新手问题 但我有这个代码 import java util import java io public class Welcome1 main method begins execution of Java application publ
  • pthread - 暂停/取消暂停所有线程

    我正在尝试在我的应用程序中编写暂停 取消暂停所有线程 该线程由 SIGUSR1 暂停 和 SIGUSR2 取消暂停 激活 我想用pthread cond wait 在所有线程中 当收到信号时 使用pthread cond broadcast
  • 如何使用android创建dll

    我是 Android 应用程序开发新手 我想开发一个dll使用安卓 是否可以开发并集成到android应用程序 请告诉我解决方案 如果可以的话请将解决方案一一告诉我 至于我 我曾经为自己写过一篇关于 NDK 的笔记 这里是 Required
  • MySQL 中加密数据的搜索过滤器

    查询说明 假设我有一个数据库表 它以加密形式存储所有用户的数据 我有一个功能 管理员可以搜索用户数据 现在的问题是 管理员将在文本框中输入普通文本 我必须根据管理员的输入过滤用户列表 在每次文本更改时 因此 与此同时 我拥有大量加密形式的数
  • 如何让tableFooterView始终位于UITableView的底部

    我有一个UITableView具有可变数量的部分 每个部分都有不同数量的单元格 并且每个部分都有页眉和页脚 我的UITableView还有一个tableFooterView我想始终将其保留在屏幕底部 除非表格太大而无法在屏幕上显示 然后ta