Section headers getting reset for Tableview - ios

I have a expandable tableview with custom headers. When I load the tableview I set the alpha for few sections based on if they have any items in it.
I set the alpha back to 1 once I have some items in those sections.
The problem I am facing is as soon as I click on any of the section to expand that section, it just resets the view for all headers that are below the clicked one and just removes the alpha. For Ex If I click on 1st one, all the headers become alpha 1 but if I click on the 4th one, just the 4th and 5th become alpha 1. For ex If I click on 1 this is how it shows -
In order to achieve the collapsable and expandable view, I use the following code to refresh the section -
let indexSet: IndexSet = IndexSet(integer: section)
self.filterTableView.beginUpdates()
self.filterTableView.reloadSections(indexSet, with: UITableView.RowAnimation.fade)
self.filterTableView.endUpdates()
The way I set the header is shown below -
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "FilterOptionHeaderView") as! FilterOptionHeaderView
let filterHeader = self.filterHeaders[section]
let selectedCount = self.selectedItems[filterHeader.key]?.count ?? 0
if selectedCount > 0 {
headerView.headerLabel.text = filterHeader.name + "(\(selectedCount))"
} else {
headerView.headerLabel.text = filterHeader.name
}
headerView.tag = kHeaderSectionTag + section
let headerTapGesture = UITapGestureRecognizer()
headerTapGesture.addTarget(self, action: #selector(sectionHeaderTapped(_:)))
headerView.addGestureRecognizer(headerTapGesture)
if self.sectionItems[section].isEmpty || self.sectionItems[section].count < 1 {
headerView.alpha = 0.5
} else {
headerView.alpha = 1
}
return headerView
}
I have tried setting the alpha in didEndDisplayingHeaderView as well but still doesn't work. Please let me know how to achieve this. I know that all the headers are reset because the arrow of the expanded direction also comes back to initial state even though i have rotation animation in place that shows up and then resets.

Related

how can I refresh a single button in UITableViewCell instead of refreshing whole table or whole cell?

In my swift app I have a UITableView with one static cell and many dynamic cells.
Static cell contains different fields, such as labels, map (taken from MapKit) and a button, that indicates whether user voted up or not.
Now, when user presses the button, I want to change its color, possibly without refreshing anything else.
So far my code looks like this:
var currentUserVote:Int = 0
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).row == 0 {
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
fetchScore(cell.score)
let voteUpImage = UIImage(named: "voteUp");
let tintedVoteUpImage = voteUpImage?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
cell.voteUpButton.setImage(tintedVoteUpImage, for: UIControlState())
checkUsersVote() { responseObject in
if(responseObject == 1) {
cell.voteUpButton.tintColor = orangeColor
} else if (responseObject == -1){
cell.voteUpButton.tintColor = greyColor
} else {
cell.voteUpButton.tintColor = greyColor
}
self.currentUserVote = responseObject
}
//map handling:
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnLocation(initialLocation, map: cell.mapView, radius: regionRadius)
//cell.mapView.isScrollEnabled = false
cell.mapView.delegate = self
.
.
.
return cell
} else {
//handle dynamic cells
}
}
So in the method above I'm checking if user voted already and based on that I'm setting different color on the button. I'm also centering the map on a specific point.
Now, since it's a static cell, I connected IBAction outlet to that button:
#IBAction func voteUpButtonAction(_ sender: AnyObject) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
}
and the vote method works as follows:
func vote(_ vote: Int){
let indexPath = IndexPath(row: 0, section: 0)
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
switch(vote) {
case 1:
cell.voteUpButton.tintColor = orangeColor
case 0:
cell.voteUpButton.tintColor = greyColor
case -1:
cell.voteUpButton.tintColor = greyColor
default:
cell.voteUpButton.tintColor = greyColor
}
tview.beginUpdates()
tview.reloadRows(at: [indexPath], with: .automatic)
tview.endUpdates()
currentUserVote = vote
//sending vote to my backend
}
My problem is, that when user taps the button, he invokes the method vote, then - based on the vote, the button changes color, but immediately after that method cellForRow is called and it changes the color of the button again. Also, it refreshes the map that's inside of it.
What I want to achieve is that when user taps the button, it should immediately change its color and that's it. Map stays untouched and the button is not changed again from cellForRow.
Is there a way of refreshing only that particular button without calling again cellForRow?
First of all, you confuse static and dynamic cells. You can use static cells only in the UITableViewController and you can't use static and dynamic cell at the same time.
I strongly recommend you not to use cell for storing map and button. All elements from the cell will be released after scrolling it beyond the screen.
I can advise you use TableViewHeaderView for this task. In this case you will be able set button and map view as #IBOutlet.
(See more about adding tableview headerView. You can also set it from interface builder.)
Another way is change tableView.contentInset and set your view with map and button as subview to tableView. This method is used when you need create Stretchy Headers.
It should be quite easy, simply do it in your button handler. The sender argument there is the button object that caused the action. When you were connecting it from IB there was a dropdown to select sender type, you may have missed it and the whole thing would have been obvious with UIButton type there.
Simply change your handler like this :
#IBAction func voteUpButtonAction(_ sender: UIButton) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
sender.backgroundColor = yourFavouriteColor
}
Another approach would be to create an IBOutlet for your button, since its from a static cell, and then you would be able to reference it from any place in your view controller.
In this call:
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I see it calls checkUsersVote() which I'm guessing should get the updated value set in the vote() call. Could the problem be that you aren't doing this
currentUserVote = vote
until after reloadRows() is called?

