在Swift中点击时展开单元格

时间:2022-11-21 21:35:13

I have been trying to implement a feature in my app so that when a user taps a cell in my table view, the cell expands downwards to reveal notes. I have found plenty of examples of this in Objective-C but I am yet to find any for Swift.

我一直在尝试在我的应用程序中实现一个功能,当用户在我的表格视图中点击一个单元时,单元格向下扩展以显示注释。我在Objective-C中已经找到了很多这样的例子,但是我还没有找到斯威夫特的例子。

This example seems perfect: Accordion table cell - How to dynamically expand/contract uitableviewcell?

这个例子看起来很完美:手风琴表格单元格——如何动态扩展/收缩uitableviewcell?

I had an attempt at translating it to Swift:

我试着把它翻译给Swift:

var selectedRowIndex = NSIndexPath()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedRowIndex = indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if selectedRowIndex == selectedRowIndex.row && indexPath.row == selectedRowIndex.row {
        return 100
    }
    return 70
}

However this just seems to crash the app.

然而,这似乎让应用程序崩溃了。

Any ideas?

什么好主意吗?

Edit:

编辑:

Here is my cellForRowAtIndexPath code:

下面是我的cellForRowAtIndexPath代码:

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell

    cell.selectionStyle = UITableViewCellSelectionStyle.None

    if tableView == self.searchDisplayController?.searchResultsTableView {
        cell.paymentNameLabel.text = (searchResults.objectAtIndex(indexPath.row)) as? String
        //println(searchResults.objectAtIndex(indexPath.row))
        var indexValue = names.indexOfObject(searchResults.objectAtIndex(indexPath.row))
        cell.costLabel.text = (values.objectAtIndex(indexValue)) as? String
        cell.dateLabel.text = (dates.objectAtIndex(indexValue)) as? String

        if images.objectAtIndex(indexValue) as NSObject == 0 {
            cell.paymentArrowImage.hidden = false
            cell.creditArrowImage.hidden = true
        } else if images.objectAtIndex(indexValue) as NSObject == 1 {
            cell.creditArrowImage.hidden = false
            cell.paymentArrowImage.hidden = true
        }
    } else {
        cell.paymentNameLabel.text = (names.objectAtIndex(indexPath.row)) as? String
        cell.costLabel.text = (values.objectAtIndex(indexPath.row)) as? String
        cell.dateLabel.text = (dates.objectAtIndex(indexPath.row)) as? String

        if images.objectAtIndex(indexPath.row) as NSObject == 0 {
            cell.paymentArrowImage.hidden = false
            cell.creditArrowImage.hidden = true
        } else if images.objectAtIndex(indexPath.row) as NSObject == 1 {
            cell.creditArrowImage.hidden = false
            cell.paymentArrowImage.hidden = true
        }
    }
    return cell
}

Here are the outlet settings:

这里是outlet设置:

在Swift中点击时展开单元格

5 个解决方案

#1


4  

The first comparison in your if statement can never be true because you're comparing an indexPath to an integer. You should also initialize the selectedRowIndex variable with a row value that can't be in the table, like -1, so nothing will be expanded when the table first loads.

if语句中的第一个比较永远不会为真,因为您正在将indexPath与整数进行比较。您还应该初始化selecteindex变量,该变量的行值不能在表中,比如-1,所以当表首次加载时,没有任何东西会被扩展。

var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if indexPath.row == selectedRowIndex.row {
        return 100
    }
    return 70
}

#2


14  

It took me quite a lot of hours to get this to work. Below is how I solved it.

我花了很长时间才把它弄出来。下面是我解决它的方法。

PS: the problem with @rdelmar's code is that he assumes you only have one section in your table, so he's only comparing the indexPath.row. If you have more than one section (or if you want to already account for expanding the code later) you should compare the whole index, like so:

PS: @rdelmar代码的问题是,他假设您的表中只有一个部分,所以他只是比较了indexPath.row。如果您有多个部分(或者您想要在以后扩展代码),您应该比较整个索引,比如:

1) You need a variable to tell which row is selected. I see you already did that, but you'll need to return the variable to a consistent "nothing selected" state (for when the user closes all cells). I believe the best way to do this is via an optional:

