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
Related
IN KVKCalendar, if we update style like below
calendarView.style.timeSystem = calendarView.style.timeSystem == .twentyFour ? .twelve : .twentyFour
if calendarView.style.headerScroll.heightHeaderWeek == 0 {
calendarView.style.headerScroll.heightHeaderWeek = 60
} else {
calendarView.style.headerScroll.heightHeaderWeek = 0
}
calendarView.updateStyle(calendarView.style)
calendarView.reloadData()
Custom cell code in func dequeueCell(dateParameter: DateParameter, type: CalendarType, view: T, indexPath: IndexPath) -> KVKCalendarCellProtocol? where T: UIScrollView {
reverts and cell reverts back as default, even on scrolling, it never gets called and i was not able to find any fixes for it in library.
code -
func dequeueCell<T>(dateParameter: DateParameter, type: CalendarType, view: T, indexPath: IndexPath) -> KVKCalendarCellProtocol? where T: UIScrollView {
switch type {
case .year where dateParameter.date?.month == Date().month:
let cell = (view as? UICollectionView)?.kvkDequeueCell(indexPath: indexPath) { (cell: CustomDayCell) in
cell.imageView.image = UIImage(named: "ic_stub")
}
return cell
case .day, .week, .month:
guard dateParameter.date?.day == Date().day && dateParameter.type != .empty else { return nil }
let cell = (view as? UICollectionView)?.kvkDequeueCell(indexPath: indexPath) { (cell: CustomDayCell) in
cell.imageView.image = UIImage(named: "ic_stub")
}
return cell
case .list:
guard dateParameter.date?.day == 14 else { return nil }
let cell = (view as? UITableView)?.kvkDequeueCell { (cell) in
cell.backgroundColor = .systemRed
}
return cell
default:
return nil
}
}
This code is mentioned in example only. Can be reproduce-able by just clicking on reload button in it and see the issue.
I have question about the tableView.
Here is my tableView code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tierCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.interestRateTextField.delegate = self
cell.rowLabel.text = "\(indexPath.row + 1)."
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
interestRateArray[indexPath.row] = interest
} else {
interestRateArray[indexPath.row] = nil
}
} else {
interestRateArray[indexPath.row] = nil
}
return cell
}
As you can see, I have the cellForRowAt method to get the value from the textfields in the cell, and assign to my arrays. (I actually have 2 textfields per cell.)
Basically, I let the users input and edit the textfield until they are happy then click this calculate button, which will call the calculation method. In the calculation method I call the "tableView.reloadData()" first to gather data from the textfields before proceed with the actual calculation.
The problem was when I ran the app. I typed values in all the textfields then clicked "calculate", but it showed error like the textfields were still empty. I clicked again, and it worked. It's like I had to reload twice to get things going.
Can anyone help me out?
By the way, please excuse my English. I'm not from the country that speak English.
edited: It may be useful to post the calculate button code here as someone suggested. So, here is the code of calculate button
#IBAction func calculateRepayment(_ sender: UIButton) {
//Reload data to get the lastest interest rate and duration values
DispatchQueue.main.async {
self.interestRateTableView.reloadData()
}
//Get the loan value from the text field
if let loanText = loanTextField.text {
if let loanValue = Double(loanText) {
loan = loanValue
} else {
print("Can not convert loan value to type Double.")
return
}
} else {
print("Loan value is nil")
return
}
tiers = []
var index = 0
var tier: Tier
for _ in 0..<tierCount {
if let interestRateValue = interestRateArray[index] {
if let durationValue = durationArrayInMonth[index] {
tier = Tier(interestRateInYear: interestRateValue, tierInMonth: durationValue)
tiers.append(tier)
index += 1
} else {
print("Duration array contain nil")
return
}
} else {
print("Interest rate array contain nil")
return
}
}
let calculator = Calculator()
repayment = calculator.calculateRepayment(tiers: tiers, loan: loan!)
if let repaymentValue = repayment {
repaymentLabel.text = "\(repaymentValue)"
totalRepaymentLabel.text = "\(repaymentValue * Double(termInYear!) * 12)"
} else {
repaymentLabel.text = "Error Calculating"
totalRepaymentLabel.text = ""
}
}
cellForRowAt is used for initially creating and configuring each cell, so the textfields are empty when this method is called.
UITableView.reloadData() documentation:
// Reloads everything from scratch. Redisplays visible rows. Note that this will cause any existing drop placeholder rows to be removed.
open func reloadData()
As it says in Apple's comment above, UITableView.reloadData() will reload everything from scratch. That includes your text fields.
There are a number of ways to fix your issue, but it's hard to say the best way without more context. Here's an example that would fit the current context of your code fairly closely:
class MyCustomTableViewCell: UITableViewCell {
#IBOutlet weak var interestRateTextField: UITextField!
var interestRateChangedHandler: (() -> ()) = nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
interestRateTextField.addTarget(self, action: #selector(interestRateChanged), for: UIControlEvents.editingChanged)
}
#objc
func interestRateChanged() {
interestRateChangedHandler?()
}
}
and cellForRowAtIndex:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.rowLabel.text = "\(indexPath.row + 1)."
cell.interestRateChangedHandler = { [weak self] in
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
self?.interestRateArray[indexPath.row] = interest
} else {
self?.interestRateArray[indexPath.row] = nil
}
} else {
self?.interestRateArray[indexPath.row] = nil
}
}
return cell
}
So I have 3 cells: SenderCell, ReceiverCell , and a default blank Cell
I want the mainTableView to show all messages from both sender and receiver, which are contained in messageFromCurrentUserArray and messageFromReceiverArray respectively. Unfortunately, whenever I run the code below, it only shows either cell. I would like to show both cells if their arrays aren't empty. Hoping someone could help me. Thanks in advance!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allMessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < messageFromCurrentUserArray.count {
let cell = mainTableView.dequeueReusableCell(withIdentifier: "SenderCell", for: indexPath) as! SenderTableViewCell
cell.senderMessage.text = messageFromCurrentUserArray[indexPath.row]
mainTableView.beginUpdates()
cell.senderMessage.sizeToFit()
let imageFile = PFUser.current()?["profilePhoto"] as! PFFile
imageFile.getDataInBackground(block: { (data, error) in
if let imageData = data {
self.currentUserImage = UIImage(data: imageData)!
}
})
cell.senderProfilePhoto.image = currentUserImage
cell.senderProfilePhoto.layer.masksToBounds = false
cell.senderProfilePhoto.layer.cornerRadius = cell.senderProfilePhoto.frame.height / 2
cell.senderProfilePhoto.clipsToBounds = true
mainTableView.endUpdates()
return cell
} else if indexPath.row < messageFromReceiverArray.count {
let cell = mainTableView.dequeueReusableCell(withIdentifier: "ReceiverCell", for: indexPath) as! ReceiverTableViewCell
cell.receiverMessage.text = messageFromReceiverArray[indexPath.row]
mainTableView.beginUpdates()
cell.receiverMessage.sizeToFit()
cell.receiverProfilePhoto.image = receiverImage
cell.receiverProfilePhoto.layer.masksToBounds = false
cell.receiverProfilePhoto.layer.cornerRadius = cell.receiverProfilePhoto.frame.height / 2
cell.receiverProfilePhoto.clipsToBounds = true
mainTableView.endUpdates()
return cell
} else {
let cell = mainTableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CellTableViewCell
return cell
}
}
The problem is the comparsion in the outer if statements:
if indexPath.row < messageFromCurrentUserArray.count {
//...
} else if indexPath.row < messageFromReceiverArray.count {
// ...
} else {
// ...
}
Since all the counts in the arrays start with index 0 and stop at their count each, but your indexPath.row will go up to the the sum of both (e.g. messageFromCurrentUserArray.count + messageFromReceiverArray.count)
You'll have to change the second if clause and the index access to something like
} else if indexPath.row < (messageFromCurrentUserArray.count + messageFromReceiverArray.count) {
// ...
int receiverIndex = indexPath.row - messageFromCurrentUserArray.count
cell.receiverMessage.text = messageFromReceiverArray[receiverIndex]
// or: cell.receiverMessage.text = allMessages[indexPath.row]
// ...
} else {
// ...
}
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.
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
}