Swift changing image doesn't work

EDIT: After changing the image to a label and transform a string ">" 90 degrees it is still not working correctly. I print out the values of the degrees and it works as expected but the transform is not happening.
I have a totally strange behaviour in my iOS App. I have a table view with section headers. In these section headers are typical dropdown images. One arrow down and one arrow up image. When I tap on the header section the cells display or hide dynamically. Now I want to change the image also according to that.
This is the code that runs when I tap the section header:
func selectedSection(sender: UIButton) {
let previousSelected = self.selectedSection
self.selectedSection = sender.tag
if previousSelected != self.selectedSection {
// one section opens another gets closed
// workaround so the animation does not look awful for section headers
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("Selected: \(self.selectedSection)")
sectionHeaders[self.selectedSection].toggleIcon()
print("***********")
var indexPathToInsert = [IndexPath]()
var indexPathToDelete = [IndexPath]()
for i in 0..<self.menuItems[self.selectedSection].getSubItems().count {
indexPathToInsert.append(IndexPath(row: i, section: self.selectedSection))
}
for i in 0..<self.menuItems[previousSelected].getSubItems().count {
indexPathToDelete.append(IndexPath(row: i, section: previousSelected))
}
tableView.beginUpdates()
tableView.deleteRows(at: indexPathToDelete, with: .none)
tableView.insertRows(at: indexPathToInsert, with: .automatic)
tableView.endUpdates()
} else {
// close the section so the selected section is 0 again
self.selectedSection = 0
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("***********")
tableView.reloadSections([previousSelected], with: .none)
}
}
Toggle Icon looks like this:
func toggleIcon() {
isOpen = !isOpen
print("\(isOpen)")
if isOpen {
print("Works")
self.sectionIcon.image = UIImage(named: "arrow-up")
} else {
print("Does not work")
self.sectionIcon.image = UIImage(named: "arrow-down")
}
}
When I run this and click on different headers everything works as expected. But as soon as I close one section without opening another this section is broken. The code still works but it does not change the image. It prints out:
'Selected: 1'
true
Works
And when I use a breakpoint the code is called where it sets the icon to arrow-up. However, it does not change the image.
I really don't understand what is happening. Is it not allowed to change an image more than once?
Thanks for your help!
I'm not sure if this is what cause the issue, but keep in mind that ALL UI Changes has be on the main thread.
on Swift 3,
try wrapping your call to update the image with something like
DispatchQueue.main.async { }
Good luck :)
Ok I was able to solve it with my own implementation.
I changed the else statement in selectedSection(sender: UIButton) to:
// close the section so the selected section is 0 again
self.selectedSection = 0
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("***********")
var indexPathToDelete = [IndexPath]()
for i in 0..<self.menuItems[previousSelected].getSubItems().count {
indexPathToDelete.append(IndexPath(row: i, section: previousSelected))
}
tableView.beginUpdates()
tableView.deleteRows(at: indexPathToDelete, with: .left)
tableView.endUpdates()
So animating the whole section did not work. Thanks #Mitesh jadav for his option but this does not close the other open sections so I sticked with my solution.

