【问题标题】:Expanding and collapsing UITableViewCells with DatePicker使用 DatePicker 展开和折叠 UITableViewCells
【发布时间】:2015-06-23 01:56:50
【问题描述】:

我正在构建一个允许用户从 UITableView 中选择日期的应用程序。 tableView 是静态的和分组的。我已经查看了很多问题,包括this one,试图弄清楚如何实现这一点——但似乎没有什么是最佳的。 Apple 的日历应用程序具有非常流畅和漂亮的动画,我经历过的所有示例都无法重新创建。

这是我想要的结果:

有人可以指点我一个教程或解释我如何以最简洁直接的方式完成如此流畅的动画,就像我们在日历应用中看到的那样?

非常感谢!

埃里克

【问题讨论】:

  • 你的tableview是静态的还是原型的?
  • @thorb65 这是一个静态分组的tableView
  • 我用我的解决方案创建了一个示例存储库。它应该在任何 TableView 中工作。 (静态/动态/分组)也许有人会觉得它有帮助。我目前没有时间创建更详细的答案。这是链接:github.com/hettiger/ios-expandable-table-view-cell

标签: ios objective-c uitableview datepicker tableviewcell


【解决方案1】:

我假设您使用的是故事板,示例是 UIPickerView: 在包含要填充的文本字段的单元格正下方创建一个 tableviewcell,并在检查器中将单元格行高设置为 216.0,并向该单元格添加 UIPickerView。

接下来通过 Outlet 将 UIPickerView 连接到您的视图控制器,并将以下属性添加到您的 ViewController.h:

@property (weak, nonatomic) IBOutlet UIPickerView *statusPicker;
@property BOOL statusPickerVisible;

在你的 ViewController.m 中做 viewWillAppear

self.statusPickerVisible = NO;
self.statusPicker.hidden = YES;
self.statusPicker.translatesAutoresizingMaskIntoConstraints = NO;

添加两个方法:

- (void)showStatusPickerCell {
    self.statusPickerVisible = YES;
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    self.statusPicker.alpha = 0.0f;
    [UIView animateWithDuration:0.25 
                 animations:^{
                     self.statusPicker.alpha = 1.0f;
                 } completion:^(BOOL finished){
                     self.statusPicker.hidden = NO;
                 }];];
}

- (void)hideStatusPickerCell {    
    self.statusPickerVisible = NO;
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    [UIView animateWithDuration:0.25
                 animations:^{
                     self.statusPicker.alpha = 0.0f;
                 }
                 completion:^(BOOL finished){
                     self.statusPicker.hidden = YES;
                 }];
}

heightForRowAtIndexPath

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat height = self.tableView.rowHeight;
    if (indexPath.row == 1){
        height = self.statusPickerVisible ? 216.0f : 0.0f;
    }
    return height;
}

didSelectRowAtIndexPath

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        if (self.statusPickerVisible){
            [self hideStatusPickerCell];
        } else {
            [self showStatusPickerCell];
        }
    }
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}

【讨论】:

  • 非常好的答案。现在这样做了,单元格扩展的动画看起来不错,但由于某种原因,pickerView 不可见
  • 看起来即使在动画的完成块(NSLog)中,alpha值也保持在0。看起来像 [cell.contentView addSubview:self.statusPicker];不能正常工作
  • 删除 addSubview 和 removeFromSubview 方法使其工作
  • 不错。我将它设置为与多个部分一起使用,并且它可以完美地与苹果在其应用程序中的用户交互相同。动画非常好,比其他例子流畅很多
  • There is one issue here: when picker is visible and you quickly tap twice, you will have expanded empty picker cell.为避免此问题,您必须在 showStatusPickerCell 中添加完成并在那里设置 self.statusPicker.hidden = NO;
【解决方案2】:

上面的 2 个答案使我能够解决这个问题。他们值得称赞,我正在为自己添加这个提醒 - 摘要格式。

这是我以上答案的版本。

1.如上所述 - 将选择器添加到要显示/隐藏的单元格中。

2.在界面构建器中为选择器添加约束 - 中心 X / 中心 Y / 等高 / 等宽到单元格的内容视图

3. 将选择器连接到您的 VC

@IBOutlet weak var dobDatePicker: UIDatePicker!

您不妨控制拖动并添加一个方法来记录日期更改

@IBAction func dateChanged(sender: UIDatePicker) { 
    // updates ur label in the cell above
    dobLabel.text = "\(dobDatePicker.date)"
}

4. 在 vi​​ewDidLoad 中

dobDatePicker.date = NSDate()
dobLabel.text = "\(dobDatePicker.date)" // my label in cell above
dobDatePicker.hidden = true

5. 设置单元格高度,在我的示例中,我要扩展的单元格是第 0 行第 3 行...将其设置为要扩展/隐藏的单元格。如果您有许多不同高度的单元格,则可以这样做。

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    if indexPath.section == 0 && indexPath.row == 3 {
        let height:CGFloat = dobDatePicker.hidden ? 0.0 : 216.0
        return height
    }

    return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}

