I'm trying to simulate a Chat Messages, and after insert some new cells, some of the oldest dissapear. And when I scroll appears again and disappear. I've tried all solutions that I found from here on SO but nothing works and I have not much idea frorm where error can come.
I'm not sure what code should I post to you tried to help, I will post my TableView code so maybe I'm doing something wrong or if you need anything else, just let me know.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.messagesCell.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return self.messagesCell[indexPath.row]
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let message = self.messages[indexPath.row]
if message.messageType == 2 {
output.setImageUrl(message.text)
router.navigateToGroupChatMessagesScene()
}
else {
self.view.endEditing(true)
}
}
This code is how I generate the cells everytime a new message is inserted:
func getMessageCell(withDisplayedMessage displayedMessage: GroupChatMessages.GetChatMessages.displayedChatMessage) -> GroupChatCell {
switch displayedMessage.messageType {
case 0:
if displayedMessage.sender == self.currentUser.userID {
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("senderCell") as! GroupChatCell
dispatch_async(dispatch_get_main_queue()) {
cell.configureCellText(withText: displayedMessage.text, andUtcSendTime: displayedMessage.utcSendTime)
cell.selectionStyle = UITableViewCellSelectionStyle.None
}
return cell
}
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("receiverCell") as! GroupChatCell
cell.configureCellAttributted(withText: displayedMessage.text, andSenderName: displayedMessage.senderName, andUtcSendTime: displayedMessage.utcSendTime)
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
case 1:
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("announcementCell") as! GroupChatCell
dispatch_async(dispatch_get_main_queue()) {
cell.configureInformationCell(withText: displayedMessage.text)
cell.selectionStyle = UITableViewCellSelectionStyle.None
}
return cell
case 2:
if displayedMessage.sender == self.currentUser.userID {
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("senderImageCell") as! GroupChatCell
dispatch_async(dispatch_get_main_queue()) {
cell.configureSenderImageCell(withImageUrl: displayedMessage.text, andUtcSendTime: displayedMessage.utcSendTime)
cell.selectionStyle = UITableViewCellSelectionStyle.None
}
return cell
}
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("receiverImageCell") as! GroupChatCell
dispatch_async(dispatch_get_main_queue()) {
cell.configureImageCell(withImageUrl: displayedMessage.text, andSenderName: displayedMessage.senderName, andUtcSendTime: displayedMessage.utcSendTime)
cell.selectionStyle = UITableViewCellSelectionStyle.None
}
return cell
case 10: //SpecialCaseForSendingImages
let cell = self.messagesTableView.dequeueReusableCellWithIdentifier("senderImageCell") as! GroupChatCell
dispatch_async(dispatch_get_main_queue()) {
cell.configureSenderImageCell(withImageUrl: displayedMessage.text, andUtcSendTime: displayedMessage.utcSendTime)
cell.selectionStyle = UITableViewCellSelectionStyle.None
}
return cell
default:
return GroupChatCell()
}
Hope you can help, and any further information I will provide you as fast I can! Thank you so much.
EDIT:
Where I receive a new message I add a new row with message information in this function:
func displayMessages(viewModel: GroupChatMessages.GetChatMessages.ViewModel) {
let displayedMessage = viewModel.displayedMessages
print ("i'm here!")
if let messages = displayedMessage {
self.messages = messages
self.messagesCell = []
for index in 0..<messages.count {
let cell = self.getMessageCell(withDisplayedMessage: messages[index])
self.messagesCell.append(cell)
let indexPath = NSIndexPath(forRow: index, inSection: 0)
self.messagesTableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
print ("i'm here2!")
firstTime = false
// self.scrollToLastMessage(false)
self.setVisible(hiddenTableView: false, hiddenChatLoader: true)
self.messagesLoaded = true
}
}
Get rid of the dispatch_async you should already be on the main
thread.
Keep an array of Model objects NOT an an array of cells (in
your case it looks like it should be an array of
displayedMessage).
Also remember that these cells can be reused,
so any property that you set must always be updated. In other words
every if must have an else when configuring a cell.
Hope that helps.
Related
I wanted to make when tabbaritem click call api and fill tableView. But in my case i had to use multiple cell.
up category (tab bar item) -> category (1- cell) -> products (2- cell)
up category (tab bar item)-> products (2- cell)
i have two prototype cell in tableView. First cell for show category. Second cell for show products.
When category click, i reloaded tableview then i can show products.
But when i showed products just only 1 time. My tableview unavaliable. I am sure i call reload data.
Message from debugger: Terminated due to signal 9
Here's my code,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if let orderType = selectedOrderType
{
switch orderType
{
case OrderViewHelper.OrderType.Category:
return self.categories.count;
case OrderViewHelper.OrderType.Menu:
return self.menus.count;
case OrderViewHelper.OrderType.Product:
return self.products.count;
}
}
return 0;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var returnerCell = UITableViewCell();
switch selectedOrderType!
{
case OrderViewHelper.OrderType.Category:
let cell = tableView.dequeueReusableCell(withIdentifier: ViewCellStatics.TABLE_ORDER_VIEW_CELL, for: indexPath) as! CategoryTableViewCell;
cell.lblCategoryId.text = self.categories[indexPath.row].Id;
cell.lblCategoryName.text = self.categories[indexPath.row].Name;
cell.lblCategoryId.isHidden = true;
returnerCell = cell;
break;
case OrderViewHelper.OrderType.Menu:
let cell = tableView.dequeueReusableCell(withIdentifier: ViewCellStatics.TABLE_PRODUCT_VIEW_CELL, for: indexPath) as! ProductsTableViewCell;
cell.lblPrice.text = self.menus[indexPath.row].Price;
cell.lblProductName.text = self.menus[indexPath.row].Name;
cell.lblId.text = self.menus[indexPath.row].Id;
cell.lblId.isHidden = true;
cell.lblProductName.numberOfLines = 2;
returnerCell = cell;
break;
case OrderViewHelper.OrderType.Product:
let cell = tableView.dequeueReusableCell(withIdentifier: ViewCellStatics.TABLE_PRODUCT_VIEW_CELL, for: indexPath) as! ProductsTableViewCell;
cell.lblPrice.text = self.products[indexPath.row].Price;
cell.lblProductName.text = self.products[indexPath.row].Name;
cell.lblId.text = self.products[indexPath.row].Id;
cell.lblId.isHidden = true;
cell.lblProductName.numberOfLines = 2;
cell.addButton.Model = self.products[indexPath.row];
cell.addButton.addTarget(self, action: #selector(addBasket(_:)), for: .touchUpInside);
break;
}
return returnerCell;
}
private func getCategories(upperCategoryId : String)
{
var paramaters = [WebServiceParamater]();
paramaters.append(WebServiceParamater(key: WebServiceVariableKeys.UPPER_CATEGORY, value: upperCategoryId));
WebService().GetData(action: ActionNames.CATEGORIES, paramater: paramaters)
{
(objects) in
if (objects == nil || objects?.count == 0) { return; }
self.ClearModels(type: OrderViewHelper.OrderType.Category);
DispatchQueue.main.sync
{
self.categories = JsonParser.ParseCategories(jsonArray: objects);
self.tableOrders.reloadData();
}
}
}
private func getProducts(category : CategoryModel)
{
var paramaters = [WebServiceParamater]();
paramaters.append(WebServiceParamater(key: WebServiceVariableKeys.CATEGORY, value: category.Id));
WebService().GetData(action: ActionNames.PRODUCT, paramater: paramaters)
{
(objects) in
if (objects == nil || objects?.count == 0) { return; }
self.ClearModels(type: OrderViewHelper.OrderType.Product);
DispatchQueue.main.sync
{
self.products = JsonParser.ParseProduct(jsonArray: objects);
self.tableOrders.reloadData();
}
}
}
Screenshots
Category
I clicked one category so i can see products
Reload data doesnt work.
Never call DispatchQueue.main.sync, try to DispatchQueue.main.async instead.
BTW: Make sure your methods in WebService calling asynchronously in background threads so you won't block UI while loading something.
Hope this answer will help you in understandings https://stackoverflow.com/a/44324968/4304998
When selecting multiple cells in my tabeview the cells out of view are being selected too. I understand that this is because i am reusing the cell and its maintaining its selection as i scroll down. I have found a few people with similar issues but cant translate their solutions across to resolve my issue. I have tried not dequeing a cell and just use:
let cell = NewBillSplitterItemCell()
but get:
unexpectedly found nil while unwrapping an Optional value
on the line:
cell.currentSplitters.text = splitterList
in the following code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
fetchBillItems()
let cell: NewBillSplitterItemCell = tableView.dequeueReusableCellWithIdentifier("NewBillSplitterItemCell") as! NewBillSplitterItemCell
let item = allItems[indexPath.row]
let numberOfSplitters = item.billSplitters?.count
if numberOfSplitters == 0 {
cell.currentSplitters.text = "No one is paying for this item yet."
} else {
var splitterList = "Split this item with "
let itemSplitters = item.billSplitters?.allObjects as! [BillSplitter]
for i in 0...Int((numberOfSplitters)!-1) {
if numberOfSplitters == 1 {
splitterList += "\(itemSplitters[i].name!)"
} else {
splitterList += ", \(itemSplitters[i].name!)"
}
}
cell.currentSplitters.text = splitterList
}
cell.name.text = item.name
cell.price.text = "£\(item.price!)"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark
{
cell.accessoryType = .None
selectedItems.removeAtIndex(selectedItems.indexOf(allItems[indexPath.row])!)
} else {
cell.accessoryType = .Checkmark
selectedItems.append(allItems[indexPath.row])
}
}
}
I dont quite understand what to do and any help would be great. Thanks
In addition to what #Mike said, inside of cellForRowAtIndexPath you need an additional check because cells get reused.
Something along the line
let isSelected = selectedItems[indexPath.row].selected
if isSelected{
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
Same thing inside of didSelectRowAtIndexPath you should update the data source instead of relying on the UI of your cell for that condition.
Assuming your cell is nil, you should use
let cell = tableView.dequeueReusableCellWithIdentifier("..." forIndexPath:indexPath) as! NewBillSplitterItemCell
instead of
let cell= tableView.dequeueReusableCellWithIdentifier("...") as! NewBillSplitterItemCell
This ensures that cell will never be nil.
Also, I would check if the correct identifier is being used in all of your .xib .storyboard files.
I have encountered an error in swift when attempting to create a tableview made up of custom cells dependent upon a set of conditions.
Here is my code:
var tableData: [String] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let phonenocell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier("phonecell", forIndexPath: indexPath) as! MyCustomCell
let pincell:SocialCell = self.tableView.dequeueReusableCellWithIdentifier("socialcell", forIndexPath: indexPath) as! SocialCell
let fbcell:FacebookCell = self.tableView.dequeueReusableCellWithIdentifier("facebookcell", forIndexPath: indexPath) as! FacebookCell
let snapcell:SnapchatCell = self.tableView.dequeueReusableCellWithIdentifier("snapchatcell", forIndexPath: indexPath) as! SnapchatCell
let twitcell:TwitterCell = self.tableView.dequeueReusableCellWithIdentifier("twittercell", forIndexPath: indexPath) as! TwitterCell
let instacell:InstagramCell = self.tableView.dequeueReusableCellWithIdentifier("instagramcell", forIndexPath: indexPath) as! InstagramCell
if tableData.contains("Number") {
return phonenocell
}
if tableData.contains("Social") {
return pincell
}
if tableData.contains("Facebook") {
return fbcell
}
if tableData.contains("Snapchat") {
return snapcell
}
if tableData.contains("Twitter") {
return twitcell
}
if tableData.contains("Instagram") {
return instacell
}
}
When attempting to build and run I get a build failed with the following fault:
"Missing Return in a function expected to return 'UITableViewCell'
I have been over and over my code but I honestly cannot see where I am going wrong...
Any help would be greatly appreciated!
You need to return cell for sure.
You already do in conditions, but in case none of your condition statements would success, your return call wouldn't be fired.
Appending, for example:
return phonenocell
to the end of the function, should be quick fix for your code. It ensures, that the function will return a cell (that is mandatory).
My data source is the array tableData. This is constructed on the previous view as: #IBAction func switch1Toggled(sender: UISwitch) { if mySwitch1.on { fbTextBox.text = "Selected" dataArray.append("Facebook")
And this may be the main issue:
Assuming, that you choose 'facebook' and that you reload your tableView, every row will pass the first condition as it IS contained.
You should put this in your method:
//assuming your data source contains multiple members, and your numberOfRowsInSections... method return tableData.count, you need to get each item for each row:
let currentTag = tableData[indexPath.row]
if (currentTag == "something") { //e.g. Facebook
let somethingcell:MySomethingCell = ...
self.tableView.dequeueReusableCellWithIdentifier("somethingcell", forIndexPath: indexPath) as! MySomethingCell
return somethingcell
} else if {
...
}
return emptycell //this line is just for the case, when no of your conditions will pass and you don't catch all the situations...
maybe your array elements doesn't match the condition, it's better to return default value instead of ur conditions failed
I am not able to wrap my head around the implementation of sections in cellForRowAtIndexPath.
I have a UITableView in which I would like to show 2 sections.
Incoming Friend Requests
Friends
In Storyboard, I change my UITableView Style to Grouped.
Next, I would like there to be no Friend Request section if there are no friend requests. In viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
(...)
if friendRequests.isEmpty {
friendsDataSource = friends
} else {
friendsDataSource = [friendRequests, friends]
}
}
The rest:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return friendsDataSource.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendsDataSource[section].count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let friendRequest = friendsDataSource[0][indexPath.row]
let friend = friendsDataSource[1][indexPath.row]
if let cell = tableView.dequeueReusableCellWithIdentifier("FriendCell") as? FriendCell {
cell.configureProfileCell(userProfile)
return cell
} else {
return FriendCell()
}
}
I know my cellForRowAtIndexPath is disgusting but I have absolutely no idea how to implement it.
Any help in the right direction, greatly appreciated
Discovered if (indexPath.section == 0), and I just hacked around that.
My eyes hurt looking at this so Please post better ways of doing this. For now:
var friendRequests = [FriendRequest]()
var friends = [UserProfile]()
var friendsDataSource = []
override func viewDidLoad() {
super.viewDidLoad()
friends = FriendManager.instance.myFriends
friendRequests = FriendManager.instance.incomingFriendRequests
if friendRequests.isEmpty {
friendsDataSource = [friends]
} else {
friendsDataSource = [friendRequests, friends]
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return friendsDataSource.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendsDataSource[section].count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("FriendCell", forIndexPath: indexPath) as? FriendCell {
if friendRequests.isEmpty {
let friendCell = friends[indexPath.row]
cell.configureProfileCell(friendCell)
} else {
if (indexPath.section == 0) {
let friendRequestCell = friendRequests[indexPath.row]
cell.configureRequestCell(friendRequestCell)
} else if (indexPath.section == 1) {
let friendCell = friends[indexPath.row]
cell.configureProfileCell(friendCell)
}
}
return cell
} else {
return FriendCell()
}
}
You should use the other, newer dequeueing method: dequeReusableCellWithIdentifier(_:forIndexPath:) instead (passing the actual index path).
That one is guaranteed to always succeed, so you can do without this if/else structure:
if let cell = ... {
...
return cell
}
else {
return FriendCell()
}
By the way, you are returning the FriendCell instance fresh, without configuring it. Is that what you really want?
Clarification
The method dequeReusableCellWithIdentifier(:) succeeds only if there is one or more cells with the specified identifier already enqueued for reuse; the first few times you call it it will return nil and you need to fallback to instantiating a new cell (with the same identifier), for immediate use (and later reuse):
func tableView(tableView:UITableView, cellForRowAtIndexPath:NSIndexPath) -> UITableViewCell
{
if let cell = tableView.dequeReusableCellWithIdentifier("Identifier") as? FriendCell {
// Successfully dequeued for reuse;
// configure it:
// (set labels' texts, etc.)
return cell
}
else{
// No cell enqueued; create anew
let cell = FriendCell(style:.Plain, reuseIdentifier:"Identifier")
// configure it
// (set labels' texts, etc.)
return cell
}
}
...But because this check is a pain, Apple added a new method:
dequeReusableCellWithIdentifier(identifier:String, forIndexPath:NSIndexPath)
that internally performs the dequeueing and also initializes a new cell if no one is available. This eliminates the need for an else path in the code above, and it gets smarter:
func tableView(tableView:UITableView, cellForRowAtIndexPath:NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeReusableCellWithIdentifier("Identifier", forIndexPath:indexPath) as! FriendCell
// (Never fails - provided identifier is right and class is registered for it)
// configure it:
// (set labels' texts, etc.)
return cell
}
So I have a Segmented Control that switches between 2 TableViews & 1 MapView inside a MainVC.
I'm able to switch the views in the simulator by adding an IBAction func changedInSegmentedControl to switch which views are hidden while one of them is active.
I created 2 custom TableViewCells with XIBs. I also added tags with each TableView.
My question is how do I add them in cellForRowAtIndexPath?
Currently, my code is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
// var cell: UITableViewCell
if (tableView.tag == 1) {
cell: CardTableViewCell = tableView.dequeueReusableCellWithIdentifier("CardCell") as! CardTableViewCell
return cell
}
else if (tableView.tag == 2) {
cell: ListTableViewCell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
return cell
}
}
Of course Swift requires a "return cell" for the function outside the If statements. I tried with a var cell: UITableViewCell outside, but run into trouble finishing the dequeuReusableCellWithIdentifier.
Anyone have some idea how to do this? Thanks.
This is how I approached it (Swift 3.0 on iOS 10). I made one tableView with two custom cells (each is their own subclass). The segmented control is on my navigationBar and has two segments: People and Places.
There are two arrays within my class, (people and places) which are the data sources for the two table views. An action attached to the segmentedControl triggers the reload of the table, and the switch statement in cellForRowAtIndex controls which cell from which array is loaded.
I load data into the two data arrays from an API call, the asynchronous completion of which triggers dataLoaded(), which reloads the tableView. Again I don't have to worry about which segment is selected when the table is reloaded: cellForRowAtIndex takes care of loading the correct data.
I initialize a basic cell just as UITableViewCell and then in the case statement I created and configure the custom cell. Then I return my custom type cell at the end, and as long as the reuseIdentifiers and classes are correct in cellForRowAtIndex, the correct cell is initialized and displayed in the tableView.
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var peoplePlacesControl: UISegmentedControl!
fileprivate var placesArray: [PlaceModel]?
fileprivate var usersArray: [UserModel]?
#IBAction func segmentedControlActionChanged(_ sender: AnyObject) {
tableView.reloadData()
switch segmentedControl.selectedSegmentIndex {
case 0:
loadUsersfromAPI()
case 1:
loadPlacesFromAPI()
default:
// shouldnt get here
return
}
}
func dataLoaded() {
switch peoplePlacesControl.selectedSegmentIndex {
case 0: // users
if favoriteUsersArray == nil {
self.tableView.reloadData()
} else {
hideTableViewWhileEmpty()
}
case 1: // places
if placesArray != nil {
self.tableView.reloadData()
} else {
hideTableViewWhileEmpty()
}
default:
return
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch segmentedControl.selectedSegmentIndex {
case 0:
if usersArray != nil {
return usersArray!.count
} else {
return 0
}
case 1: // places
if placesArray != nil {
return placesArray!.count
} else {
return 0
}
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch peoplePlacesControl.selectedSegmentIndex {
case 0: // people
let userCell = tableView.dequeueReusableCell(withIdentifier: "MyUserCell", for: indexPath) as! MyUserTableViewCell
if usersArray != nil && indexPath.row < usersArray!.count {
let user = usersArray![indexPath.row]
userCell.configure(user)
userCell.myDelegate = self
}
cell = userCell as MyUserTableViewCell
case 1: // places
let placeCell = tableView.dequeueReusableCell(withIdentifier: "MyPlaceCell", for: indexPath) as! MyPlaceTableViewCell
if favoritePlacesArray != nil && indexPath.row < favoritePlacesArray!.count {
let place = placesArray![indexPath.row]
placeCell.configure(place)
placeCell.myDelegate = self
}
cell = placeCell as MyPlaceTableViewCell
default:
break
}
return cell
}
I have made change in your code.
Use following code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
if (tableView.tag == 1) {
cell: CardTableViewCell = tableView.dequeueReusableCellWithIdentifier("CardCell") as! CardTableViewCell
return cell
}
cell: ListTableViewCell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
return cell
}