Swift 3 - Expandable table view cells without closing other ones - ios

I am using Swift 3.
I've been following this tutorial: https://www.youtube.com/watch?v=VWgr_wNtGPM , supplemented by this answer on StackOverflow.
However, the way that this works is that if I click on a cell, it expands while hiding other cells. How do I make it such that when I expand it, the other already-expanded cells stay expanded?

The best approach I suggest you for achieving this in an elegant way is implementing it through UIStackView elements.
Take a look this post http://www.atomicbird.com/blog/uistackview-table-cells

if you wanna do this yourself, you could try this way.
first step is you should create a model list just like:
var cellsData: [CustomData] = [];
the CustomData seem like:
class CustomData {
var isExpanded: Bool = false;
// whatever other variables
}
then your custom cell should whatever look like but you must do something in the tableView:didSelectItemAt like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row;
self.cellsData[row].isExpanded = !self.cellsData[row].isExpanded;
self.tableView.reloadRows(at: [indexPath], with: .none); // or the other animations
}
then in the "tableView:cellForRowAt" seems like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell;
if(cell.isExpanded){
// do something when the cell is expanded
}else{
// do something when the cell is not expanded
}
}
remember, the cell is reusable, means if you have used the cell more than one time, then the cell will keep the state when it was used the last time.

You can use ExpyTableView, which makes an expandable section from your given header cell. Compatible down to iOS 8.0.
All you have to do is to import ExpyTableView and then:
class ViewController: ExpyTableViewDataSource, ExpyTableViewDelegate {
#IBOutlet weak var expandableTableView: ExpyTableView!
// First, set data source and delegate for your table view.
override func viewDidLoad() {
super.viewDidLoad()
expandableTableView.dataSource = self
expandableTableView.delegate = self
}
// Then return your expandable cell instance from expandingCell data source method.
func expandableCell(forSection section: Int, inTableView tableView: ExpyTableView) -> UITableViewCell {
// this cell will be displayed at IndexPath with section: section and row 0
}
}
You can see your former table view section is now an expandable table view section. You can also download the example project and see more detailed examples.

Related

Always keep cell on the top of uitableview

I have an application that is viewbased and I am adding a uitableview as a subview to the main view. The uitableview is full with class Category cells. Everything works fine, but I want to have “Quick notes” Category always on the top of the uitableview. This means when I reloadData() in the Array, “Quick Notes” is always with index 0 and it goes on the bottom of the uitableview. And when I create new cell I need it to go under the “Quick Notes” section.
Please help me, what code I need to achieve that functionality and where I need to put it. Thanks!
Edit:
Thats where I am adding "Quick Notes" to the Realm database.
private let categories = try! Realm()
private init() {
if categories.objects(Category.self).isEmpty {
createCategoryWith(title: "Quick Notes", color: "#FF0000", icon: "quickNotes")
}
}
Update the array in the ViewController:
func didCreateCategory(category: Category) {
RealmHandler.shared.createCategoryWith(title: category.title, color: category.color, icon: category.icon)
self.categories = RealmHandler.shared.getAllCategories()
tableView.reloadData()
}
DataSourceDelegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath)
cell.textLabel?.text = categories[categories.count - (1+indexPath.row)].getTitle()
cell.contentView.backgroundColor = hexStringToUIColor(hex: categories[categories.count-(1+indexPath.row)].getColor())
return cell
}
use viewForHeaderInSection delegate method of tableView.
tableView(_ tableView: UITableView, viewForHeaderInSection section: Int)
here you can define your header cell.
then implement your logic that what do you want to show on this cell.
this headerCell will be always on top of your tableView.

How to access to a textLabel in a static cell