6. 选择上面的单元格以展开下面的单元格,再次将此设置为您将点击以显示下面的单元格的单元格。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

let dobIndexPath = NSIndexPath(forRow: 2, inSection: 0)
if dobIndexPath == indexPath {

    dobDatePicker.hidden = !dobDatePicker.hidden

    UIView.animateWithDuration(0.3, animations: { () -> Void in
        self.tableView.beginUpdates()
        // apple bug fix - some TV lines hide after animation
        self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
        self.tableView.endUpdates()
    })
}
}

【讨论】:

  • 这太棒了,但是如果我们想让同一个单元格可扩展,那么我们该如何进行?
  • 我的折叠动画不流畅。关闭时,我可以看到第一个文本“Mon, Feb 1 ...”首先被截断?
  • 您好,此解决方案在 iOS 14 上无法正常工作。您知道如何解决吗?
  • @Gianni 不抱歉有一段时间没看这个了 - 我现在使用 SwiftUI
【解决方案3】:

我在 Swift 中实现了 @thorb65 的答案,它就像一个魅力。即使我设置了两个日期选择器(例如,日历中的“开始”和“结束”),并将它们设置为在展开另一个时自动折叠打开的一个(即,“一次最多打开一个”策略,就像日历一样),(并发)动画仍然很流畅。

不过,我一直在努力解决的一件事是找到正确的自动布局约束。以下给了我与 Calendar.app 相同的行为:

  1. 自下而上折叠(日期选择器内容不移动)

从 UIDatePicker 到自身的约束:

  • 身高

UIDatePicker 对 UITableViewCell 的内容视图的约束:

  • 领先空间到容器边距
  • 容器边距的尾随空间
  • 顶部空间到容器边距

Resulting animation

“Bottom Space to Container Margin”被明确省略,以在整个动画中强制执行固定高度(这重新创建了 Calendar.app 的行为,表格视图单元格“滑动打开”以显示不变的固定高度日期选择器下)。

  1. 自下而上折叠(日期选择器均匀移动)

从 UIDatePicker 到自身的约束:

  • 身高

UIDatePicker 对 UITableViewCell 的内容视图的约束:

  • 领先空间到容器边距
  • 容器边距的尾随空间
  • 在外部容器中垂直居中

Resulting animation

注意约束在折叠/展开动画中的不同。

编辑:这是快速代码

属性:

// "Start Date" (first date picker)
@IBOutlet weak var startDateLabel: UILabel!
@IBOutlet weak var startDatePicker: UIDatePicker!
var startDatePickerVisible:Bool?

// "End Date" (second date picker)
@IBOutlet weak var endDateLabel: UILabel!
@IBOutlet weak var endDatePicker: UIDatePicker!
var endDatePickerVisible:Bool?

private var startDate:NSDate
private var endDate:NSDate
// Backup date labels' initial text color, to restore on collapse 
// (we change it to control tint while expanded, like calendar.app)  
private var dateLabelInitialTextColor:UIColor!

UIViewController 方法:

override func viewDidLoad()
{
    super.viewDidLoad()

    // Set pickers to their initial values (e.g., "now" and "now + 1hr" )
    startDatePicker.date = startDate
    startDateLabel.text = formatDate(startDate)

    endDatePicker.date = endDate
    endDateLabel.text = formatDate(endDate)

    // Backup (unselected) date label color    
    dateLabelInitialTextColor = startDateLabel.textColor
}

override func viewWillAppear(animated: Bool)
{
    super.viewWillAppear(animated)

    startDatePickerVisible = false
    startDatePicker.hidden = true

    endDatePickerVisible = false
    endDatePicker.hidden = true
}

UITableViewDelegate 方法:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
    var height:CGFloat = 44 // Default

    if indexPath.row == 3 {
        // START DATE PICKER ROW
        if let startDatePickerVisible = startDatePickerVisible {
            height = startDatePickerVisible ? 216 : 0
        }
    }
    else if indexPath.row == 5 {
        // END DATE PICKER ROW
        if let endDatePickerVisible = endDatePickerVisible {
            height = endDatePickerVisible ? 216 : 0
        }
    }

    return height
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
    switch indexPath.row {

    case 2:
        // [ A ] START DATE

        // Collapse the other date picker (if expanded):
        if endDatePickerVisible! {
            hideDatePickerCell(containingDatePicker: endDatePicker)
        }

        // Expand:
        if startDatePickerVisible! {
            hideDatePickerCell(containingDatePicker: startDatePicker)
        }
        else{
            showDatePickerCell(containingDatePicker: startDatePicker)
        }

    case 4:
        // [ B ] END DATE

        // Collapse the other date picker (if expanded):
        if startDatePickerVisible!{
            hideDatePickerCell(containingDatePicker: startDatePicker)
        }

        // Expand:
        if endDatePickerVisible! {
            hideDatePickerCell(containingDatePicker: endDatePicker)
        }
        else{
            showDatePickerCell(containingDatePicker: endDatePicker)
        }

    default:
        break
    }

    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

