I have created two custom cells in tableview and on the second cell, I have a list of check box items. When I click on check box the tableview scrolls automatically.
Here is my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0
{
let cell:SharedFirstTableViewCell = self.ListTableview.dequeueReusableCellWithIdentifier("CellF")! as! SharedFirstTableViewCell
tableView.estimatedRowHeight = 364.0
tableView.rowHeight = UITableViewAutomaticDimension
tableView .separatorColor = UIColor .clearColor()
tableView .tableFooterView = UIView (frame:CGRectZero)
cell .selectionStyle = UITableViewCellSelectionStyle .None
cell.contentView .addSubview(collectionView)
cell.btn_SelectAll.tag=indexPath.row
cell.btn_SelectAll.addTarget(self, action:#selector(ShareViewController.SelectAll(_:)), forControlEvents: .TouchUpInside)
cell.btn_ByCountry.tag=indexPath.row
cell.btn_ByCountry.addTarget(self, action:#selector(ShareViewController.Country(_:)), forControlEvents: .TouchUpInside)
groupname = cell.txt_GroupName
if country.isEmpty == true {
cell.lbl_ByCountry.text = "By Country"
}
else
{
cell.lbl_ByCountry.text = country
}
if load == true
{
cell.btn_SelectAll.setImage((UIImage (named: "box.png")), forState: .Normal)
}
else
{
cell.btn_SelectAll.setImage((UIImage (named: "Full Image-30.png")), forState: .Normal)
}
return cell
}
else
{
let cellFirst:SharedTwoTableViewCell = self.ListTableview.dequeueReusableCellWithIdentifier("CellT")! as! SharedTwoTableViewCell
ListTableview.bounces = false
tableView.estimatedRowHeight = 57.0
tableView.rowHeight = UITableViewAutomaticDimension
tableView .separatorColor = UIColor .clearColor()
tableView .tableFooterView = UIView (frame:CGRectZero)
cellFirst.lbl_Name.text = newDictionary .objectAtIndex(indexPath.row).valueForKey("name") as? String
cellFirst.lbl_Address.text = newDictionary .objectAtIndex(indexPath.row).valueForKey("address") as? String
let url_image = newDictionary .objectAtIndex(indexPath.row).valueForKey("profile_pic") as? String
if url_image != nil {
imageURL = ""
imageURL = imageURLs+(url_image )!
cellFirst.img_profile.hnk_setImageFromURL(NSURL(string: imageURL)!)
}
cellFirst.tickButton.tag=indexPath.row
cellFirst.tickButton.addTarget(self, action:#selector(ShareViewController.tickClicked(_:)), forControlEvents: .TouchUpInside)
if selectedArray .containsObject(newDictionary.objectAtIndex(indexPath.row))
{
cellFirst.tickButton.setImage((UIImage (named: "box.png")), forState: .Normal)
}
else
{
cellFirst.tickButton.setImage((UIImage (named: "Full Image-30.png")), forState: .Normal)
}
return cellFirst
}
}
It's probably auto scrolling because your tickClicked handler is making a textfield a first responder.
You can disable this by disabling the table view's scrolling during this transition:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// This is solving the issue where making the text field first responder
// automatically scrolls the scrollview down by the height of the search bar.
if (!scrollView.isDragging && !scrollView.isDecelerating &&
self.searchField.isFirstResponder &&
(scrollView.contentOffset.y < -scrollView.contentInset.top)) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
}
}
Other methods can be found here:
Disable UIScrollView scrolling when UITextField becomes first responder
Disabling automatic scrolling of UITableView when editing UITextField inside UITableViewCell
As you said when you click on check box table going to scroll so problem maybe in tickClicked.
Related
I had used the code remove from superView to remove labels from screen when there is no address available just showing one label as shown in below
but here when I got address from the api and was not displaying properly and the code itself was entering into it is showing in image as shown below
but the proper image should be displayed with name label , address label and mobile number label can anyone help me how to display the address label and mobile number label after removing from view when address has got from api ?
here is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AddressTableViewCell
let dict = guestShippingAddressModel
self.tableDetails.isHidden = false
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
cell.deleteButton.tag = indexPath.row
if self.street?.isEmpty == true || self.street?.isEmpty == nil {
cell.addressLabel.isHidden = true
cell.mobileNumberLabel.isHidden = true
cell.radioButton.isHidden = true
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
cell.addresslabel.removeFromSuperview()
cell.mobileNumberlabel.removeFromSuperview()
cell.nameLabel.text = "No address available"
if delayCheck == true {
let when = DispatchTime.now() + 5 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let addtoCartVC = storyboard.instantiateViewController(withIdentifier: "newAddress") as! NewAddressViewController
self.navigationController?.pushViewController(addtoCartVC, animated: true)
}
}
}
else {
cell.addressLabel.isHidden = false
cell.radioButton.isHidden = false
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
cell.nameLabel.isHidden = false
cell.nameLabel.text = "\((dict?.firstName)!) \((dict?.lastName)!)"
cell.addressLabel.text = "\((self.street)!) \((dict?.city)!) \((dict?.region)!) \((dict?.postCode)!)"
cell.mobileNumberLabel.text = "\((dict?.telephone)!)"
}
cell.radioButton.tag = indexPath.row
cell.editButton.tag = indexPath.row
cell.deleteButton.tag = indexPath.row
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
cell.radioButton.addTarget(self, action: #selector(selectRadioButton(_:)), for: .touchUpInside)
cell.deleteButton.addTarget(self, action: #selector(deleteAction(button:)), for: .touchUpInside)
cell.editButton.addTarget(self, action: #selector(editButtonAction(_:)), for: .touchUpInside)
let checkIndex = self.checkIsRadioSelect.index(of: indexPath.row)
if(checkIndex != nil) {
cell.radioButton.isSelected = true
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
}
else
{
cell.radioButton.isSelected = false
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
}
if (checkIsPaymentRadioSelect == true) {
let defaultvalue = street
if defaultvalue?.isEmpty == false {
cell.radioButton.isSelected = true
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
addressSelected = true
}
}
return cell
}
instead of remove address label and mobile label from superview set the height constraint for that labels and change the height constant to 0 to hide the labels and make the cell height dynamic like below.
tblList.rowHeight = UITableViewAutomaticDimension;
tblList.estimatedRowHeight = CGFloat(100)
Man, don't write so many code in dataSource method of UITableViewDataSource. It's very hard to handle and understand what is really going on! There is sooo many strange logic I don't really understand. So I recommend you
to make all configuration in UITableViewCell subclass (AddressTableViewCell & NewAddressViewController in your case)
Models is better to write like structs / classes but not dictionary
activityIndicator shouldn't be handled in cellForRow method. It should change depending on request whether it's still processing / ended / failed and so on.
If you are doing something like cell.radioButton.tag = indexPath you are doing something wrong cause you have method for tableView .indexPath(forCell: UICollectionViewCell). Which can say you exact indexPath
If you handle some touches from cell - you should make delegate of the cell which will send to your ViewController the cell where touch occurred like
func selectRadioButton(inCell cell: UICollectionViewCell) {
guard let indexPath = tableView.indexPath(forCell: cell) else { return }
// do what you need to do & you have your indexPath! = know where touch occurred
}
Ending my lecture - your source should fully define your cell. It means if you have some flag in the model which indicate on whether you display something or not you should write something like:
yourView.isHidden = !yourModel.isDisplayed
Why? Because cell are reused and all the subclass properties retain their state that is all UI "settings" (visibility and others remain). Read about it in google there are a lot of articles on this theme which did it better then I am
Hope I helped you!
I have a table view and a custom cell which has a button.According to my need I sometime set
1.Title of Button
or
2.Background image without title
on runtime.The problem is my button size specifically width is not changing according to textlength of title or according to background image size.I have tried sizeToFit and cell.btnChat.sizeThatFits(backgroundimage.size) but it does'nt reflect any change.
The "button" condition is for set title and image to set background image.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if blockChatForLogin{
if (chatRespones[indexPath.row].type == "text"){
//Adding rows in table
// let cell:ChatResponseController = self.tbChat.dequeueReusableCell(withIdentifier: "ChatResponseCell") as! ChatResponseController
let cell:CustLabelCell = self.tbChat.dequeueReusableCell(withIdentifier: "CustomLabelCell") as! CustLabelCell
cell.lblTextResponse.text = chatRespones[indexPath.row].value
cell.lblTextResponse.layer.masksToBounds=true
cell.lblTextResponse.layer.cornerRadius=5
return cell
}
else if (chatRespones[indexPath.row].type == "button"){
//Adding rows in table
let cell:CustButtonCell = self.tbChat.dequeueReusableCell(withIdentifier: "CustomButtonCell") as! CustButtonCell
cell.btnChat.setTitle(chatRespones[indexPath.row].value,for: .normal)
cell.btnChat.layer.masksToBounds=true
cell.btnChat.layer.cornerRadius=5
cell.btnChat.layer.borderWidth=2
cell.btnChat.layer.borderColor=UIColor(red:0.18, green:0.61, blue:0.49 ,alpha:1.0).cgColor
cell.delegete = self
return cell
}
else if (chatRespones[indexPath.row].type == "image"){
//Adding rows in table
// let cell:ChatButtonController = self.tbChat.dequeueReusableCell(withIdentifier: "ChatButtonCell") as! ChatButtonController
let cell:CustButtonCell = self.tbChat.dequeueReusableCell(withIdentifier: "CustomButtonCell") as! CustButtonCell
if let image: UIImage = UIImage(named:"\(chatRespones[indexPath.row].value.replacingOccurrences(of: ":", with: "")).jpg")
{
cell.btnChat.setImage(image, for: .normal)
cell.btnChat.setTitle("",for: .normal)
cell.btnChat.backgroundColor = UIColor.clear
}
else{
cell.btnChat.backgroundColor = UIColor.clear
cell.btnChat.setTitleColor(UIColor.red, for: .normal)
cell.btnChat.setTitle("Error: Image Missing with name \(chatRespones[indexPath.row].value)",for: .normal)
}
cell.btnChat.layer.masksToBounds=true
cell.btnChat.layer.cornerRadius=5
cell.btnChat.layer.borderWidth=2
cell.btnChat.layer.borderColor=UIColor(red:0.18, green:0.61, blue:0.49 ,alpha:1.0).cgColor
cell.delegete = self
return cell
}
}
default:
print("No case")
}
}
After you set the text of the cell, you can do this:
cell.textLabel.intrinsicContentWidth
To get the required width of the text, you can then create the frame of the view using the retrieved size as the width. Be careful if you have any fixed widths set in your storyboard or something, so it might be best to create your textLabel programmatically as well.
I have a View Controller with multiple Collection Views.
Each collection view is using the same custom cell with xib. In this xib i have a button.
The collectionViews names are
1) manPerfumeCV
2) womanPerfumeCV
3) kidsPerfumeCV
Inside cellForItemAt i have cell.productCart.tag = indexPath.row
let cell:iPhoneCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "iPhoneCell", for: indexPath) as! iPhoneCollectionViewCell
if collectionView == self.womanPerfumeCV {
let prods = womanPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.manPerfumeCV {
let prods = manPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.kidsPerfumeCV {
let prods = kidsPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
}
and in the same view controller i have this action for the button from the xib file
#IBAction func iPhoneAddToCart(_ sender: AnyObject) {
let butt = sender as! UIButton
let indexP = IndexPath(row: butt.tag, section: 0)
let cell = manPerfumeCV.cellForItem(at: indexP) as! iPhoneCollectionViewCell
print(manPerfumeData[indexP.row].price)
}
Each collection view has its own [Products] array.
1) manPerfumeData
2) womanPerfumeData
3) kidPerfumeData.
In my code if i tap at the button which is at the 1st collection view with manPerfumeData it prints the price very well.
Although if i push the button on the 2nd or 3rd collection view the app crashes.
Is there any way to know from wich collection view he pushed the button so i can load the spesific [Products] array ??
Thanks!
You can use tag property of UIButton.
Let consider your code
let cell:iPhoneCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "iPhoneCell", for: indexPath) as! iPhoneCollectionViewCell
if collectionView == self.womanPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 1000
} else if collectionView == self.manPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 2000
} else if collectionView == self.kidsPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 3000
}
You noticed that I changed the tag property of UIButton. Now when tap on button, check tag property of UIButton. Like this
if (sender.tag >= 1000 && sender.tag<2000)
{
//manPerfumeCV
}
else if (sender.tag >= 2000 && sender.tag<3000)
{
//womanPerfumeCV
}
else
{
//kidsPerfumeCV
}
For fetching value from array, subtract the beginning value from tag property and you will get value, like this
For manPerfumeCV
indexValue = sender.tag - 1000
For womanPerfumeCV
indexValue = sender.tag - 2000
For kidsPerfumeCV
indexValue = sender.tag - 3000
This value can directly use for getting data from array.
inorder to achieve it you can add tag property to the collectionViews in viewDidLoad
override func viewDidLoad() {
// your code
self.womanPerfumeCV.tag = 0;
self.manPerfumeCV.tag = 1;
self.kidsPerfumeCV = 2;
}
using this you can identify the source
// Now its time to listen to the buttonEvent
I would suggest to keep the event listener in the same controller or make a protocol callback only with index and tag. To keep it simple lets make the changes in same viewController File
if collectionView == self.womanPerfumeCV {
let prods = womanPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.manPerfumeCV {
let prods = manPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.kidsPerfumeCV {
let prods = kidsPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
}
cell.yourButton.tag = (collectionView.tag * 1000 )+(indexPath.row);
cell.yourButton.addTarget(self, action: #selector(ButtonClicked(_:)), forControlEvents: .TouchUpInside)
now add selector to event
func ButtonClicked(sender: UIButton) {
let index : Int = sender.tag % 1000;
switch sender.tag {
case let x where x < 1000:
loadDataFromFirstArrayWithIndex(index);break;
case let x where x <2000:
loadDataFromSecondArrayWithIndex(index);break;
default:
loadDataFromThirdArrayWithIndex(index);
}
}
I want the first row of a table to have a cell with a TextField (in my case named FindTextField).
The way I have implemented (see code below), as the cells are reused and if the list of cells is scrolled down, then more cells will be assigned with the TextField. I would like to know how can I use tags or anything else that allows to force that only the first row will have the TextField even if the user scrolls all the way down and then up.
Now, here is the code:
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("identifier",
forIndexPath: indexPath) as UITableViewCell
if indexPath.row == 0 {
FindTextField.frame = CGRectMake(74, 4, cell.bounds.width - 78 , cell.bounds.height - 8)
FindTextField.hidden = false
FindTextField.placeholder = "Find"
FindTextField.font = UIFont.systemFontOfSize(20)
FindTextField.borderStyle = UITextBorderStyle.RoundedRect
FindTextField.autocorrectionType = UITextAutocorrectionType.No
FindTextField.keyboardType = UIKeyboardType.Default
FindTextField.returnKeyType = UIReturnKeyType.Done
FindTextField.clearButtonMode = UITextFieldViewMode.WhileEditing;
FindTextField.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
FindTextField.delegate = self
FindTextField.autocapitalizationType = .AllCharacters
cell.contentView.addSubview(FindTextField)
cell.imageView?.image = UIImage(named: "menu_find.png")
cell.textLabel?.hidden = true
}
cell.textLabel!.text = StationsChatList[indexPath.row]
cell.textLabel!.font = UIFont.systemFontOfSize(24)
cell.textLabel?.textColor = UIColor.whiteColor()
cell.textLabel?.backgroundColor = UIColor.blackColor()
cell.backgroundColor = UIColor.blackColor()
cell.layer.borderWidth = 1.0
cell.layer.borderColor = UIColor.darkGrayColor().CGColor
return cell
}
You can assign tag to your textField, and later access it. And when row != 0 simply hide it. This is what I'm talking about:
if cell.viewWithTag(1001) == nil { // init your text field
FindTextField.frame = ...
FindTextField.tag = 1001 // or whatever you want
...
}
cell.viewWithTag(1001)?.hidden = indexPath.row != 0
...
There is another way - implement prepareForReuse() method inside your cell and manually hide FindTextField (it will be hidden for each cell). And later inside cellForRow make it visible if row == 0.
My tableview displays podcast data. All cells are reused fine and display podcasts in the right order. I add download and play buttons as subviews of each cell. When I scroll down the list, the play button which is meant to be hidden for non-downloaded episodes will be shown and will hold the data of the cell which previously initiated a download. For example if I tap download button in cell 1, it will download the right episode and the play button will play the episode perfectly. If I scroll down to cell 10, the same play button will appear and will play episode from button 1. What's wrong with my code?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell: PFTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PFTableViewCell
if let title = object?["title"] as? String {
cell.textLabel?.text = title
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Download", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "downloadEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
let playButton = UIButton(type: UIButtonType.Custom)
playButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height - 89, 100, 35)
playButton.setTitle("Play", forState: .Normal)
playButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
playButton.tag = indexPath.row
playButton.addTarget(self, action: "playEpisode:", forControlEvents: .TouchUpInside)
if let isDownloaded = object?["isDownloaded"] as? String {
if isDownloaded == "yes" {
playButton.hidden = false
} else {
playButton.hidden = true
}
}
cell.addSubview(playButton)
}
return cell
}
Edit:
I've also tried this but it doesn't work and still creates 1 more button per cell:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell: PFTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PFTableViewCell
if let title = object?["title"] as? String {
cell.textLabel?.text = title
}
if let button = cell.viewWithTag(indexPath.row) {
print(button)
} else {
if let isDownloaded = object?["isDownloaded"] as? String {
if isDownloaded == "yes" {
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Play", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "playEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
} else {
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Download", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "downloadEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
}
}
}
return cell
}
Edit2:
Added download method:
func downloadEpisode(sender: UIButton) {
print("Downloading..")
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let object = self.objectAtIndexPath(indexPath)
if let result = object {
let urlstring = result["downloadURL"] as? String
if urlstring != nil {
let episodeURL = NSURL(string: urlstring!)
downloader.downloadPodcastEpisode(episodeURL!, podcast: result)
}
}
}
Answer:
Although all answers were right, I have decided to remove the button download/play on cell taps in the end. Many thanks!
The problem is that you're adding a new button every time. When your cell is reused, it will already have a button. You need to write code that guarantees a button will only be created once, and after that, modify the existing button instead of creating a new one.
There are two common approaches to this:
Use viewWithTag to check if a button exists (create a button and set its tag if it doesn't)
Subclass UITableViewCell, create the button in the initializer, and configure the button each time the cell is used.
Searching for "uitableviewcell viewwithtag" or "subclass uitableviewcell" should give you plenty of sample code, so I won't rehash that here.
This happens because placement of a button in a cell in your code is a one-way street: once a button is added to the cell, it is never removed or made invisible.
You need to add an else to your if statement to remove the button from the recycled cell, if that button exists.
Better yet, make the button a permanent part of the "Cell", and control its visibility in the same if statement: instead of adding the button, make it visible; instead of removing the button, make it invisible. As an added bonus, this would let you set up the visual appearance for your button in interface builder, and set some constraints, which would help avoiding "magic numbers" such as 100, 89, and 35 in the body of your code.
Looking at your code its clear you are already subclassing UITableViewCell and have custom UI, then its recommended add button in side UITableViewCell either in initialization or if you have xib for cell then add UIButton in xib (via interface builder) and create an IBOutlet for newly added button .
Now when a cell will be reused you will have that button as well, when cell is being reused its your responsibility to keep its state right (means updating its UI), for that override prepareForReuse method inside custom tableViewCell class and reset UI of cell, and in cellForRowAtIndexPath you can update UI as per new data object.