After much searching and reading I unfortunately do not come from the following. I want to use static tables to display certain data. (Are there better options?)
In my view I first put an onion picture with a container view underneath. The container view again refers to a Table View Controller.
I made an outlet from the cells and then I thought I could easily adjust the text.
Now I want to change the text of the fields in the table, but unfortunately I do not succeed.
When I start the app then the table is completely empty as seen on the screenshot.
What am I doing wrong ?
class TableViewController: UITableViewController {
var data: [String] = ["Muis", "Aap", "Koe", "Vis"]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let iets = data[indexPath.row]
cell.textLabel?.text = iets
return cell
}
}
If you want to use static cells
Forget dequeueing UITableViewCell instances and all tableview data source and delegate methods.
In Interface Builder select the table view and select Static Cells from the Content popup
Drag the amount of static cells you need into the canvas
In the view controller declare IBOutlets and connect them directly to the UI elements in the cells
You need to change your way of thinking for this one. You do not own the cells, the UITableView does. It will provide cells as it seems fit by using your implementations of UITableViewDataSource:
func numberOfSections(in: UITableView) -> Int
func tableView(UITableView, numberOfRowsInSection: Int) -> Int
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
Normally, the texts (your actual data) would be held in a list available to this data source.
Example:
var data: [String] = []
// Other functions
func numberOfSections(in: UITableView) -> Int {
return 1
}
func tableView(UITableView, numberOfRowsInSection: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER")
cell.text = data[indexPath.row]
return cell
}
Now, if you want to change this cell's text, all you have to do is update your data list and reload the data.
What I have done after a lot of testing and reading. I have create a segue to the statutable class.
if (segue.identifier == "myEmbeddedSegue") {
let childViewController = segue.destination as! hondDetialTableViewController
childViewController.hondId = hondData["hondId"]!
}
In this segue I send only the hondId, everything else i ask entities.
I'm sorry but this is not at all how UITableView works. The UITableViewCell that you define in the Xib/Storyboard within the tableview are just "models" or templates, they don't actually exists until you dequeue them.
You can read how UITableView works here: http://www.thomashanning.com/uitableview-tutorial-for-beginners/
You have to return numberOfSections > 0 if you want anything displayed in your tableview; similarly, that section has to also have numberOfRows > 0 otherwise again, nothing will be displayed (ok, maybe headers and footers if those are properly setup).
At any rate, cells are only accessible after you dequeue them. Creating an outlet in a XIB to a UITableViewCell is useless in most cases.
You can explore other options, such as UIStackView, or maybe what you need is just plain custom UIView with labels that you properly set and layout using NSLayoutConstraints. There are plenty of resources out there, this is just one I quickly Googled for you to get started: https://www.youtube.com/watch?v=de0sthle44I
Good Luck.

Dynamic height for UITableViewCell not working correctly

I'm having some height problems with my dynamic UITableViewCell (sew picture below). Some cells have the correct height and some not, and when I drag the tableView some of the cells become correct and some don't.
I'm using this code to get the cell's height to be dynamic and reloading it in viewDidAppear and viewDidLoad.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = tableView.rowHeight
As mentioned the cells are sometimes correct and sometimes not. Is there another way to do it or am I doing something wrong? I have tried many different solutions, all mentioned here as well as other suggestions both here at StackOverflow and other sites.
I appreciate all help!
Edit!
TableView
extension ChatVC: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupMessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "groupFeedCell", for: indexPath) as? GroupFeedCell else { return UITableViewCell() }
let message = groupMessages[indexPath.row]
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
cell.configureCell(name: name, content: message.content)
})
cell.layoutSubviews()
cell.layoutIfNeeded()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 66
}
}
Cell
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var contentLbl: UILabel!
func configureCell(name: String, content: String) {
self.nameLbl.text = name //email
self.contentLbl.text = content
}
For dynamic tableViewCell
1- Setup this 2 lines with an inital value for the row height to help autolayout drawing it (take it from current cell height in the nib file)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = number;
2- don't implement this function heightForRowAtIndexPath . or implement it and return this
return UITableViewDynamicHeight;
3- make sure all constraints in the cell nib file or in storyboard are hooked correctly from top to bottom.
4- in cellForRowAtIndexPath before the line retrun cell insert that
[cell layoutSubviews];
[cell layoutIfneeded];
5- Test in simulator some versions like ios 8 it's a bug also in the viewController call
[tableView LayouSubviews];
in viewdidLayoutSubViews function to re relayout again correctly
6- Make lines property of any UILabel that you want to wrap = 0 and hook it's leading and trailing constarints to superView
Your are facing this issue because the content of your label comes from an async function.
The cell uses its content to work out its height dynamically. When your async request returns it has already done its work and will not recalculate and resize.
You need to make these requests, cache/store the results and reload the cells as needed. Usually in chat there would only be a couple of users to load usernames for anyway. You could also try pre-loading this data before the chat is displayed.
You can quickly confirm this by creating an array of random usernames and messages (sample data) and adding that to the cell straight away.
I guess the problem is asynchronous function. So Should you try
Step 1: Create names array
var names: [String?] = Array<String>.init(repeating: nil, count: groupMessages.count)
Step 2: Replace
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
cell.configureCell(name: name, content: message.content)
})
By
if names[indexPath.row] == nil {
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
names[indexPath.row] = name
tableView.reloadRows(at: [indexPath], with: .automatic)
})
} else {
cell.configureCell(name: name, content: message.content)
}

