Расширение и свертывание UITableViewCells с помощью DatePicker

Я создаю приложение, которое позволяет пользователю выбирать даты из UITableView. TableView является статическим и сгруппированным. Я просмотрел много вопросов, в том числе этот, пытаясь понять, как это сделать, но ничего не работает оптимально. Приложение календаря Apple имеет очень плавную и красивую анимацию, которую не удалось воссоздать ни в одном из примеров, через которые я прошел.

Это мой желаемый результат:

Выбор даты на месте

Может ли кто-нибудь указать мне на учебник или объяснить, как я могу выполнить такую ​​плавную анимацию с помощью наиболее лаконичного и прямого способа сделать это, как мы видим в приложении календаря?

Большое спасибо!

Эрик


person Erik    schedule 16.04.2015    source источник
comment
ваше табличное представление статично или прототип?   -  person thorb65    schedule 16.04.2015
comment
@ thorb65 это статический и сгруппированный tableView   -  person Erik    schedule 16.04.2015
comment
Я создал пример репозитория с моим решением. Он должен работать в любом TableView. (статические/динамические/сгруппированные) Может кому пригодится. В настоящее время у меня нет времени, чтобы создать более подробный ответ. Вот ссылка: github.com/hettiger/ios-expandable-table-view- ячейка   -  person Martin    schedule 04.01.2020


Ответы (6)


Я предполагаю, что вы используете раскадровку, пример с UIPickerView: создайте tableviewcell прямо под ячейкой, содержащей текстовое поле, которое вы хотите заполнить, и установите высоту строки ячеек на 216,0 в инспекторе и добавьте UIPickerView в эту ячейку.

см. здесь

Затем подключите UIPickerView через Outlet к вашему контроллеру представления и добавьте следующее свойство в свой 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;
                 }];
}

In 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;
}

In 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];
}
person thorb65    schedule 16.04.2015
comment
Очень хороший ответ. Сделал это сейчас, анимация расширения ячейки выглядит хорошо, но по какой-то причине pickerView невидим - person Erik; 16.04.2015
comment
Похоже, альфа-значение остается равным 0 даже в блоке завершения анимации (NSLog). Похоже на [cell.contentView addSubview:self.statusPicker]; не работает должным образом - person Erik; 16.04.2015
comment
Удаление методов addSubview и removeFromSubview заставило его работать - person Erik; 16.04.2015
comment
хорошо, что это работает :-) я удалил addSubview и связанный с ним из примера. - person thorb65; 17.04.2015
comment
Ницца. Я настроил его для работы с несколькими разделами, и он работает безупречно с тем же взаимодействием с пользователем, что и Apple в своем приложении. Анимации очень приятные, намного плавнее, чем в других примерах. - person Erik; 17.04.2015
comment
@Erik @thorb65 Я немного смущен тем, что именно находится в вашей раскадровке, поскольку вы не можете поместить UIPicker в ячейку (что может быть моим недоразумением). Как именно организована ваша раскадровка? - person Anconia; 30.07.2015
comment
Я получаю следующую ошибку: The datePicker outlet is invalid. Outlets cannot be connected to repeating content - person Anconia; 30.07.2015
comment
Обновление: мне пришлось встроить tableVC в представление контейнера, чтобы установить представление таблицы для статических ячеек (иначе средство выбора даты не может быть подключено). См. stackoverflow.com/questions/22364230/ - person Anconia; 30.07.2015
comment
@Anconia Я добавил UIPicker/UIDatePicker непосредственно в представление содержимого моего UITableViewCell. Я считаю, что вам понадобится UITableViewController, если вы собираетесь использовать статические ячейки, но ячейки-прототипы работают в tableViews в обычных подклассах UIViewController. - person Erik; 30.07.2015
comment
Здесь есть одна проблема: когда средство выбора видно и вы дважды быстро нажимаете, у вас будет расширенная пустая ячейка средства выбора. Чтобы избежать этой проблемы, вы должны добавить завершение в showStatusPickerCell и установить там self.statusPicker.hidden = NO; - person Oleshko; 02.06.2017

2 ответа выше позволили мне решить эту проблему. Они заслуживают похвалы, я добавляю это напоминание для себя - формат резюме.

Это моя версия приведенных выше ответов.

1. Как отмечалось выше, добавьте средство выбора в ячейку, которую хотите показать/скрыть.

2. Добавьте ограничения для средства выбора в конструкторе интерфейса — центр X / центр Y / одинаковая высота / одинаковая ширина для представления содержимого ячейки.

3. Подключите средство выбора к вашей ВК.

@IBOutlet weak var dobDatePicker: UIDatePicker!

Вы также можете контролировать перетаскивание и добавить метод, который будет регистрировать изменения даты.

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

4. In viewDidLoad

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()
    })
}
}

введите описание изображения здесь

person DogCoffee    schedule 10.01.2016
comment
Это было потрясающе, но если мы хотим сделать одну и ту же ячейку расширяемой, то как мы можем поступить? - person Abhishek Thapliyal; 15.07.2017
comment
Моя анимация коллапса не плавная. При закрытии я вижу первый текст Пн, 1 февраля ... получает первое усечение? - person manismku; 10.09.2018
comment
Здравствуйте, это решение не работает должным образом с iOS 14. Вы знаете, как это исправить? - person Gianni; 22.09.2020
comment
@Джанни, извините, я давно не смотрел на это - теперь я использую SwiftUI - person DogCoffee; 23.09.2020