1)需要一个变量来判断选择了哪一行。我看到您已经这样做了,但是您需要将变量返回到一致的“无选择”状态(当用户关闭所有单元格时)。我认为最好的方法是通过一个可选的:

var selectedIndexPath: NSIndexPath? = nil

2) You need to identify when the user selects a cell. didSelectRowAtIndexPath is the obvious choice. You need to account for three possible outcomes:

2)您需要识别用户何时选择单元格。didSelectRowAtIndexPath是显而易见的选择。你需要考虑三种可能的结果:

  1. the user is tapping on a cell and another cell is expanded
  2. 用户点击一个单元格,另一个单元格被展开
  3. the user is tapping on a cell and no cell is expanded
  4. 用户点击一个单元,没有任何单元被展开
  5. the user is tapping on a cell that is already expanded
  6. 用户正在访问一个已经展开的单元格

For each case we check if the selectedIndexPath is equal to nil (no cell expanded), equal to the indexPath of the tapped row (same cell already expanded) or different from the indexPath (another cell is expanded). We adjust the selectedIndexPath accordingly. This variable will be used to check the right rowHeight for each row. You mentioned in comments that didSelectRowAtIndexPath "didn't seem to be called". Are you using a println() and checking the console to see if it was called? I included one in the code below.

对于每种情况,我们检查selectedIndexPath是否等于nil(没有展开单元格),是否等于已展开的行的indexPath(已展开的相同单元格),或者与indexPath(已展开的另一个单元格)不同。我们相应地调整selectedIndexPath。这个变量将用于为每一行检查正确的rowh8。在didSelectRowAtIndexPath的注释中,您提到“似乎没有调用”。您是否正在使用println()并检查控制台以查看它是否被调用?我在下面的代码中包含了一个。

PS: this doesn't work using tableView.rowHeight because, apparently, rowHeight is checked only once by Swift before updating ALL rows in the tableView.

PS:使用tableView是不行的。rowh8因为显然,rowh8在更新tableView中的所有行之前只被Swift检查过一次。

Last but not least, I use reloadRowsAtIndexPath to reload only the needed rows. But, also, because I know it will redraw the table, relayout when necessary and even animate the changes. Note the [indexPath] is between brackets because this method asks for an Array of NSIndexPath:

最后,我使用reloadRowsAtIndexPath只重载需要的行。但是,同时,因为我知道它会重新画桌子,在必要的时候放松,甚至会使变化有生气。注意,[indexPath]位于括号之间,因为此方法需要一个NSIndexPath数组:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("didSelectRowAtIndexPath was called")
        var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
        switch selectedIndexPath {
        case nil:
            selectedIndexPath = indexPath
        default:
            if selectedIndexPath! == indexPath {
                selectedIndexPath = nil
            } else {
                selectedIndexPath = indexPath
            }
        }
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}

3) Third and final step, Swift needs to know when to pass each value to the cell height. We do a similar check here, with if/else. I know you can made the code much shorter, but I'm typing everything out so other people can understand it easily, too:

3)第三步也是最后一步,Swift需要知道何时将每个值传递到单元格高度。我们在这里用if/else做类似的检查。我知道你可以把代码缩短很多,但我把所有东西都打出来,这样其他人也能很容易地理解:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        let smallHeight: CGFloat = 70.0
        let expandedHeight: CGFloat = 100.0
        let ip = indexPath
        if selectedIndexPath != nil {
            if ip == selectedIndexPath! {
                return expandedHeight
            } else {
                return smallHeight
            }
        } else {
            return smallHeight
        }
    }

Now, some notes on your code which might be the cause of your problems, if the above doesn't solve it:

现在,在你的代码上做一些笔记,如果上面没有解决问题的话,这可能是你问题的原因:

var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell

I don't know if that's the problem, but self shouldn't be necessary, since you're probably putting this code in your (Custom)TableViewController. Also, instead of specifying your variable type, you can trust Swift's inference if you correctly force-cast the cell from the dequeue. That force casting is the as! in the code below:

我不知道这是不是问题所在,但是self不应该是必要的,因为你可能把这些代码放到你的(自定义的)TableViewController中。此外,如果您正确地强制从dequeue强制转换单元格,您可以信任Swift的推断,而不是指定您的变量类型。那个强制施法是a !下面的代码:

var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell

However, you ABSOLUTELY need to set that identifier. Go to your storyboard, select the tableView that has the cell you need, for the subclass of TableViewCell you need (probably CustomTransactionTableViewCell, in your case). Now select the cell in the TableView (check that you selected the right element. It's best to open the document outline via Editor > Show Document Outline). With the cell selected, go to the Attributes Inspector on the right and type in the Identifier name.

但是,您绝对需要设置该标识符。转到storyboard,选择具有所需单元格的tableView,用于需要的TableViewCell的子类(在您的例子中,可能是CustomTransactionTableViewCell)。现在在TableView中选择单元格(检查是否选择了正确的元素)。最好通过编辑器>显示文档大纲来打开文档大纲)。选择单元格后,转到右边的属性检查器并输入标识符名称。

You can also try commenting out the cell.selectionStyle = UITableViewCellSelectionStyle.None to check if that's blocking the selection in any way (this way the cells will change color when tapped if they become selected).

您还可以尝试注释单元格。selectionStyle = UITableViewCellSelectionStyle。不需要检查它是否以任何方式阻塞了选择(这样当单元格被选中时,它们将改变颜色)。

Good Luck, mate.

祝你好运,伴侣。

#3


4  

A different approach would be to push a new view controller within the navigation stack and use the transition for the expanding effect. The benefits would be SoC (separation of concerns). Example Swift 2.0 projects for both patterns.

另一种方法是在导航堆栈中推送一个新的视图控制器,并使用转换来实现扩展效果。其好处将是SoC(关注点分离)。这两种模式都采用Swift 2.0项目。

#4


1  

I suggest solving this with modyfing height layout constraint

我建议用模板高度布局约束来解决这个问题

class ExpandableCell: UITableViewCell {

@IBOutlet weak var img: UIImageView!


@IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!


var isExpanded:Bool = false
    {
    didSet
    {
        if !isExpanded {
            self.imgHeightConstraint.constant = 0.0

        } else {
            self.imgHeightConstraint.constant = 128.0
        }
    }
}

}

Then, inside ViewController:

然后,在ViewController:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.delegate = self
    self.tableView.dataSource = self
    self.tableView.estimatedRowHeight = 2.0
    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.tableFooterView = UIView()
}


// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
    cell.img.image = UIImage(named: indexPath.row.description)
    cell.isExpanded = false
    return cell
}

// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }

        UIView.animate(withDuration: 0.3, animations: {
            tableView.beginUpdates()
            cell.isExpanded = !cell.isExpanded
            tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
            tableView.endUpdates()

        })

    }




func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }
    UIView.animate(withDuration: 0.3, animations: {
        tableView.beginUpdates()
        cell.isExpanded = false
        tableView.endUpdates()
    })
}
}

Full tutorial available here

完整的教程可以在这里

#5


0  

After getting the index path in didSelectRowAtIndexPath just reload the cell with following method reloadCellsAtIndexpath and in heightForRowAtIndexPathMethod check following condition

在didSelectRowAtIndexPath中获得索引路径后,只需使用以下方法reloadCellsAtIndexpath重新加载单元格,并在heightForRowAtIndexPathMethod检查以下条件

if selectedIndexPath != nil && selectedIndexPath == indexPath {    
       return yourExpandedCellHieght
}

#1


4  

The first comparison in your if statement can never be true because you're comparing an indexPath to an integer. You should also initialize the selectedRowIndex variable with a row value that can't be in the table, like -1, so nothing will be expanded when the table first loads.

if语句中的第一个比较永远不会为真,因为您正在将indexPath与整数进行比较。您还应该初始化selecteindex变量,该变量的行值不能在表中,比如-1,所以当表首次加载时,没有任何东西会被扩展。

var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if indexPath.row == selectedRowIndex.row {
        return 100
    }
    return 70
}

#2


14  

It took me quite a lot of hours to get this to work. Below is how I solved it.