Swift 3.0 Using a Table View to Show and Add Comments

I am interested in having a tableview for comments (something similar to instagram comments). So far, I have used a custom cell to set up a textView for comments in my set array, dataName. I was wondering how I could go about setting up a textfield and button on the last row of the tableview that would act as the place to input more comments. Do I need to create another customcell for this and implement this in cellForRowAt indexPath ?
var comments = ["I like this item","Where did you get this?", "I can't believe you found this!", "Hello", "Yay"]
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.commentView.text = dataName[indexPath.row]
cell.commentView.textColor = UIColor.lightGray
cell.commentView.isEditable = false
cell.commentView.isScrollEnabled = false
return cell
}
You can accomplish you desired effect by adding a view that contains a text field and a button as the footer view of the tableview. And when a new comment is added you will proceed to add the comment to the array, and reload the tableview or insertRow with animation.
You already have one cell prototype called "Cell". Just add another cell prototype called "Comment". Now you have two cell prototypes with two different identifiers. If you're on the last row, ask for the "Comment" cell prototype in your dequeue call.

Prototype UITableViewCell with other objects (UITextField, UISwitch)

I'm trying to make a UITableView that can support having different objects/elements inside it. Specifically, these elements are a UITextField and UISwitch.
The first problem:
The elements do not show up. They are placed in the prototype cell, which is then constructed inside the class I have set up. I have verified that the cell setup is working because I can change the words on each cell, but there are no elements within the cell.
Here is the code that constructs my cells right now:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 1
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "EmailCell")
return cell
}
Second problem (which might be solved along with the first):
I have no way of accessing the information in each UITextField or each UISwitch. How can I get this information from all cells that exist?
Thanks in advance for the help!
There are multiple things wrong with your code.
For custom cells you need to implement a custom UITableViewCell subclass. Here is an example:
import UIKit
class EmailCell: UITableViewCell {
#IBOutlet var customTextField: UITextField!
#IBOutlet var customSwitch: UISwitch!
}
After that, open your Storyboard and select the prototype cell. Change it's class to EmailCell in the Identity Inspector.
Also make sure to connect your ui elements to the #IBOutlets created earlier. See this StackOverflow post if you need help with #IBOutlet.
In the next step, change your tableView(_:, cellForRowAt:) implementation like this:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "EmailCell", for: indexPath) as! EmailCell
// Configure your custom ui elements however you want
cell.customTextField.text = "somestring"
cell.customSwitch.isOn = true
return cell
}
Make sure your cells have reuse identifiers and you're using
tableView.dequeueReusableCell(withIdentifier: -Your cell Id- , for: indexPath) as? -Your Cell Class-
in your cell for row at index datasource method
next you can add targets to your cell text field / switch by doing this in your cell for row at index datasource method
cell.-your switch / text field-.addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged)
and you should subclass a uitableview cell to add the property / iboutlets
class YourTableViewCell: UITableViewCell {
#IBOutlet weak var yourSwitch: UISwitch!
}

Resources