I managed a way to animate my cells. I created a global variable named rowHeight in type of CGFloat and implicitly assigned it as 40. I wrote this code down:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return rowHeight
}
In the touch event:
#IBAction func lessonCollapser(sender: AnyObject) {
if rowHeight == 40 {
self.tableView.beginUpdates()
UIView.animateWithDuration(1, animations: {self.rowHeight = 0})
self.tableView.endUpdates()
} else if rowHeight == 0 {
self.tableView.beginUpdates()
UIView.animateWithDuration(1, animations: {self.rowHeight = 40})
self.tableView.endUpdates()
}
}
So far so good. Even though it is being animated, there are a few problems.
First: My custom headers disappear.
Second: I get an error: "no index path for table cell being reused". I think because there are no index paths, the headers are disappeared. That is expected. What is not expected is that, why my tables' index paths disappeared when I used the animation? I think that is because of the beginUpdates() and endUpdates(). Can someone advice me something to do?
The code for my header view:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! lessonHeaderTableViewCell
switch (section) {
default:
headerCell.lessonHeader.setTitle("TEST HEADER", forState: .Normal)
}
return headerCell
}
What happens on the code above is this: I created a prototype cell with "headerCell" identifier. I threw a button inside it. I created a swift file named "lessonHeaderTableViewCell. I assigned it as class of the prototype cell so it could control it. Lastly, I created an outlet named "lessonHeader" for the button, inside the swift file that I created.
Related
I am trying to make a expandable table view (static cells). A container view is placed inside a cell below the "Lists of options" cell, as shown below:
The problem comes with the animation when expanding / collapsing. The topmost cell of each section will disappear briefly(hidden?), then reappear after the animation ends. I have later experimented and the results are as followed:
This will not happen when using tableView.reloadData().
This will happen when using tableView.beginUpdates() / endUpdates() pairs.
Hence I have come to the conclusion that this has sth to do with the animation. Below is my code and pictures for further explanation for said issue.
Code for hiding / showing
private func hideContainerView(targetView: UIView) {
if targetView == violationOptionsContainerView {
violationOptionContainerViewVisible = false
}
tableView.beginUpdates()
tableView.endUpdates()
UIView.animate(withDuration: 0.1, animations: { targetView.alpha = 0.0 }) { _ in
targetView.isHidden = true
}
}
private func showContainerView(targetView: UIView) {
if targetView == violationOptionsContainerView {
violationOptionContainerViewVisible = true
}
tableView.beginUpdates()
tableView.endUpdates()
targetView.alpha = 0.0
UIView.animate(withDuration: 0.1, animations: { targetView.alpha = 1.0 }) { _ in
targetView.isHidden = false
}
}
Table view
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
switch cell {
case violationTableViewCell:
if violationOptionContainerViewVisible {
hideContainerView(targetView: violationOptionsContainerView)
} else {
showContainerView(targetView: violationOptionsContainerView)
}
default: break
}
// tableView.reloadData()
// if we use this instead of begin/end updates, the cells won't disappear. But then we'll be ditching animation too.
}
tableView.deselectRow(at: indexPath, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 3 {
if !violationOptionContainerViewVisible {
return 0.0
}
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
Images
Note that the first cell in each section ("FFFF" and the "slider bar") disappears and reappears during the animation
After searching for a few days, I have found the cause to this behavior.
I have, for some reason, set the cells layer.zposition below default zero. This will cause the cell to be seen "below" it's background view (hence disappeared) during the animation even though it's a subview of it.
Adjusting the value back to 0 or higher will remove this issue.
I'm using a static table view which contains 3 different cells. And when a button in the first cell is tapped, the height of the first cell should increase. Below is the function called when the button is tapped.
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
And in the table view's delegate heightForRowAt()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
let expandedHeight: CGFloat = 415
let collapsedHeight: CGFloat = 115
if self.shouldShowCameraPreview {
return expandedHeight
}
return collapsedHeight
} else if indexPath.row == 2 {
// If already premium, dont show purchases cell.
if AGTUserDefaultValues.isUserPremium {
return 0
}
// Last cell should be same height as the table view
return self.tableView.frame.height - (self.navigationController?.navigationBar.frame.height ?? 0)
- min(UIApplication.shared.statusBarFrame.height, UIApplication.shared.statusBarFrame.width)
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
I've verified that heightForRowAt() IS getting called, and it is working fine for the other cell heights when toggleExpandCamera() is called. It's just that the first cell that is behaving quite weird. It seems like it disappeared or something. I've attached screenshots down below, before and after expanding.
On further inspection, it looks like the cell still exist, but still has the same height. The only difference is there's now more space between the two cells. I also found out the alpha value of the cell is 0.
UPDATE
I've tried created a new project, with only the tableview and the function to expand the cell, and still on that project, the same thing happened. If anyone is curious to help I've uploaded the project here.
I've tried your project and found that changing :
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
into :
self.tableView.reloadData()
Works as expected.
I figured out the answer. In my toggleExpandCamera() function instead of reloading the table view, I replaced that with:
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
And the cell height animates as expected.
I can't see suggestions when typing.. i have tableview cell and textfield in it.
I'm using MPGTextField library, swift version(swift 2 supported).
Any solution for this?
Code:
#IBOutlet weak var articleField: MPGTextField_Swift!
override func viewDidLoad() {
super.viewDidLoad()
articleField.mDelegate = self
}
func dataForPopoverInTextField(textfield: MPGTextField_Swift) -> [Dictionary<String, AnyObject>] {
return articles
}
func textFieldShouldSelect(textField: MPGTextField_Swift) -> Bool{
return true
}
func textFieldDidEndEditing(textField: MPGTextField_Swift, withSelection data: Dictionary<String,AnyObject>){
print(data["CustomObject"])
}
In the MPGTextField-Swift.swift you'll find a function provideSuggestions()
In this function you'll find a line
self.superview!.addSubview(tableViewController!.tableView)
Replace this line with
//BUG FIX - SHOW ON TOP
//self.superview!.addSubview(tableViewController!.tableView)
let aView = tableViewController!.tableView
var frame = aView.frame
frame.origin = self.superview!.convertPoint(frame.origin, toView: nil)
aView.frame = frame
self.window!.addSubview(aView)
////
I've forked MPGTextField repository, made necessary changes for demo purpose.
You can find my repo at https://github.com/rishi420/MPGTextField
Note: This repo needs Xcode 7.1.1 to compile. Feel free to contribute. :-]
I can't really tell what's going on. Is the autocomplete box showing but just cut off at the bottom of the tableViewCell? If so, try setting clipsToBounds to false on the tableViewCell, and maybe even its content view too.
Touch events are not by default recognized for areas outside of a view's frame. To route the taps to the suggestion, you'll have to subclass the tableViewCell and override hitTest
A solution for this would be set the cell height when the cell is selected:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if tableView.indexPathForSelectedRow == indexPath {
return self.selectedCellHeight
}
return self.unSelectedCellHeight
}
You only have to set the unselectedCellHeight and the selectedCellHeight
The beginUpdates() and endUpdates() will update the cell height and animate it.
I have a tableView, designed in storyboard, that mimics a chat UI. A cell consists of:
A TextView for the message text
A Profile Image of the sender
Right now, the profile image is displayed in every cell, next to the text bubble. This is fine, but if the same users send two or more messages directly after the other, the profile image should only appear on the last bubble and not on the previous one.
I tried calling cellForRowAtIndexPath to get the previous cell's properties and change the hidden property of the profile image, but this gave me two problems:
I'm calling cellForRowAtIndexPath inside cellForRowAtIndexPath, because that's where I make the cell UI and decide wether the profile image has to be hidden or not. I don't think it's a good idea to call this Method inside itself.
Sometimes (when scrolling up and down very fast) this does not work properly.
I also tried to store all the cells in an dictionary (indexPath.row: Cell), so I can access it faster later, but this gave me the same problem namely that it does not work when scrolling up and down really fast.
This is an illustration of how it should be: http://tinypic.com/view.php?pic=2qavj9w&s=8#.Vfcpi7yJfzI
You need to both look ahead inside of your cellForRowAtIndexPath method and, as Paulw11 recommended, call reloadRowsAtIndexPaths after inserting the cell:
import UIKit
struct MyMessage {
let sender: String
let text: String
}
class MyTableViewCell: UITableViewCell {
var message: MyMessage?
var showProfileImage: Bool = false
}
class MyTableViewController: UITableViewController {
private var _messages: [MyMessage] = []
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self._messages.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = self._messages[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyTableViewCell
cell.message = message
if self._messages.count > indexPath.row + 1 {
let nextMessage = self._messages[indexPath.row + 1]
cell.showProfileImage = message.sender != nextMessage.sender
} else {
cell.showProfileImage = true
}
return cell
}
func addMessage(message: MyMessage) {
let lastIndexPath = NSIndexPath(forRow: self._messages.count - 1, inSection: 0)
let indexPath = NSIndexPath(forRow: self._messages.count, inSection: 0)
self._messages.append(message)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
self.tableView.reloadRowsAtIndexPaths([lastIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
}
I have a tableview need to be updated very second. The code are as following. I design the headerview to have a dropdown function, when the header tap the rest are displayed. The code will crashes when I am trying to tap the header, the thread stops, xcode is not giving any hint on how and why.
func didListOfBLEDevicesUpdate(newDevice: BLEDevice)
{
println("receivedDevice from scanner every second: \(newDevice.deviceName)")
self.deviceTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return BLEDevice.listOfDevices.items.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1;
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return BLEDevice.listOfDevices.items[section].deviceName
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(IsExpandedMode[indexPath.section] == true){
return 400
}
return 70;
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.grayColor()
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
headerString.text = BLEDevice.listOfDevices.items[section].deviceName
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
println("Tapping working")
println(recognizer.view?.tag)
var indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
var collapsed = self.IsExpandedMode [indexPath.section]
collapsed = !collapsed;
self.IsExpandedMode[indexPath.section] = collapsed
//reload specific section animated
var range = NSMakeRange(indexPath.section, 1)
var sectionToReload = NSIndexSet(indexesInRange: range)
self.deviceTableView.reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : DeviceTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! DeviceTableViewCell
let row = indexPath.row
cell.deviceName!.text = BLEDevice.listOfDevices.items[row].deviceName
cell.connectionStatus.text = BLEDevice.listOfDevices.items[row].connectionStatus
cell.deviceSignalStrengthen.text = BLEDevice.listOfDevices.items[row].RSSI
cell.manufacturerData.text = BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataManufacturerData
cell.serviceUUID.text = BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataServiceUUIDs
cell.serviceData.text = DataConvertHelper.getNSDictionary(BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataServiceData)
cell.TxPowerLevel.text = BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataTxPowerLevel
cell.IsConnectable.text = DataConvertHelper.getBool(BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataIsConnectable)
cell.solicitedServiceUUID.text = BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataSolicitedServiceUUIDs
cell.shortenedLocalName.text = BLEDevice.listOfDevices.items[row].advertisementPackage.cBAdvertisementDataLocalName
return cell
}
Use reload sections and reload rows rather than reloading data
The method you have used to handle the table seems to be rather complex. An alternative would be as follows:
1) Assumption from you code is that each device is associated with a section. As noted in the comments, your cellForRorAtIndexPath method seems to be using [row] to index your data model, but the model is based on [section] as you always return the number of rows as 1 for every section and the number of sections is the number of devices.
2) Rather than using a header view for each section and having to add gesture recognizers, simply create a custom cell to represent the device and make this row 0 of the section.
3) So each device is associated with a section, and row 0 of each section is the header information cell, NOT a header view. Make the header view nil. You can use a header height to leave a gap between sections.
4) Add code to detect selection of cells. When the cell row is 0, its the header cell. If the device is collapsed, set it to be expanded and vice versa and reload the section.
5) Make a new custom cell for you dropdown information. this will be row 1 of any section which is showing information.
6) Update your number of rows in section to return 2 if expanded, or 1 if collapsed.
7) Update cellForRowAtIndexPath to return the header cell for row 0 and the detail cell for row 1. Make sure to fix the [row] indexing to be [section] indexing.
This gives you a table of device header cells, which when clicked insert a detail cell below and when clicked again remove it and no gesture recognizers needed.
You need to make sure that your data model updates are working correctly. Seems from your errors that you are not updating the data model properly: in particular removal of devices.