Я реализовал ответ @thorb65 в Swift, и он работает как шарм. Даже если я настрою два средства выбора даты (например, «начало» и «конец», как в календаре), и настрою их так, чтобы открытое автоматически сворачивалось при раскрытии другого (т. е. «одно открытое время максимум» политики, как и в случае с Календарем), (параллельная) анимация по-прежнему плавная.

Одна вещь, с которой я боролся, — это поиск правильных ограничений автомакета. Следующее дало мне то же поведение, что и Calendar.app:

  1. Свернуть снизу вверх (содержимое средства выбора даты не перемещается)

Ограничения от UIDatePicker к себе:

  • Высота

Ограничения от UIDatePicker против представления содержимого UITableViewCell:

  • Ведущее пространство к марже контейнера
  • Завершающий пробел до поля контейнера
  • Верхнее пространство к краю контейнера

Результирующая анимация

«Нижнее пространство до поля контейнера» явно опущено, чтобы обеспечить фиксированную высоту на протяжении всей анимации (это воссоздает поведение Calendar.app, где ячейка табличного представления «раздвигается», открывая неизменный элемент выбора даты фиксированной высоты ниже).

  1. Свернуть снизу вверх (средство выбора даты перемещается равномерно)

Ограничения от UIDatePicker к себе:

  • Высота

Ограничения от UIDatePicker против представления содержимого UITableViewCell:

  • Ведущее пространство к марже контейнера
  • Завершающий пробел до поля контейнера
  • Отцентрировать по вертикали во внешнем контейнере

Результирующая анимация

Обратите внимание на разницу, которую ограничения вносят в анимацию свертывания/развертывания.

EDIT: Это быстрый код

Свойства:

// "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
        }
    )
}
person Nicolas Miari    schedule 02.10.2015
comment
вы должны добавить быстрый код здесь ... или я могу добавить его в свой ответ и упомянуть вас :-) - person thorb65; 10.10.2015
comment
Может быть, я просто не вижу, но мне кажется, что метод formatDate отсутствует?! Кроме того, это отличный ответ, который дает очень красивое решение! Спасибо, что поделился - person chuky; 16.11.2015
comment
Да, это всего лишь очень маленький метод, который оборачивает NSDateFormatter и возвращает отформатированную строку. - person Nicolas Miari; 16.11.2015
comment
Это работает очень хорошо. Я использую это в статической таблице с пятью ячейками. Ячейка данных находится внизу, а ячейка выбора — прямо над ней. У меня небольшая проблема с этим функционированием, когда устройство находится в ландшафтном режиме. Когда я расширяю ячейку выбора даты, таблица не прокручивается вверх, а средство выбора даты находится в нижней части экрана. Как я могу заставить представление таблицы прокручиваться вверх, когда отображается средство выбора даты? - person Scott Kilbourn; 15.12.2015
comment
UITableView имеет несколько методов программной прокрутки до заданной позиции; прочитайте документы. - person Nicolas Miari; 16.12.2015
comment
@NicolasMiari Спасибо! Ограничения очень важны для того, как выглядит анимация при сворачивании ячейки с помощью средства выбора даты. С этими предоставленными ограничениями все просто смещается при разрушении. Я обнаружил, что если вы вместо этого установите этот набор ограничений, вы получите более равномерную анимацию свертывания: - Вертикальное центрирование средства выбора даты в представлении содержимого ячейки - Установите начальные и конечные ограничения для поля контейнера - Высота средства выбора даты Таким же образом при сворачивании средства выбора даты также движется вверх (а не только ячейка движется вверх). - person Octocat; 09.01.2018

Я тоже работал над этим, и я подумал, что могу поделиться своим решением, которое основано на уже представленных здесь.

Что я заметил, так это то, что в других примерах много кода, специфичного для отдельных элементов, поэтому я создал класс «менеджер», чтобы обрабатывать его для любого элемента.

Вот что я сделал:

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 является родительским классом большинства (всех?) элементов пользовательского интерфейса.

Далее у нас есть менеджер, который будет обрабатывать сколько угодно таких элементов:

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
    }
}

Затем вы можете легко создать CellShowHideManager для любого класса UITableViewController, добавив элементы, которые вы хотите переключать:

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()
            })
        }
    }

И это должно работать красиво!

person Greg    schedule 26.10.2016

Делюсь своим ответом:

Я делаю все без раскадровки

Свифт 3

1.1 добавить datePicker

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. и, наконец, установите оператор if в didSelectAt

если (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()
            })
        }
    }

}
person dmyma    schedule 20.07.2017

Просто подумал, что тоже добавлю свои два цента. На самом деле я программирую в Xamarin, и мне пришлось внести небольшие коррективы, чтобы заставить его работать в рамках Xamarin.

Все принципы одинаковы, но поскольку Xamarin использует отдельный класс для TableViewSource, управление делегатами отличается. Конечно, вы всегда можете назначить UITableViewDelegates, если хотите, и в UIViewController, но мне было любопытно, смогу ли я заставить его работать следующим образом:

Для начала я разделил на подклассы как ячейку выбора даты (datePickerCell), так и ячейку выбора (selectorCell). Примечание: я делаю это на 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.

Источник таблицы

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. Мне кажется, на моем тренажере это отлично работает. Мне еще предстоит протестировать на нем реальные устройства. Надеюсь, это поможет кому-то!

person Chris Shields    schedule 15.05.2016