日期选择器控制操作:

@IBAction func dateChanged(sender: AnyObject)
{
    guard let picker = sender as? UIDatePicker else {
        return
    }

    let dateString = formatDate(picker.date)

    if picker == startDatePicker {
        startDateLabel.text = dateString
    }
    else if picker == endDatePicker {
        endDateLabel.text = dateString
    }
}

辅助方法:(动画、日期格式)

@IBAction func dateChanged(sender: AnyObject)
{
    guard let picker = sender as? UIDatePicker else {
        return
    }

    let dateString = formatDate(picker.date)

    if picker == startDatePicker {
        startDateLabel.text = dateString
    }
    else if picker == endDatePicker {
        endDateLabel.text = dateString
    }
}

func showDatePickerCell(containingDatePicker picker:UIDatePicker)
{
    if picker == startDatePicker {

        startDatePickerVisible = true

        startDateLabel.textColor = myAppControlTintColor
    }
    else if picker == endDatePicker {

        endDatePickerVisible = true

        endDateLabel.textColor = myAppControlTintColor
    }

    tableView.beginUpdates()
    tableView.endUpdates()

    picker.hidden = false
    picker.alpha = 0.0

    UIView.animateWithDuration(0.25) { () -> Void in

        picker.alpha = 1.0
    }
}

func hideDatePickerCell(containingDatePicker picker:UIDatePicker)
{
    if picker == startDatePicker {

        startDatePickerVisible = false

        startDateLabel.textColor = dateLabelInitialTextColor
    }
    else if picker == endDatePicker {

        endDatePickerVisible = false

        endDateLabel.textColor = dateLabelInitialTextColor
    }

    tableView.beginUpdates()
    tableView.endUpdates()

    UIView.animateWithDuration(0.25,
        animations: { () -> Void in

            picker.alpha = 0.0
        },
        completion:{ (finished) -> Void in

            picker.hidden = true
        }
    )
}

【讨论】:

  • 你应该在这里添加快速代码......或者我可以将它添加到我的答案中并提及你:-)
  • 也许我只是没有看到它,但似乎缺少 formatDate 方法?!除此之外,这是一个很好的答案,它提供了一个非常漂亮的解决方案!感谢分享
  • 是的,它只是一个非常小的方法,它包装了一个 NSDateFormatter 并返回格式化的字符串。
  • 这很好用。我在一个有五个单元格的静态表中使用它。数据单元格位于底部,选择单元格位于其正上方。当设备处于横向模式时,我在此功能上遇到了一个小问题。当我展开日期选择器单元格时,表格不会向上滚动,并且日期选择器不在屏幕底部。如何在显示日期选择器时强制表格视图向上滚动?
  • @NicolasMiari 谢谢!当使用日期选择器折叠单元格时,这些约束对于动画的外观非常重要。有了这些提供的约束,一切都会在崩溃时向上移动。我发现如果你设置这些约束集,你会得到一个更统一的折叠动画: - 在单元格内容视图中垂直居中日期选择器 - 将前导和尾随约束设置为容器边距 - 日期选择器的高度 折叠日期选择器时也是这样向上移动(而不仅仅是单元格向上移动)。
【解决方案4】:

我正在分享我的答案:

我在没有故事板的情况下做所有事情

斯威夫特 3

1.1 添加日期选择器

var travelDatePicker: UIDatePicker = {
            let datePicker = UIDatePicker()
            datePicker.timeZone = NSTimeZone.local
            datePicker.backgroundColor = UIColor.white
            datePicker.layer.cornerRadius = 5.0
            datePicker.datePickerMode = .date
            datePicker.addTarget(self, action: #selector(TableViewController.datePickerValueChanged(_:)), for: .valueChanged)
            return datePicker
        }()

1.2及其方法

func datePickerValueChanged(_ sender: UIDatePicker){

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let dateString = dateFormatter.string(from: travelDatePicker.date)
        self.shareCell.textLabel?.text = "\(dateString)"

        print("changed")
        print("Selected value \(dateString)")
    }

2。然后在 loadView 中以格式显示上面单元格中的日期

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let dateString = dateFormatter.string(from: travelDatePicker.date)
        self.shareCell.textLabel?.text = "\(dateString)"
        travelDatePicker.isHidden = true

3。将 datePicker 添加到单元格中

self.datePickerCell.backgroundColor = UIColor.red
        self.datePickerCell.addSubview(self.travelDatePicker)
        self.travelDatePicker.frame = CGRect(x: 0, y: 0, width: 500, height: 216)
        self.datePickerCell.accessoryType = UITableViewCellAccessoryType.none

4。设置单元格的高度

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            if indexPath.section == 1 && indexPath.row == 1{
                let height: CGFloat = travelDatePicker.isHidden ? 0.0 : 216.0
                return height
            }
            return 44.0
        }
  1. 最后在 didSelectAt 中设置 if 语句