Activity Indicator Not Disabling for CollectionViewCell

I have Collection view forming of 3 cells. I am displaying images on them, however, as fetching the image data from the server takes a few seconds, I use nsuserdefaults and cache.
let imagesFromUserDefaults
var dataIsFetched = false
viewDidLoad() {
let imagesFromUserDefaults = // Data from user defaults
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("ACollectionViewCell", forIndexPath: indexPath) as! ACollectionViewCell
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
activityView.hidesWhenStopped = true
if dataIsFetched == false {
activityView.center = CGPointMake( cell.contentView.frame.size.width / 2, cell.contentView.frame.size.height / 2)
cell.contentView.addSubview(activityView)
activityView.startAnimating()
cell.img = // set image - works
} else {
// Data is fetched from the Server
activityView.stopAnimating()
cell.img = // set image - works
}
func fetchDataFromServer() {
// onSuccessResponse: Store into Array
dataIsFetched = true
collectionView.reloadData()
}
However, after the server data is loaded, even though the images change, the activity indicator doesn't go away. So if cells have the same indexPath.row (as proved by images as well), why doesn't it happen with activity indicator?
Also, is it more logical to place this behaviour to CollectionViewCell class? What is the proper way of handling
Edit:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if initialLoad {
count = self.defaults.count
} else {
count = self.fetched[0].count
}
print("number of cells \(count)")
if count < 4 {
return count
}
return 0
}
You need to set
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
activityView.hidesWhenStopped = true
Also you can set activityView.center = cell.center for a cleaner code.
You know, collect view cells are reusable, the 3 cells displayed in the collection view may share the same cell instance. So every time you call the cellForItemAtIndexPath method, you may get the same cell instance, and you repeatedly add an active indicator to the cell but never remove it. That's not good. Imagine what will you to indicate if an image is fetched with YES/NO, that'll be easy, if the image is fetched, you display YES in the cell, otherwise NO. So the indicator is here to do the same thing, the indicator's YES is animating and NO is not animating, don't get tricked because it's spinning, it's not for tracking the whole fetching process. All you need to do is to add the indicator in the prototype cell, and maintain the states of you images, fetched or not. And update the indicator base on the state:
var isImageFetched = yourImagesStates[indexPath.row];
if isImageFetched == true {
cell.indicator.stopAnimating()
cell.img = image
} else {
cell.indicator.startAnimating()
}
hope it'll help.

Last line in label for cell wont appear with auto layout and dynamic cell height

