I'm working on a instant messaging app. The user can send text messages, for which I'm using a UITextView inside the custom tableview cells. The user should be able to send all kinds of multimedia data, such as images, documents and videos.
Inside my table view cell I have both a textView and a UIImageView in a stackView and I can send the respective kinds of data. If there is no text, I hide the TextView. If there is no Image, I hide the image.
The problem is: to scale the app, I'd have to add a new container for documents, another one for videos, another one for audio files and hide all the other containers that have no data added to it. It's a garbage solution, but it was the only one that I found.
Knowing from the backend what kind of data is sent, how could I programatically add a container view in which I make the setup on the spot? I was thinking of having a textView as default and an empty View and inside cellForRowAt just add the needed elements.
I would try to create several cells which you dequeue in cellForRowAt by checking for a criteria, e.g.:
if userPost.images != nil {
let cell = tableView.dequeueReusableCell(withReuseIdentifier: "Identifier1", for: indexPath) as! UITableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withReuseIdentifier: "Identifier2", for: indexPath) as! UITableViewCell
return cell
}
With this solution, you can create a cell that nicely fits the data a user sent another user. Just check for any criteria you want and return the according cell.
Hope it helped!
Related
I have created a single prototype cell which has two labels (mainLabel and subLabel) and an uiimageview. In the uitableview I'd like to have several cells which reuse the prototype and when needed the subLabel is hidden and the uiimageview is changed with different one or with a uiswitch. The two labels have different text for each cell. Do you have any suggestions/hints in order to do it? possibly in a mvvm architecture?
I'll describe what I am doing:
I have a struct (the Model) with two properties: label and sublabel. This is then instantiate by a viewModel which provides text for each cell, done by a method called getModel(_ indexPath: IndexPath) -> cellModel { ... }. Finally in UIViewController, in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) { ... } I am calling getModel(), using dequeueReusableCell and setting up each cell.
In getModel() there is a huuuuge switch which I use to know which cell is which
Then in uitableviewcell I have some method that hides sublabel and changes uiimageview.
It kind of works, however I have some issues with while scrolling. For example, sometimes a uiimageview is drawn in another cell, or a subLabel is hidden, even if it is not supposed to. I guess this is due because it is reusing the cell, and I am not resetting it.
Anyway, any suggestions or ideas?
I know this is overkilling...
No need for any pattern. Yes, you can use that single cell design for all cells. Just hide/empty label(s) and image view as you like per cell.
First of all you have to set default value to both the labels and imageview
i.e. (consider a title label, a sub label and a imageview)
lblTitle.isHidden = false
lblSubLabel.isHidden = false
imgViewIcon.image = nil
Then just show labels in specific condition that you want to match and set image in imageview
i.e. (consider your condition to hide sub label)
if needToHide == true {
lblSubLabel.isHidden = true
}
I am trying to have two buttons as the first row and the rest of the rows as it is shown in the following picture (I apologize for the flipped picture):
I know how to put two buttons in a cell, like so:
And use the following code to add the first cell to my tableview like so:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//Configure the first cell with the two buttons
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = cathyList[indexPath.row]
}
return cell
}
My logic allows me to have two buttons as the first row cell but I don't know how to make the buttons big and square like the ones that are shown here. Any suggestions?
You format your button as what you want on Storyboard. I suggest 2 options you can get this:
You can add image and title on button and use Edge property to achieve this. Look at this:
This way is hard to achieve what you need, you have to make sure your icon has the right size, and it's not dynamic.
The second way is easier and more dynamic. But maybe not the best solution. I usually use this way.
You add an image (show your icon), a label (your title) and embed them in a view. Then add a button stretch all the view. Connect this button to action and use it as usual.
Hope this help.
Screenshot of weird behavior
The screenshot tells is quite well. I have a tableview with dynamic custom cells. I added a println for one of the contents of the cell to check, if the labels are set. I can see in the debug log, that each cell has its content. Still, on the device there are empty cells at random, which means, the row, where no content appears, is changing a lot. Even just scrolling up and down makes the second row disappear, but the third row is filled. Scrolling again turns this around again. If I close the app and start it again, every row is filled correctly.
Here is the code for the cell generation:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Return a count picker cell
if countPickerTableRow == indexPath.row {
...
}
// Return a normal wish list entry cell
else {
let article = wishListEntries[indexPath.row]!
let cell = tableView.dequeueReusableCellWithIdentifier("ArticleCell", forIndexPath: indexPath) as! WOSArticleCell
// Correct the order in case a count picker cell was inserted
var row = indexPath.row
if countPickerTableRow != -1 && indexPath.row > countPickerTableRow {
row--
}
cell.setThePreviewImage(UIImage(data: article.thumbnail))
cell.setArticleName(article.name)
cell.setArticleDescription(article.text)
cell.setArticleNumber(article.number)
cell.setArticleCount(article.count as Int)
cell.setOrderInTable(row)
cell.setTableViewController(self)
cell.setNeedsDisplay()
cell.setInputAccessoryView(numberToolbar) // do it for every relevant textfield if there are more than one
println(String(indexPath.row) + " " + cell.nameLabel.text!)
return cell
}
}
In the custom cell class there is nothing special. Just a few outlets to the labels.
Here is a screen of the storyboard:
Storyboard
Can anyone please help me finding out what is going on here? I can't find the reason why the debug log can output the contents of a cell, but the device is not able to render them.
You should change the logic of your code. If the PickerCell comes up just call reloadData() and reload everything in the tableview. If the amount of rows you have is small this won’t be an issue and it’s not an expensive operation as you are not doing any heavy calculating during display.
If you need to update only a single cell because of changes you made in the PickerCell then you should be calling reloadRowsAtIndexPaths:withRowAnimation: with the indexPath of the cell to be updated.
Your issue is with your subclass WOSArticleCell. Have you implemented prepareForUse()? If you have, are you setting any properties to nil?
UITableViewCell Class Reference
Discussion
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Working on a social iPhone app using Swift (with a Storyboard) and Parse where users can create posts and comment on posts similar to the Facebook iOS app and other social network apps.
The app has an initial, master Home Feed page (which displays user posts) and a detail Reply page (which displays user comments). Both use the PFTableViewController class and each of those table views have their own PFTableViewCell implemented in separate swift files as the prototype cells.
I am trying to send the content (username, post message, timestamp, etc) of a selected post cell from the master Home Feed page to the detail Reply page's topmost cell (the first cell at the top of the table view).
My goal is to have it work similar to the way the Facebook, Instagram, and Twitter iOS apps have it - whenever a user selects a post in the main feed/timeline, the detail view is shown where the main post is at the top (but not pinned to the top - meaning it will scroll up/down with the tableview) and its comments are in the cells beneath it.
I appreciate your help and time! Thank you!
On Selection on left panel, you can make tableview on right panel scroll to top animated false.
After further research, one way to "hack" it is by doing something similar to the following:
1) Create a custom tableview cell for the top row (topmost cell) by implementing it in a separate Swift file, just as you may have done for the prototype cell.
2) Now you have two custom cells. So implement two function where in each you customize them the way that serves your apps requirements, etc and the cell.
3) In the cellForRowAtIndexPath method, use an if/else statement to return the appropriate tableview cell for row 0 (topmost/header cell), otherwise return the other prototype cell for any other row (i.e.: the cells beneath the header cell):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
return headerCellAtIndexPath()
} else {
return commentCellAtIndexPath(indexPath)
}
}
func headerCellAtIndexPath() -> CommentsHeaderTableViewCell {
let headerCell = tableView.dequeueReusableCellWithIdentifier("topCell") as! CommentsHeaderTableViewCell
// Customize your headerCell...
return headerCell
}
func commentCellAtIndexPath(indexPath:NSIndexPath ) -> CommentTableViewCell {
let commentCell = tableView.dequeueReusableCellWithIdentifier("commentCell", forIndexPath: indexPath) as! CommentTableViewCell
// Customize your commentCell...
return commentCell
}
I'm following tutorial from raywenderlich (How To Make A Swipeable Table View Cell With Actions) site, that shows you how to create custom cell with layers and delegate.
Now I got everything working correctly, buy I would like one of my cells to close if other cell open, how can I achieve this? or Like Messenger app, don't allow users to open another cell option unless they close the current one.
I can't wrap my head around this, I see few other people also ask the same question in comments, but no one reply.
Anyone with Objective-C knowledge, it's okay, I can translate it to swift myself.
The reason I'm using this tutorial, is because the Apple API doesn't allow custom button (using PaintCode) to be used as Action button.
I find a very simple solution for anyone else who trying to achieve the same method.
Create a Method - closeOtherCells:
Since we store all cells in NSMutableSet we can see which ones are available for closing.
func closeOtherCells(close close: Bool){
if close{
//Check For Available Cell
for cells in cellsCurrentEditing {
//Get Table Cells at indexPath
var cellToClose: CustomTableViewCell = self.tableView.cellForRowAtIndexPath(cells as! NSIndexPath) as! CustomTableViewCell
//Call Reset Method in our Custom Cell.
cellToClose.resetConstraintContstantsToZero(true, notifyDelegateDidClose: true)
}
}
}
Now Simply in your cellDidOpen: delegate method call closeOtherCells: with true parameter.
func cellDidOpen(cell: UITableViewCell) {
//Close Other Cells
closeOtherCells(close: true)
//Store Open Cell in NSMutableSet
let indexPath: NSIndexPath = self.tableView.indexPathForCell(cell)!
self.cellsCurrentEditing.addObject(indexPath)
}
I hope this help others. :)