if(indexPath.section == 1 && indexPath.row == 0) {

    travelDatePicker.isHidden = !travelDatePicker.isHidden

    UIView.animate(withDuration: 0.3, animations: { () -> Void in
        self.tableView.beginUpdates()
        // apple bug fix - some TV lines hide after animation
        self.tableView.deselectRow(at: indexPath, animated: true)
        self.tableView.endUpdates()
    })
}

这里有完整的代码和其他元素,只是让你感觉可以工作的应用程序

import Foundation
import UIKit

class TableViewController: UITableViewController {

    var firstNameCell: UITableViewCell = UITableViewCell()
    var lastNameCell: UITableViewCell = UITableViewCell()
    var shareCell: UITableViewCell = UITableViewCell()
    var datePickerCell: UITableViewCell = UITableViewCell()
    var cityToCell: UITableViewCell = UITableViewCell()
    var cityFromCell: UITableViewCell = UITableViewCell()

    var firstNameText: UITextField = UITextField()
    var lastNameText: UITextField = UITextField()

    var travelDatePicker: UIDatePicker = {
        let datePicker = UIDatePicker()
        datePicker.timeZone = NSTimeZone.local
        datePicker.backgroundColor = UIColor.white
        datePicker.layer.cornerRadius = 5.0
        datePicker.datePickerMode = .date
        datePicker.addTarget(self, action: #selector(TableViewController.datePickerValueChanged(_:)), for: .valueChanged)
        return datePicker
    }()

    override func loadView() {
        super.loadView()

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let dateString = dateFormatter.string(from: travelDatePicker.date)
        self.shareCell.textLabel?.text = "\(dateString)"
        travelDatePicker.isHidden = true

        // set the title
        self.title = "User Options"

        // construct first name cell, section 0, row 0
        self.firstNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.firstNameText = UITextField(frame: self.firstNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
        self.firstNameText.placeholder = "First Name"
        self.firstNameCell.addSubview(self.firstNameText)

        // construct last name cell, section 0, row 1
        self.lastNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.lastNameText = UITextField(frame: self.lastNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
        self.lastNameText.placeholder = "Last Name"
        self.lastNameCell.addSubview(self.lastNameText)

        // construct share cell, section 1, row 0
        self.shareCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.shareCell.accessoryType = UITableViewCellAccessoryType.checkmark

        self.datePickerCell.backgroundColor = UIColor.red
        self.datePickerCell.addSubview(self.travelDatePicker)
        self.travelDatePicker.frame = CGRect(x: 0, y: 0, width: 500, height: 216)
        self.datePickerCell.accessoryType = UITableViewCellAccessoryType.none

        self.cityToCell.textLabel?.text = "Kiev"
        self.cityToCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.cityToCell.accessoryType = UITableViewCellAccessoryType.none

        self.cityFromCell.textLabel?.text = "San Francisco"
        self.cityFromCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.cityFromCell.accessoryType = UITableViewCellAccessoryType.none
    }

    func datePickerValueChanged(_ sender: UIDatePicker){

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let dateString = dateFormatter.string(from: travelDatePicker.date)
        self.shareCell.textLabel?.text = "\(dateString)"

        print("changed")
        print("Selected value \(dateString)")
    }

    // Return the number of sections
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

    // Return the number of rows for each section in your static table
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch(section) {
        case 0: return 2    // section 0 has 2 rows
        case 1: return 4    // section 1 has 1 row
        default: fatalError("Unknown number of sections")
        }
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.section == 1 && indexPath.row == 1{
            let height: CGFloat = travelDatePicker.isHidden ? 0.0 : 216.0
            return height
        }
        return 44.0
    }

    // Return the row for the corresponding section and row
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch(indexPath.section) {
        case 0:
            switch(indexPath.row) {
            case 0: return self.firstNameCell   // section 0, row 0 is the first name
            case 1: return self.lastNameCell    // section 0, row 1 is the last name
            default: fatalError("Unknown row in section 0")
            }
        case 1:
            switch(indexPath.row) {
            case 0: return self.shareCell       // section 1, row 0 is the share option
            case 1: return self.datePickerCell
            case 2: return self.cityToCell
            case 3: return self.cityFromCell
            default: fatalError("Unknown row in section 1")
            }
        default: fatalError("Unknown section")
        }
    }

    // Customize the section headings for each section
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        switch(section) {
        case 0: return "Profile"
        case 1: return "Social"
        default: fatalError("Unknown section")
        }
    }

    // Configure the row selection code for any cells that you want to customize the row selection
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        // Handle social cell selection to toggle checkmark
        if(indexPath.section == 1 && indexPath.row == 0) {

            // deselect row
            tableView.deselectRow(at: indexPath as IndexPath, animated: false)

            // toggle check mark
            if(self.shareCell.accessoryType == UITableViewCellAccessoryType.none) {
                self.shareCell.accessoryType = UITableViewCellAccessoryType.checkmark;
            } else {
                self.shareCell.accessoryType = UITableViewCellAccessoryType.none;
            }
        }