我花了很长时间才把它弄出来。下面是我解决它的方法。

PS: the problem with @rdelmar's code is that he assumes you only have one section in your table, so he's only comparing the indexPath.row. If you have more than one section (or if you want to already account for expanding the code later) you should compare the whole index, like so:

PS: @rdelmar代码的问题是,他假设您的表中只有一个部分,所以他只是比较了indexPath.row。如果您有多个部分(或者您想要在以后扩展代码),您应该比较整个索引,比如:

1) You need a variable to tell which row is selected. I see you already did that, but you'll need to return the variable to a consistent "nothing selected" state (for when the user closes all cells). I believe the best way to do this is via an optional:

1)需要一个变量来判断选择了哪一行。我看到您已经这样做了,但是您需要将变量返回到一致的“无选择”状态(当用户关闭所有单元格时)。我认为最好的方法是通过一个可选的:

var selectedIndexPath: NSIndexPath? = nil

2) You need to identify when the user selects a cell. didSelectRowAtIndexPath is the obvious choice. You need to account for three possible outcomes:

2)您需要识别用户何时选择单元格。didSelectRowAtIndexPath是显而易见的选择。你需要考虑三种可能的结果:

  1. the user is tapping on a cell and another cell is expanded
  2. 用户点击一个单元格,另一个单元格被展开
  3. the user is tapping on a cell and no cell is expanded
  4. 用户点击一个单元,没有任何单元被展开
  5. the user is tapping on a cell that is already expanded
  6. 用户正在访问一个已经展开的单元格

For each case we check if the selectedIndexPath is equal to nil (no cell expanded), equal to the indexPath of the tapped row (same cell already expanded) or different from the indexPath (another cell is expanded). We adjust the selectedIndexPath accordingly. This variable will be used to check the right rowHeight for each row. You mentioned in comments that didSelectRowAtIndexPath "didn't seem to be called". Are you using a println() and checking the console to see if it was called? I included one in the code below.

对于每种情况,我们检查selectedIndexPath是否等于nil(没有展开单元格),是否等于已展开的行的indexPath(已展开的相同单元格),或者与indexPath(已展开的另一个单元格)不同。我们相应地调整selectedIndexPath。这个变量将用于为每一行检查正确的rowh8。在didSelectRowAtIndexPath的注释中,您提到“似乎没有调用”。您是否正在使用println()并检查控制台以查看它是否被调用?我在下面的代码中包含了一个。

PS: this doesn't work using tableView.rowHeight because, apparently, rowHeight is checked only once by Swift before updating ALL rows in the tableView.

PS:使用tableView是不行的。rowh8因为显然,rowh8在更新tableView中的所有行之前只被Swift检查过一次。

Last but not least, I use reloadRowsAtIndexPath to reload only the needed rows. But, also, because I know it will redraw the table, relayout when necessary and even animate the changes. Note the [indexPath] is between brackets because this method asks for an Array of NSIndexPath:

最后,我使用reloadRowsAtIndexPath只重载需要的行。但是,同时,因为我知道它会重新画桌子,在必要的时候放松,甚至会使变化有生气。注意,[indexPath]位于括号之间,因为此方法需要一个NSIndexPath数组:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("didSelectRowAtIndexPath was called")
        var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
        switch selectedIndexPath {
        case nil:
            selectedIndexPath = indexPath
        default:
            if selectedIndexPath! == indexPath {
                selectedIndexPath = nil
            } else {
                selectedIndexPath = indexPath
            }
        }
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}

3) Third and final step, Swift needs to know when to pass each value to the cell height. We do a similar check here, with if/else. I know you can made the code much shorter, but I'm typing everything out so other people can understand it easily, too:

3)第三步也是最后一步,Swift需要知道何时将每个值传递到单元格高度。我们在这里用if/else做类似的检查。我知道你可以把代码缩短很多,但我把所有东西都打出来,这样其他人也能很容易地理解:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        let smallHeight: CGFloat = 70.0
        let expandedHeight: CGFloat = 100.0
        let ip = indexPath
        if selectedIndexPath != nil {
            if ip == selectedIndexPath! {
                return expandedHeight
            } else {
                return smallHeight
            }
        } else {
            return smallHeight
        }
    }