I am having some serious trouble getting my multiline label to show all of its lines. Often, the last line of the label simply does not appear, but it is apparent that the dynamically calculated cell height has taken in to account that it should have appeared, leaving around the appropriate amount of white space left over in my cell.
The affected label can display 1-7 lines depending on the data. I have played around with many various constraints to try and get it to display but regardless of what is on the last line, it just won't display.
The weird thing is, sometimes it will display when I segue in to the VC, but then when I use the segmented controller inside the VC to display different data and then go back again, the last line will again not display. The opposite happens frequently too (last line of label cutting off when I segue in to the VC, but then using the segmented controller inside the VC to change the displayed data and then go back, it will then display fine).
Things I have ensured: The label is set to word wrap, has line count of 0, has a height greater than or equal to the height of one of it's lines, its resistance and vertical content hugging is set to the highest of anything in the cell, and the width is set appropriately.
The below code is how I determine how many lines the label will have:
let descString = NSMutableAttributedString()
let bountyStart = NSMutableAttributedString(string: "Bounty: ", attributes: [NSFontAttributeName : UIFont.boldSystemFontOfSize(15)])
let bountyDesc = NSMutableAttributedString(string: bounty.description)
descString.appendAttributedString(bountyStart)
descString.appendAttributedString(bountyDesc)
let bLine = NSMutableAttributedString(string: "\n\n", attributes: [NSFontAttributeName : UIFont.systemFontOfSize(2)])
descString.appendAttributedString(bLine)
if !(bounty.turtles == 0.0){
let turtleStart = NSMutableAttributedString(string: "Your turtle count: ")
let turtleAmount = NSMutableAttributedString(string: bounty.turtleCount.description)
descString.appendAttributedString(turtleStart)
descString.appendAttributedString(turtleAmount)
}
descriptionLabel.attributedText = descString
In the screen shot below, you can see that the height of the cell is being calculated appropriately but for some reason, the last line of the label just refuses to show. It should appear after the "is for noobs" line. I've manipulated the white space to appear after the line instead of elsewhere by setting the problem labels bottom to constraint to be greater than or equal, and all other top to bottom constraints as equal to.
Constraints of the problem label:
I've been stumped for quite a while on this one, and I'm starting to think it's not the constraints I've set but something much deeper. All though I would love to be proven wrong.
Here is my VC code.
import UIKit
class TrendingVC: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var menubtn:UIBarButtonItem!
#IBOutlet var trendingTableView:UITableView!
var trendingToggle:Int = 0
let nwt = NWTrending()
let appUserId = NSUserDefaults.standardUserDefaults().stringForKey("UserId") ?? "1" //#TODO: remove ?? 1
var bountyArr: [Bounty] = []
var compArr: [Completion] = []
var peopleArr: [Person] = []
var userId: String = "0"
var username: String = ""
let bountyCellIdentifier = "BountyCellNew"
let personCellIdentifier = "PersonCell"
let completedCellIdentifier = "TrendingCompletedImageCell"
#IBAction func toggleTrending(sender:UISegmentedControl){
switch sender.selectedSegmentIndex{
case 0:
//loads the bounties on segmented control tab
trendingToggle=0
nwt.getTrendingBounties(appUserId, position: 0){(bountyArr, err) in //#TODO: change pos
self.bountyArr = bountyArr as [Bounty]
self.reloadTableViewContent()
}
case 1:
trendingToggle=1
nwt.getTrendingCompletions(appUserId, position: 0){(compArr, err) in
self.compArr = compArr as [Completion]
self.reloadTableViewContent()
}
case 2:
trendingToggle=2
nwt.getTrendingPeople(appUserId, position: 0){(peopleArr, err) in
self.peopleArr = peopleArr as [Person]
self.reloadTableViewContent()
}
default:
break
}
//reloadTableViewContent()
}
override func viewDidLoad() {
super.viewDidLoad()
trendingTableView.estimatedRowHeight = 300.0
trendingTableView.rowHeight = UITableViewAutomaticDimension
/******* Kyle Inserted *******/
//for followers and following back button text, you set it here for when you segue into that section
let backItem = UIBarButtonItem(title: " ", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backItem
/******* END Kyle Inserted *******/
trendingTableView.allowsSelection = false;
trendingTableView.delegate = self
trendingTableView.dataSource = self
//sidebar code
if self.revealViewController() != nil {
menubtn.target = self.revealViewController()
menubtn.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
//loads the bounty on segue
nwt.getTrendingBounties(appUserId, position: 0){(bountyArr, err) in
self.bountyArr = bountyArr as [Bounty]
self.reloadTableViewContent()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//deselectAllRows()
}
func deselectAllRows() {
if let selectedRows = trendingTableView.indexPathsForSelectedRows() as? [NSIndexPath] {
for indexPath in selectedRows {
trendingTableView.deselectRowAtIndexPath(indexPath, animated: false)
}
}
}
func reloadTableViewContent() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.trendingTableView.reloadData()
println("reloading table view content")
self.trendingTableView.scrollRectToVisible(CGRectMake(0, 0, 1, 1), animated: false)
})
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(trendingTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if trendingToggle == 0{
return bountyArr.count
}
else if trendingToggle == 1{
return compArr.count
}
else {
return peopleArr.count
}
}
func tableView(trendingTableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if trendingToggle == 0{
return bountyCellAtIndexPath(indexPath)
}
else if trendingToggle == 1{
return completedCellAtIndexPath(indexPath)
}
else{
return personCellAtIndexPath(indexPath)
}
}
//calls method to set and display each trending bounty cell
func bountyCellAtIndexPath(indexPath:NSIndexPath) -> BountyCellNew {
let cell = trendingTableView.dequeueReusableCellWithIdentifier(bountyCellIdentifier) as! BountyCellNew
var bounty = bountyArr[indexPath.row]
cell.setBountyCellTrending(bounty)
return cell
}
func completedCellAtIndexPath(indexPath:NSIndexPath) -> CompletedCell{
let cell = trendingTableView.dequeueReusableCellWithIdentifier(completedCellIdentifier) as! CompletedCell
var comp = compArr[indexPath.row]
cell.setTrendingCompletedCell(comp)
return cell
}
func personCellAtIndexPath(indexPath:NSIndexPath) -> PersonCell{
let cell = trendingTableView.dequeueReusableCellWithIdentifier(personCellIdentifier) as! PersonCell
var peop = peopleArr[indexPath.row]
cell.setTrendingPeopleCell(peop)
return cell
}
}
Unable to comment due to rep low.
This seems like a IB thing, very likely constraints.
Make sure that any component's top constraint placed under this multiline UILabel is set to the bottom of said UILabel.
For example, in this project of mine, both the TitleView and TimeAndDetailsView contain UILabels that span multiplelines. In order to allow autolayout to properly space things all constraints need to be in order, also notice how the top constraint of TimeAndDetails is the bottom of TitleView
Edit note:
Before you tableView.reloadData() type the following 2 lines, or if uncertain, put it inside viewDidLoad
tableView.estimatedRowHeight = 60 //Type your cell estimated height
tableView.rowHeight = UITableViewAutomaticDimensionhere
Went to sleep last night, ok so I decided to add your code to my details label from the storyboards picture I had posted before, and it seems to have displayed just fine. I have a few questions and pointers below.
let descString = NSMutableAttributedString()
let bountyStart = NSMutableAttributedString(string: "Bounty: ", attributes: [NSFontAttributeName : UIFont.boldSystemFontOfSize(15)])
let bountyDesc = NSMutableAttributedString(string: "this would be 'bounty.description and is set to size 40", attributes: [NSFontAttributeName : UIFont.boldSystemFontOfSize(40)])
descString.appendAttributedString(bountyStart)
descString.appendAttributedString(bountyDesc)
let bLine = NSMutableAttributedString(string: "\n\n", attributes: [NSFontAttributeName : UIFont.systemFontOfSize(10)])
descString.appendAttributedString(bLine)
let turtleStart = NSMutableAttributedString(string: "Your turtle count: ")
let turtleAmount = NSMutableAttributedString(string: "random number 1234 goes here")
descString.appendAttributedString(turtleStart)
descString.appendAttributedString(turtleAmount)
detailsLabel.attributedText = descString
Sadly, this is in a scrollview and not in a cell, so there is the first difference. My first question; is your descriptionLabel.attributedText supposed to display different fontscolors or fonttypes/sizes?, if not then I would say to just use descriptionLabel.text instead of descriptionLabel.attributedText.
Now the fact that there is white space but nothing showing us makes me wonder if bounty.turtleCount.description was previously set to color white somewhere else, or you are simply seeing the newlines you added \n\n and nothing is there because if !(bounty.turtles == 0.0) doesnt execute. Are you sure the that if statement is executed? place a breakpoint and follow along to make sure the values are appended to descString
If this is all correct, then my guess would still be on constraints.
Could you elaborate on, quote from you; "sometimes it will display when I segue in to the VC, but then when I use the segmented controller inside the VC to display different data and then go back again, the last line will again not display." what are you doing different in the seg control when loading the values and reloading the tableview that makes it not display, and under what conditions does it work properly when you segue.
I updated xcode and now this doesn't happen anymore.

Automatic scroll to the next cell in a PFQueryTableViewController in Swift

I have a PFTableViewController with PFTableViewCells in Swift.
In each TableViewCell (with a height of 80% the screen size), there is a button. I would like that when a user select the button, there is an automatic scroll to the next TableViewCell.
Any idea how I can make it?
Thanks a lot
Just determine the indexPath and scroll to the next indexPath. This example assumes a flat table without sections.
func buttonTapped(sender: UIButton) {
let point = sender.convertPoint(CGPointZero, toView:tableView)
let indexPath = tableView.indexPathForRowAtPoint(point)!
// check for last item in table
if indexPath.row < tableView.numberOfRowsInSection(indexPath.section) {
let newIndexPath = NSIndexPath(forRow:indexPath.row + 1 inSection:indexPath.section)
tableView.scrollToRowAtIndexPath(
newIndexPath, atScrollPosition:.Top, animated: true)
}
}

Resources