Expanding and collapsing UITableViewCells with DatePicker

后端 未结 6 1328
一生所求
一生所求 2020-12-02 05:21

I\'m building an app that lets the user select dates from a UITableView. The tableView is static and grouped. I\'ve looked through many questions, including this one, trying

相关标签:
6条回答
  • 2020-12-02 05:41

    I implemented @thorb65's answer in Swift, and it works like a charm. Even if I set up two date pickers (e.g., "start" and "end" like in calendar), and set them up so that the open one collapses automatically when expaning the other one (i.e., "one open at a time maximum" policy, just like Calendar), the (concurrent) animations are still smooth.

    One thing I struggled with, though, is finding the right autolayout constraints. The following gave me the same behaviour as Calendar.app:

    1. Collapse from bottom up (date picker contents do not move)

    Constrains from UIDatePicker towards itself:

    • Height

    Constrains from UIDatePicker against UITableViewCell's content view:

    • Leading Space to Container Margin
    • Trailing Space to Container Margin
    • Top Space to Container Margin

    Resulting animation

    "Bottom Space to Container Margin" is explicitly left out, to enforce fixed hight all along the animation (this recreates Calendar.app's behaviour, where the table view cell "slides open" to reveal the unchanging, fixed-height date picker beneath).

    1. Collapse from bottom up (date picker moves uniformly)

    Constrains from UIDatePicker towards itself:

    • Height

    Constrains from UIDatePicker against UITableViewCell's content view:

    • Leading Space to Container Margin
    • Trailing Space to Container Margin
    • Center vertically in outer container

    Resulting animation

    Notice the difference the constraints make in the collapse/expand animation.

    EDIT: This is the swift code

    Properties:

    // "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 methods:

    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 Methods:

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

    Date Picker Control Actions:

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

    Auxiliary Methods: (animation, date formatting)

    @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
            }
        )
    }
    
    0 讨论(0)
  • 2020-12-02 05:46

    I am sharing my answer:

    I am doing everything without storyboard

    Swift 3

    1.1 add the 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 and its method

    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. then in the loadView display the date in the cell above with format

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

    3. add datePicker to the cell

    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. set the height of the cell

    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. and finally set the if statement in the didSelectAt

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

    Full code is here with other elements just get a feeling of working app

    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()
                })
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-02 05:47

    I've been working on this too, and I thought I might share my solution, which is derived from the ones already provided here.

    What I noticed is that there is a lot of code in the other examples specific to individual elements, and so what I did was to create a 'manager' class to deal with it for any item.

    Here is what I did:

    The CellShowHideDetail stores details about the item you want to show or hide. These details include the cell it is in, and also the cell that will be tapped to toggle the showing and hiding:

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

    Note that UIView is a parent class of most (all?) UI elements.

    Next we have the manager, which will process as many of these items as you like:

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

    You can then easily create a CellShowHideManager on any UITableViewController class, add in the items you want to be toggle-able:

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

    Finally just override these two TableView methods as follows:

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

    And it should work nicely!

    0 讨论(0)
  • 2020-12-02 05:52

    I assume you're using storyboard, the example is with UIPickerView: Create a tableviewcell right under the cell that contains the textfield you want to fill and set the cells row height to 216.0 in the inspector and add a UIPickerView to that cell.

    see here

    Next connect the UIPickerView via Outlet to your viewcontroller and add the following property to your ViewController.h:

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

    In your ViewController.m do in viewWillAppear

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

    Add two methods:

    - (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];
    }
    
    0 讨论(0)
  • 2020-12-02 05:59

    Just thought I would add my two cents as well. I am actually programming in Xamarin and had to make some small adjustments to get it to work in the framework of Xamarin.

    All of the principles are the same but as Xamarin uses a separate class for the TableViewSource and as such the management of delegates is different. Of course you can always assign UITableViewDelegates if you want as well in the UIViewController, but I was curious if I could get it to work this way:

    To start with I subclassed both the Date Picker Cell (datePickerCell) and the selector cell (selectorCell). Side note, I am doing this 100% programmatically without a StoryBoard

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

    Selector Cell

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

    So as you can see I have some methods exposed from each cell to facilitate the needed communication. I then needed to create an instance of these cells in my tableViewSource. Maybe there is a less coupling way of doing this but I couldn't easily figure that out. I believe I am a lot less experienced in iOS programming than my predecessors above :). That said, with the cells available in the scope of the class it makes it very easy to call and access the cells in the RowSelected and GetHeightForRow methods.

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

    Hope the code is fairly self explanatory. The method toDateTime btw is just an extension method to convert NSDateTime to a .net DateTime object. The reference can be found here: https://forums.xamarin.com/discussion/27184/convert-nsdate-to-datetime and DefaultiOSDimensions is just a small static class that I use to keep track of typical dimensions such as cellHeight (44pts) or in the case of heightForDatePicker; 216. It seems to work great for me on my simulator. I have yet to test on it actual devices. Hope it helps someone!

    0 讨论(0)
  • 2020-12-02 06:02

    The 2 answers above enabled me to solve this problem. They deserve the credit, I'm adding this a reminder for myself - summary format.

    This is my version of the above answers.

    1. As noted above - add picker to a the cell you want to show / hide.

    2. Add constraints for the picker in interface builder - center X / center Y / equal height / equal width to the cell's content view

    3. Connect the picker to you VC

    @IBOutlet weak var dobDatePicker: UIDatePicker!
    

    You might as well control drag and add a method that will register the date changes

    @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. Setting cell heights, in my example the cell I want to expand is section 0, row 3... set this to what you cell you want to expand / hide. If you have many cells with various heights this allows for that.

    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. Selecting the cell above to expand the one below, again set this to the cell you will tap to show the cell below.

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

    0 讨论(0)
提交回复
热议问题