Now, some notes on your code which might be the cause of your problems, if the above doesn't solve it:

现在,在你的代码上做一些笔记,如果上面没有解决问题的话,这可能是你问题的原因:

var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell

I don't know if that's the problem, but self shouldn't be necessary, since you're probably putting this code in your (Custom)TableViewController. Also, instead of specifying your variable type, you can trust Swift's inference if you correctly force-cast the cell from the dequeue. That force casting is the as! in the code below:

我不知道这是不是问题所在,但是self不应该是必要的,因为你可能把这些代码放到你的(自定义的)TableViewController中。此外,如果您正确地强制从dequeue强制转换单元格,您可以信任Swift的推断,而不是指定您的变量类型。那个强制施法是a !下面的代码:

var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell

However, you ABSOLUTELY need to set that identifier. Go to your storyboard, select the tableView that has the cell you need, for the subclass of TableViewCell you need (probably CustomTransactionTableViewCell, in your case). Now select the cell in the TableView (check that you selected the right element. It's best to open the document outline via Editor > Show Document Outline). With the cell selected, go to the Attributes Inspector on the right and type in the Identifier name.

但是,您绝对需要设置该标识符。转到storyboard,选择具有所需单元格的tableView,用于需要的TableViewCell的子类(在您的例子中,可能是CustomTransactionTableViewCell)。现在在TableView中选择单元格(检查是否选择了正确的元素)。最好通过编辑器>显示文档大纲来打开文档大纲)。选择单元格后,转到右边的属性检查器并输入标识符名称。

You can also try commenting out the cell.selectionStyle = UITableViewCellSelectionStyle.None to check if that's blocking the selection in any way (this way the cells will change color when tapped if they become selected).

您还可以尝试注释单元格。selectionStyle = UITableViewCellSelectionStyle。不需要检查它是否以任何方式阻塞了选择(这样当单元格被选中时,它们将改变颜色)。

Good Luck, mate.

祝你好运,伴侣。

#3


4  

A different approach would be to push a new view controller within the navigation stack and use the transition for the expanding effect. The benefits would be SoC (separation of concerns). Example Swift 2.0 projects for both patterns.

另一种方法是在导航堆栈中推送一个新的视图控制器,并使用转换来实现扩展效果。其好处将是SoC(关注点分离)。这两种模式都采用Swift 2.0项目。

#4


1  

I suggest solving this with modyfing height layout constraint

我建议用模板高度布局约束来解决这个问题

class ExpandableCell: UITableViewCell {

@IBOutlet weak var img: UIImageView!


@IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!


var isExpanded:Bool = false
    {
    didSet
    {
        if !isExpanded {
            self.imgHeightConstraint.constant = 0.0

        } else {
            self.imgHeightConstraint.constant = 128.0
        }
    }
}

}

Then, inside ViewController:

然后,在ViewController:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.delegate = self
    self.tableView.dataSource = self
    self.tableView.estimatedRowHeight = 2.0
    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.tableFooterView = UIView()
}


// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
    cell.img.image = UIImage(named: indexPath.row.description)
    cell.isExpanded = false
    return cell
}

// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }

        UIView.animate(withDuration: 0.3, animations: {
            tableView.beginUpdates()
            cell.isExpanded = !cell.isExpanded
            tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
            tableView.endUpdates()

        })

    }




func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }
    UIView.animate(withDuration: 0.3, animations: {
        tableView.beginUpdates()
        cell.isExpanded = false
        tableView.endUpdates()
    })
}
}

Full tutorial available here

完整的教程可以在这里

#5


0  

After getting the index path in didSelectRowAtIndexPath just reload the cell with following method reloadCellsAtIndexpath and in heightForRowAtIndexPathMethod check following condition

在didSelectRowAtIndexPath中获得索引路径后,只需使用以下方法reloadCellsAtIndexpath重新加载单元格,并在heightForRowAtIndexPathMethod检查以下条件

if selectedIndexPath != nil && selectedIndexPath == indexPath {    
       return yourExpandedCellHieght
}