        if(indexPath.section == 1 && indexPath.row == 0) {

            travelDatePicker.isHidden = !travelDatePicker.isHidden

            UIView.animate(withDuration: 0.3, animations: { () -> Void in
                self.tableView.beginUpdates()
                // apple bug fix - some TV lines hide after animation
                self.tableView.deselectRow(at: indexPath, animated: true)
                self.tableView.endUpdates()
            })
        }
    }

}

【讨论】:

    【解决方案5】:

    我也一直在研究这个问题,我想我可能会分享我的解决方案,该解决方案源自此处已经提供的解决方案。

    我注意到在其他示例中有很多特定于单个元素的代码,所以我所做的是创建一个“管理器”类来处理任何项目。

    这是我所做的:

    CellShowHideDetail 存储有关您要显示或隐藏的项目的详细信息。这些详细信息包括它所在的单元格,以及将被点击以切换显示和隐藏的单元格:

    public class CellShowHideDetail
    {
        var item: UIView
        var indexPath_ToggleCell: IndexPath
        var indexPath_ItemCell: IndexPath
        var desiredHeight: CGFloat
    
        init(item: UIView, indexPath_ToggleCell: IndexPath, indexPath_ItemCell: IndexPath, desiredHeight: CGFloat)
        {
            self.item = item
            self.indexPath_ToggleCell = indexPath_ToggleCell
            self.indexPath_ItemCell = indexPath_ItemCell
            self.desiredHeight = desiredHeight
    
            //By default cells are not expanded:
            self.item.isHidden = true
        }
    }
    

    请注意,UIView 是大多数(所有?)UI 元素的父类。

    接下来我们有经理,它将根据需要处理任意数量的这些项目:

    import Foundation
    import UIKit
    
    public class CellShowHideManager
    {
        var cellItems: [CellShowHideDetail]
    
        init()
        {
            cellItems = []
        }
    
        func addItem(item: CellShowHideDetail)
        {
            cellItems.append(item)
        }
    
        func getRowHeight(indexPath: IndexPath) -> (match: Bool, height: CGFloat)
        {
            for item in cellItems
            {
                if indexPath.section == item.indexPath_ItemCell.section
                    && indexPath.row == item.indexPath_ItemCell.row
                {
                    return (match: true, height: item.item.isHidden ? 0.0 : item.desiredHeight)
                }
            }
    
            return (match: false, height: 0)
        }
    
        func rowSelected(indexPath: IndexPath) -> Bool
        {
            var changesMade = false
    
            for item in cellItems
            {
                if item.indexPath_ToggleCell == indexPath
                {
                    item.item.isHidden = !item.item.isHidden
    
                    changesMade = true
                }
                else
                {
                    if item.item.isHidden == false
                    {
                        changesMade = true
                    }
    
                    item.item.isHidden = true
                }
            }
    
            return changesMade
        }
    }
    

    然后您可以轻松地在任何 UITableViewController 类上创建 CellShowHideManager,添加您想要切换的项目:

    var showHideManager = CellShowHideManager()
    
    override func viewDidLoad()
        {
            super.viewDidLoad()
    
            let item1ToShowHide = CellShowHideDetail(item: datePicker, indexPath_ToggleCell: IndexPath(row: 0, section: 0), indexPath_ItemCell: IndexPath(row: 1, section: 0), desiredHeight: 232.0)
    
            let item2ToShowHide = CellShowHideDetail(item: selection_Picker, indexPath_ToggleCell: IndexPath(row: 0, section: 1), indexPath_ItemCell: IndexPath(row: 1, section: 1), desiredHeight: 90.0)
    
            //Add items for the expanding cells:
            showHideManager.addItem(item: item1ToShowHide)
            showHideManager.addItem(item: item2ToShowHide)
        }
    

    最后只需重写这两个TableView 方法,如下所示:

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
        {
            let showHideResult = showHideManager.getRowHeight(indexPath: indexPath)
    
            if showHideResult.match
            {
                return showHideResult.height
            }
            else
            {
                return super.tableView(tableView, heightForRowAt: indexPath)
            }
        }
    
        override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
        {
    
            if showHideManager.rowSelected(indexPath: indexPath)
            {
                UIView.animate(withDuration: 0.3, animations: { () -> Void in
                    self.tableView.beginUpdates()
    
                    // apple bug fix - some TV lines hide after animation
                    //self.tableView.deselectRowAt(indexPath, animated: true)
                    self.tableView.endUpdates()
                })
            }
        }
    

    它应该很好用!

    【讨论】:

      【解决方案6】:

      只是想我也会加上我的两分钱。我实际上是在 Xamarin 中编程,必须进行一些小调整才能使其在 Xamarin 框架中工作。

      所有的原则都是相同的,但是 Xamarin 为 TableViewSource 使用了一个单独的类,因此委托的管理是不同的。当然,如果你也想在 UIViewController 中分配 UITableViewDelegates,当然可以,但我很好奇我是否可以让它以这种方式工作:

      首先,我对日期选择器单元 (datePickerCell) 和选择器单元 (selectorCell) 进行了子类化。 旁注,我在没有 StoryBoard 的情况下 100% 以编程方式完成此操作

      datePickerCell:

      using System;
      using UIKit;
      
      namespace DatePickerInTableViewCell 
      {
          public class CustomDatePickerCell : UITableViewCell
          {
              //========================================================================================================================================
              //  PRIVATE CLASS PROPERTIES
              //========================================================================================================================================
              private UIDatePicker datePicker;
              private bool datePickerVisible;
              private Boolean didUpdateConstraints;
      
              //========================================================================================================================================
              //  PUBLIC CLASS PROPERTIES
              //========================================================================================================================================
              public event EventHandler dateChanged;
              //========================================================================================================================================
              //  Constructor
              //========================================================================================================================================
              /// <summary>
              /// Initializes a new instance of the <see cref="DatePickerInTableViewCell.CustomDatePickerCell"/> class.
              /// </summary>
              public CustomDatePickerCell (string rid) : base(UITableViewCellStyle.Default, rid)
              {
                  Initialize ();
              }
              //========================================================================================================================================
              //  PUBLIC OVERRIDES
              //========================================================================================================================================
              /// <summary>
              /// Layout the subviews.
              /// </summary>
              public override void LayoutSubviews ()
              {
                  base.LayoutSubviews ();
      
                  ContentView.AddSubview (datePicker);
      
                  datePicker.Hidden   = true;
                  AutoresizingMask    = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
      
                  foreach (UIView view in ContentView.Subviews) {
                      view.TranslatesAutoresizingMaskIntoConstraints = false;
                  }
                  ContentView.SetNeedsUpdateConstraints ();
      
              }
      
              /// <summary>
              /// We override the UpdateConstraints to allow us to only set up our constraint rules one time.  Since 
              /// we need to use this method to properly call our constraint rules at the right time we use a boolean
              /// as a flag so that we only fix our auto layout once.  Afterwards UpdateConstraints runs as normal. 
              /// </summary>
              public override void UpdateConstraints ()
              {
                  if (NeedsUpdateConstraints () && !didUpdateConstraints) {
                      setConstraints ();
                      didUpdateConstraints = true;
                  }
                  base.UpdateConstraints ();
              }
              //========================================================================================================================================
              //  PUBLIC METHODS
              //========================================================================================================================================
      
              /// <summary>
              /// Allows us to determine the visibility state of the cell from the tableViewSource.
              /// </summary>
              /// <returns><c>true</c> if this instance is visible; otherwise, <c>false</c>.</returns>
              public bool IsVisible()
              {
                  return datePickerVisible;
              }
      
              /// <summary>
              /// Allows us to show the datePickerCell from the tableViewSource.
              /// </summary>
              /// <param name="tableView">Table view.</param>
              public void showDatePicker(ref UITableView tableView)
              {
      
                  datePickerVisible   = true;
                  tableView.BeginUpdates  ();
                  tableView.EndUpdates    ();
                  datePicker.Hidden   = false;
                  datePicker.Alpha    = 0f;
      
                  UIView.Animate(
                      0.25, 
                      ()=> { datePicker.Alpha = 1f;}
                  );
              }
      
              public void hideDatePicker(ref UITableView tableView)
              {
                  datePickerVisible   = false;
                  tableView.BeginUpdates  ();
                  tableView.EndUpdates    ();
      
                  UIView.Animate(
                      0.25, 
                      ()=> { datePicker.Alpha = 0f;}, 
                      ()=> {datePicker.Hidden = true;}
                  );
              }
              //========================================================================================================================================
              //  PRIVATE METHODS
              //========================================================================================================================================
              /// <summary>
              /// We make sure the UIDatePicker is center in the cell.
              /// </summary>
              private void setConstraints()
              {
                  datePicker.CenterXAnchor.ConstraintEqualTo(ContentView.CenterXAnchor).Active = true;
              }
      
              /// <summary>
              /// Init class properties.
              /// </summary>
              private void Initialize()
              {
                  datePicker              = new UIDatePicker ();
                  datePickerVisible       = false;
                  datePicker.TimeZone     = Foundation.NSTimeZone.LocalTimeZone;
                  datePicker.Calendar     = Foundation.NSCalendar.CurrentCalendar;
      
                  datePicker.ValueChanged += (object sender, EventArgs e) => {
                      if(dateChanged != null) {
                          dateChanged (datePicker, EventArgs.Empty);
                      }
                  };
              }
          }
      }   
      

      选择器单元格

      using System;
      using UIKit;
      
      namespace DatePickerInTableViewCell 
      {
          ///<summary>
          ///
          ///</summary>
          public class CustomDatePickerSelectionCell : UITableViewCell
          {
              //========================================================================================================================================
              //  PRIVATE CLASS PROPERTIES
              //========================================================================================================================================
              private UILabel prefixLabel;
              private UILabel dateLabel;
              private UILabel timeLabel;
              private Boolean didUpdateConstraints;
              private UIColor originalLableColor;
              private UIColor editModeLabelColor;
              //========================================================================================================================================
              //  PUBLIC CLASS PROPERTIES
              //========================================================================================================================================
              //========================================================================================================================================
              //  Constructor
              //========================================================================================================================================
              /// <summary>
              /// Initializes a new instance of the <see cref="DatePickerInTableViewCell.CustomDatePickerSelectionCell"/> class.
              /// </summary>
              public CustomDatePickerSelectionCell (string rid) : base(UITableViewCellStyle.Default, rid)
              {
                  Initialize ();
              }
              //========================================================================================================================================
              //  PUBLIC OVERRIDES
              //========================================================================================================================================
              /// <summary>
              /// We override the UpdateConstraints to allow us to only set up our constraint rules one time.  Since 
              /// we need to use this method to properly call our constraint rules at the right time we use a boolean
              /// as a flag so that we only fix our auto layout once.  Afterwards UpdateConstraints runs as normal. 
              /// </summary>
              public override void UpdateConstraints ()
              {
                  if (NeedsUpdateConstraints () && !didUpdateConstraints) {
                      setConstraints ();
                      didUpdateConstraints = true;
                  }
                  base.UpdateConstraints ();
              }
      
              public override void LayoutSubviews ()
              {
                  base.LayoutSubviews ();
      
                  AutoresizingMask    = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
                  timeLabel.TextAlignment = UITextAlignment.Right;
                  prefixLabel.Text    = "On: ";
                  dateLabel.Text      = DateTime.Now.ToString ("MMM d, yyyy");
                  timeLabel.Text      = DateTime.Now.ToShortTimeString ();
      
                  ContentView.AddSubviews (new UIView[]{ prefixLabel, dateLabel, timeLabel });
                  foreach (UIView view in ContentView.Subviews) {
                      view.TranslatesAutoresizingMaskIntoConstraints = false;
                  }
      
                  ContentView.SetNeedsUpdateConstraints ();
              }
              //========================================================================================================================================
              //  PUBLIC METHODS
              //========================================================================================================================================
              public void willUpdateDateTimeLables(string date, string time)
              {
                  dateLabel.Text = date;
                  timeLabel.Text = time;
      
              }
      
              public void willEditDateTime()
              {
                  dateLabel.TextColor = editModeLabelColor;
                  timeLabel.TextColor = editModeLabelColor;
              }
      
              public void didEditDateTime()
              {
                  dateLabel.TextColor = originalLableColor;
                  timeLabel.TextColor = originalLableColor;
              }
              //========================================================================================================================================
              //  PRIVATE METHODS
              //========================================================================================================================================
              private void Initialize()
              {
                  prefixLabel         = new UILabel ();
                  dateLabel       = new UILabel ();
                  timeLabel       = new UILabel ();
                  originalLableColor  = dateLabel.TextColor;
                  editModeLabelColor  = UIColor.Red;
              }
      
      
      
              private void setConstraints()
              {
                  var cellMargins = ContentView.LayoutMarginsGuide;
      
                  prefixLabel.LeadingAnchor.ConstraintEqualTo (cellMargins.LeadingAnchor).Active      = true;
                  dateLabel.LeadingAnchor.ConstraintEqualTo (prefixLabel.TrailingAnchor).Active       = true;
                  timeLabel.LeadingAnchor.ConstraintEqualTo (dateLabel.TrailingAnchor).Active         = true;
                  timeLabel.TrailingAnchor.ConstraintEqualTo (cellMargins.TrailingAnchor).Active      = true;
      
                  dateLabel.WidthAnchor.ConstraintEqualTo (ContentView.WidthAnchor, 2f / 7f).Active   = true;
                  prefixLabel.HeightAnchor.ConstraintEqualTo (ContentView.HeightAnchor, 1).Active     = true;
                  timeLabel.HeightAnchor.ConstraintEqualTo (ContentView.HeightAnchor, 1).Active       = true;
                  dateLabel.HeightAnchor.ConstraintEqualTo (ContentView.HeightAnchor, 1).Active       = true;
              }
          }
      }
      

      如您所见,我从每个单元格中公开了一些方法来促进所需的通信。然后我需要在我的 tableViewSource 中创建这些单元格的实例。也许这样做的耦合方式较少,但我无法轻易弄清楚。我相信我在 iOS 编程方面的经验比我上面的前辈要少得多 :)。也就是说,由于单元格在类的范围内可用,因此可以很容易地调用和访问 RowSelected 和 GetHeightForRow 方法中的单元格。

      TableViewSource

      using System;
      using UIKit;
      using System.Collections.Generic;
      
      namespace DatePickerInTableViewCell 
      {
          public class TableViewSource : UITableViewSource
          {
              //========================================================================================================================================
              //  PRIVATE CLASS PROPERTIES
              //========================================================================================================================================
              private const string datePickerIdentifier           = "datePickerCell";
              private const string datePickerActivateIdentifier   = "datePickerSelectorCell";
              private const int datePickerRow                     = 1;
              private const int datePickerSelectorRow             = 0;
      
              private List<UITableViewCell> datePickerCells;
              private CustomDatePickerCell datePickerCell;
              private CustomDatePickerSelectionCell datePickerSelectorCell;
              //========================================================================================================================================
              //  PUBLIC CLASS PROPERTIES
              //========================================================================================================================================
              //========================================================================================================================================
              //  Constructor
              //========================================================================================================================================
              /// <summary>
              /// Initializes a new instance of the <see cref="DatePickerInTableViewCell.TableViewSource"/> class.
              /// </summary>
              public TableViewSource ()
              {
                  initDemoDatePickerCells ();
              }
      
      
              //========================================================================================================================================
              //  PUBLIC OVERRIDES
              //========================================================================================================================================
              public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
              {
                  UITableViewCell cell = null;
      
                  if (indexPath.Row == datePickerSelectorRow) {
                      cell = tableView.DequeueReusableCell (datePickerActivateIdentifier);
                      cell = cell ?? datePickerCells[indexPath.Row];
                      return cell;
                  }
      
                  if (indexPath.Row == datePickerRow) {
                      cell = tableView.DequeueReusableCell (datePickerIdentifier) as CustomDatePickerCell;
                      cell = cell ?? datePickerCells[indexPath.Row];
                      return cell;
                  }
      
      
                  return cell;
      
              }
      
              public override nint RowsInSection (UITableView tableview, nint section)
              {
                  return datePickerCells.Count;
              }
      
              public override nfloat GetHeightForRow (UITableView tableView, Foundation.NSIndexPath indexPath)
              {
      
                  float height = (float) tableView.RowHeight;
                  if (indexPath.Row == datePickerRow) {
                      height = datePickerCell.IsVisible () ? DefaultiOSDimensions.heightForDatePicker : 0f;
                  }
      
                  return height;
              }
      
              public override void RowSelected (UITableView tableView, Foundation.NSIndexPath indexPath)
              {
                  if (indexPath.Row == datePickerSelectorRow) {
                      if (datePickerCell != null) {
                          if (datePickerCell.IsVisible ()) {
                              datePickerCell.hideDatePicker (ref tableView);
                              datePickerSelectorCell.didEditDateTime ();
                          } else {
                              datePickerCell.showDatePicker (ref tableView);
                              datePickerSelectorCell.willEditDateTime ();
                          }
      
                      }
                  }
      
                  tableView.DeselectRow (indexPath, true);
              }
      
      
              //========================================================================================================================================
              //  PUBLIC METHODS
              //========================================================================================================================================
      
              //========================================================================================================================================
              //  PRIVATE METHODS
              //========================================================================================================================================
              private void willUpdateDateChanged(Object sender, EventArgs args)
              {
                  var picker      = sender as UIDatePicker;
                  var dateTime    = picker.Date.ToDateTime ();
                  if (picker != null && dateTime != null) {
                      var date = dateTime.ToString ("MMM d, yyyy");
                      var time = dateTime.ToShortTimeString ();
                      datePickerSelectorCell.willUpdateDateTimeLables (date, time);
                  }
      
              }
      
              private void initDemoDatePickerCells()
              {
                  datePickerCell              = new CustomDatePickerCell (datePickerIdentifier);
                  datePickerSelectorCell      = new CustomDatePickerSelectionCell (datePickerActivateIdentifier);
      
                  datePickerCell.dateChanged  += willUpdateDateChanged;
      
                  datePickerCells             = new List<UITableViewCell> () {
                      datePickerSelectorCell,
                      datePickerCell
                  };
              }
          }
      }
      

      希望代码是相当不言自明的。顺便说一句,toDateTime 方法只是将 NSDateTime 转换为 .net DateTime 对象的扩展方法。可以在此处找到参考:https://forums.xamarin.com/discussion/27184/convert-nsdate-to-datetime 和 DefaultiOSDimensions 只是一个小型静态类,我用它来跟踪典型尺寸,例如 cellHeight (44pts) 或 heightForDatePicker 的情况; 216. 在我的模拟器上似乎对我很有效。我还没有在实际设备上进行测试。希望它可以帮助某人!

      【讨论】:

        猜你喜欢
        • 2018-12-13
        • 2013-06-06
        • 1970-01-01
        • 2014-11-25
        • 1970-01-01
        • 2012-05-15
        • 2014-02-26
        • 2016-11-15
        • 1970-01-01
        相关资源
        最近更新 更多