My problem is, that I have a tableview with a number of cells. Inside of those cells there are images, that get a green border if they are being taped. If I have a lot of cells and I tap for example the first pic in the first cell, that's when the problem happens. When I start scrolling down the table a little faster, other cells of the same type also have the first picture with green border, even though they are in cell number 13 or something. I think that's because they are reusable cells, but how can I make sure, that only the one cell that is being taped, keeps the change? Hope you guys got what I meant, I know it's kinda confusing. Here is one of the two custom cells, with the code that turns the border green. The images of type bouncingRoundImages, are just UIImages, I created the custom subclass of UIImage to make them bounce.
class TwoPicsTableViewCell: UITableViewCell {
#IBOutlet var containerView: UIView!
#IBOutlet var firstImage: bouncingRoundImageView!
#IBOutlet var secondImage: bouncingRoundImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
containerView.layer.cornerRadius = 10
containerView.clipsToBounds = true
setupFirstImage()
setupSecondImage()
}
func setupFirstImage() {
let tappedOne = UITapGestureRecognizer(target: self, action: #selector(checkPicTwo))
firstImage.addGestureRecognizer(tappedOne)
}
func setupSecondImage() {
let tappedTwo = UITapGestureRecognizer(target: self, action: #selector(checkPicOne))
secondImage.addGestureRecognizer(tappedTwo)
}
func checkPicTwo() {
firstImage.bouncing()
if secondImage.layer.borderWidth != 0 {
secondImage.layer.borderWidth = 0
}
}
func checkPicOne() {
secondImage.bouncing()
if firstImage.layer.borderWidth != 0 {
firstImage.layer.borderWidth = 0
}
}
}
It sounds like a reusable cell problem. Try setting the image to nil and borderWidth to 0 in prepareForReuse.
override func prepareForReuse()
{
super.prepareForReuse()
firstImage.image = nil
secondImage.image = nil
firstImage.layer.borderWidth = 0
secondImage.layer.borderWidth = 0
}
Related
Im implementing a UITableViewCell for social media post which includes username, userImage, text, media like image post posted by the user and like, comment buttons etc. Here the media will be optional and if there is any image posted, I will unhide the UIView contains imageView and adjust the UIImageView height based on aspect ratio of the image coming from the API response.
Here is my code for the UITableViewCell class:
class PostsTableViewCell: UITableViewCell {
#IBOutlet weak var ivProfilePic: UIImageView!
#IBOutlet weak var ivPost: UIImageView!
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lblPostContent: UILabel!
#IBOutlet weak var viewPost: UIView!
#IBOutlet weak var heighIvPost: NSLayoutConstraint!
var postImage: UIImage? {
didSet {
if let image = postImage {
configureCellWhenPostImageIsAvailable(image: image)
}
viewPost.isHidden = postImage == nil
layoutIfNeeded()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
viewPost.isHidden = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() {
super.prepareForReuse()
viewPost.isHidden = true
heighIvPost.constant = 162 // default height of UIImageView
ivPost.image = nil
}
// To calculate aspect ratio & set heightIvPost constraint value
func configureCellWhenPostImageIsAvailable(image: UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * viewPost.bounds.width
heighIvPost.constant = newImageHeight
ivPost.image = image
ivPost.layoutIfNeeded()
}
}
This is my cellForRowAt function in the main UIViewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostsTableViewCell", for: indexPath) as! PostsTableViewCell
let data = posts[indexPath.row]
if let userImage = data.memberProfilePic {
cell.ivProfilePic.kf.setImage(with: userImage)
}
cell.lblName.text = data.memberName
if let postText = data.postContent {
cell.lblPostContent.isHidden = false
cell.lblPostContent.text = postText
}
if let postImage = data.postImage { // data.postImage contains URL for image and if not nil then unhide viewPost and set Image
cell.viewPost.isHidden = false
cell.ivPost.kf.setImage(with: postImage)
if let image = cell.ivPost.image {
cell.configureCellWhenPostImageIsAvailable(image: image)
cell.layoutIfNeeded()
}
}
return cell
}
Here is my data model just in case:
class PostEntity: NSObject {
var postContent: String?
var postImage: URL?
var memberName: String?
var memberProfilePic: URL?
override init() {
}
init(jsonData: JSON){
postContent = jsonData["postContent"].stringValue
postImage = jsonData["postImages"].url
memberName = jsonData["member_name"].stringValue
memberProfilePic = jsonData["member_profile"].url
}
}
When I run this code, my requirement is if there is any image in post ie.. data.postImage != nil, it should display image with correct aspect ratio however what I get is:
When UITableView is loaded, the cells that are loaded show images with correct aspect ratio.
When I scroll down, the UIImageView will not show images in correct aspect ratio but default one.
When I scroll back up, I think because of prepareForReuse, it again displays images in correct aspect ratio.
Only problem I face is when o scroll down and new cells are created, it won't show correct aspect ratio if data.postImage != nil.
Here is the video link for further clarification:
https://youtube.com/shorts/vcRb4u_KAVM?feature=share
In the video above you can see, at start all images have perfect aspect ratio but when I scroll down and reach robot and car image, they are of default size i.e. 162, but when I scroll down and scroll back up to them, they get resized to desired results.
I want to remove that behaviour and have correct aspect ratio based on image size.
The problem is...
When a table view calls cellForRowAt, the row height gets set -- usually, be constraints on the content of the cell. If you change the height after the cell has been displayed, it is up to you to inform the table view that it needs to re-calculate the height of the row.
So, you can add a closure to your cell class like this:
class PostsTableViewCell: UITableViewCell {
// closure
var layoutChange: ((PostsTableViewCell) -> ())?
// the rest of your cell code...
func configureCellWhenPostImageIsAvailable(image: UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * viewPost.bounds.width
heighIvPost.constant = newImageHeight
ivPost.image = image
// not needed
//ivPost.layoutIfNeeded()
// use the closure to inform the table view the row height has changed
self.layoutChange?(self)
}
}
then, in your controller's cellForRowAt:
cell.layoutChange = { [weak self] theCell in
guard let self = self,
let cellIndexPath = tableView.indexPath(for: theCell)
else { return }
// you probably want to update something in your data
// maybe:
var data = self.posts[cellIndexPath.row]
data.profilePicDownloaded = true
self.posts[cellIndexPath.row] = data
// tell the table view to re-cacluclate the row heights
self.tableView.performBatchUpdates(nil, completion: nil)
}
return cell
I'm writing a demo to show user's tweets.
The question is:
Every time I scroll to the bottom and then scroll back, the tweet's images and comments are reloaded, even the style became mess up. I know it something do with dequeue, I set Images(which is an array of UIImageView) to [] every time after dequeue, but it is not working. I'm confused and couldn't quite sleep....
Here is core code of my TableCell(property and Images set), which provide layout:
class WechatMomentListCell: UITableViewCell{
static let identifier = "WechatMomentListCell"
var content = UILabel()
var senderAvatar = UIImageView()
var senderNick = UILabel()
var images = [UIImageView()]
var comments = [UILabel()]
override func layoutSubviews() {
//there is part of Image set and comments
if images.count != 0 {
switch images.count{
case 1:
contentView.addSubview(images[0])
images[0].snp.makeConstraints{ (make) in
make.leading.equalTo(senderNick.snp.leading)
make.top.equalTo(content.snp.bottom)
make.width.equalTo(180)
make.height.equalTo(180)
}
default:
for index in 0...images.count-1 {
contentView.addSubview(images[index])
images[index].snp.makeConstraints{ (make) in
make.leading.equalTo(senderNick.snp.leading).inset(((index-1)%3)*109)
make.top.equalTo(content.snp.bottom).offset(((index-1)/3)*109)
make.width.equalTo(90)
make.height.equalTo(90)
}
}
}
}
if comments.count != 0, comments.count != 1 {
for index in 1...comments.count-1 {
comments[index].backgroundColor = UIColor.gray
contentView.addSubview(comments[index])
comments[index].snp.makeConstraints{(make) in
make.leading.equalTo(senderNick)
make.bottom.equalToSuperview().inset(index*20)
make.width.equalTo(318)
make.height.equalTo(20)
}
}
}
}
Here is my ViewController, which provide datasource:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tweetCell = tableView.dequeueReusableCell(withIdentifier: WechatMomentListCell.identifier, for: indexPath) as? WechatMomentListCell else {
fatalError("there is no WechatMomentList")
}
let tweet = viewModel.tweetList?[indexPath.row]
for i in tweet?.images ?? [] {
let flagImage = UIImageView()
flagImage.sd_setImage(with: URL(string: i.url))
tweetCell.images.append(flagImage)
}
for i in tweet?.comments ?? [] {
let flagComment = UILabel()
flagComment.text = "\(i.sender.nick) : \(i.content)"
tweetCell.comments.append(flagComment)
}
return tweetCell
}
The Images GET request has been define at ViewModel using Alamofire.
The firsttime is correct. However, If I scroll the screen, the comments will load again and images were mess up like this.
I found the problem in your tableview cell. in cell you have two variables like this.
var images = [UIImageView()]
var comments = [UILabel()]
Every time you using this cell images and comments are getting appended. make sure you reset these arrays every time you use this cell. like setting theme empty at initialization.
screenshot
I have a problem with piecharts not showing in my custom tableview cell.
The tableview cells are added through storyboard, each cell contains a small icon image, two labels and a UIView which I set in the view inspector as PieChartView.
The small icon image and the text in the two labels is showing fine, no problem there. For the piecharts however, I get no error message but simply none of the charts is displayed. The table shows up, is filled with the proper texts in the labels but the piechartview is empty except the middle hole of the piechart. When I click one of these, the piechartview is displayed - not totally correct (only one of the two pie slices is displayed, the other part is missing).
The touch event therefore makes the slice visible, but I want the piechart be visible for all cells upon running the cell for row function.
I have added the code for the tableviewcontroller and the tableviewcell. Would be great, if someone could point out my error. I have researched and tried a lot, among others the following stack overflow resources:
Charts not plotting in tableViewCell
iosChart not displaying LineChartView points in UITableViewCell
Adding a SubView to UITableViewCell doesn't get displayed
How to implement iOS Chart in a tableview cell?
The screenshot shows the situation after I click a few of the invisible piecharts. They become visible, also when I then scroll down the table some more (not all) of piecharts in random cells are visible, some are not.
Code for tableviewcontroller:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "easyAndDiffAllWordsCell", for: indexPath) as! BVBResultsDiffAndEasyAllWordsGraphTableViewCell
let easyVoc = parsedInEasyVocStructures[indexPath.row]
//get the current voc for the writings
let currentVoc = BVBVocabularyManager.getVoc()
cell.label1.text = currentVoc.kanji
cell.label2.text = currentVoc.kanji2
let image = UIImage(named: "plus")
cell.plusMinusImage.image = image
//set the percentages
cell.percentageSolved = arrayOfSuccessPercentagesForPieChart
cell.percentageNotSolved = arrayOfNegativeSuccessPercentagesForPieChart
cell.setChart(forIndexNo:indexPath.row, dataPoints: months, valuesSolved: arrayOfSuccessPercentagesForPieChart, valuedNonSolved: arrayOfNegativeSuccessPercentagesForPieChart)
cell.setNeedsDisplay()
cell.pieChartView.clipsToBounds = true
cell.pieChartView.layer.masksToBounds = true
cell.pieChartView.contentMode = .scaleAspectFit
return cell
}
And for the tableViewCell:
class BVBResultsDiffAndEasyAllWordsGraphTableViewCell: UITableViewCell {
#IBOutlet weak var kanjiL: UILabel!
#IBOutlet weak var translationL: UILabel!
#IBOutlet weak var pieChartView: PieChartView!
#IBOutlet weak var plusMinusImage: UIImageView!
var testconditions: Array<String>?
var percentageSolved: Array<Int>?
var percentageNotSolved: Array<Int>?
var solvedPercentageDataEntry = PieChartDataEntry(value: 0)
var nonSolvedPercentageDataEntry = PieChartDataEntry(value: 0)
var percentageSolvedNonSolvedDataEntries = [PieChartDataEntry]()
override func awakeFromNib() {
super.awakeFromNib()
solvedPercentageDataEntry.label = NSLocalizedString("solved", comment: "piechart label for the solved area")
pieChartView.chartDescription?.text = ""
pieChartView.legend.enabled = false
pieChartView.setExtraOffsets(left: 2, top: 0, right: 2, bottom: 0)
pieChartView.holeRadiusPercent = 2.8
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
//not used
}
func setChart(forIndexNo: Int, dataPoints: [String], valuesSolved: [Int], valuedNonSolved: [Int]){
var dataEntries: [PieChartDataEntry] = []
solvedPercentageDataEntry = PieChartDataEntry(value: Double(valuesSolved[forIndexNo]), label: "")
nonSolvedPercentageDataEntry = PieChartDataEntry(value:Double(valuedNonSolved[forIndexNo]), label: "")
dataEntries = [solvedPercentageDataEntry, nonSolvedPercentageDataEntry]
percentageSolvedNonSolvedDataEntries = [solvedPercentageDataEntry, nonSolvedPercentageDataEntry]
let pieChartDataSet = PieChartDataSet(entries: percentageSolvedNonSolvedDataEntries, label: nil)
pieChartDataSet.drawValuesEnabled = false
let pieChartData = PieChartData(dataSet: pieChartDataSet)
let colors = [UIColor.themeColor(), UIColor.red]
pieChartDataSet.colors = colors as! [NSUIColor]
pieChartView.data = pieChartData
pieChartView.notifyDataSetChanged()
}
}
I test your code. It works fine.
And the middle hole, maybe it's the configuration problem. Try to add this
pieChartDataSet.drawIconsEnabled = false
pieChartDataSet.sliceSpace = 1
pieChartDataSet.highlightColor = UIColor.white
pieChartDataSet.entryLabelColor = UIColor.white
pieChartDataSet.selectionShift = 0
pieChartView.holeRadiusPercent = 0.5
pieChartView.transparentCircleRadiusPercent = 0.0
Hope this helps
I have a calendarView made up of collectionView. It is a custom calendarView derived using mathematical calculations.
The seventh row marks Saturday and it's holiday so the font color is red for the all the labels of seventh column.
However, when I swipe or navigate to other days, the red color labels are scattered in random order which is untraceable. A screenshot is herewith:
How did this occur?
In my dequeueReusableCell method I have cell configured for holiday as:
cell.isHoliday = (indexPath.row + 1) % 7 == 0 ? true : false
And this is the logic for holiday in my custom collectionViewCell.
#IBOutlet var dateLabel: UILabel!
#IBOutlet var englishDateLabel: UILabel!
#IBOutlet var tithiLabel: UILabel!
var isToday: Bool = false {
didSet {
self.contentView.backgroundColor = isToday ? Colors.Palette.LightGreen : UIColor.white
}
}
var isHoliday: Bool = false {
didSet {
if isHoliday {
tithiLabel.textColor = Colors.Palette.DarkRed
dateLabel.textColor = Colors.Palette.DarkRed
englishDateLabel.textColor = Colors.Palette.DarkRed
}
else {
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
}
}
The number of red labels on top of each collectionview cells goes on increasing as I swipe to next month. Why is this happening and how can I stop this from happening?
You are missing else part:
var isHoliday: Bool = false {
didSet {
if isHoliday {
tithiLabel.textColor = Colors.Palette.DarkRed
dateLabel.textColor = Colors.Palette.DarkRed
englishDateLabel.textColor = Colors.Palette.DarkRed
}
else {
tithiLabel.textColor = UIColor.black
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
}
}
This may be because the cell is being reused and you are not implemented any logic in prepareForReuse method of your custom cell class. In this method try setting text colour properties to nil.
The right way to deal with old data showing up in reused cells is to override prepeareForReuse in your custom cell
open override func prepareForReuse() {
super.prepareForReuse()
tithiLabel.textColor = UIColor.black
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
Clear out old values (by assigning them to nil) or set defaults to all values that might not necessarily be set after the cell is reused. This way, even if the new value(s) are not explicitly set to the cell, you are sure that old values are not being retained.
I'm adding CALayer to top and bottom of scrollable objects (UIScrollView, TableView, CollectionView) to display them when there is a content behind the visible area.
class TableViewWithCALayers: UITableView {
var topGradientLayer: CAGradientLayer?
var bottomGradientLayer: CAGradientLayer?
override func layoutSubviews() {
super.layoutSubviews()
guard self.topGradientLayer != nil && self.bottomGradientLayer != nil else {
addGradientLayerToTop() // create layer, set frame, etc.
addGradientLayerToBottom()
return
}
// addGradientLayerToTop()// if uncomment it - multiple layers are created and they are visible, but this is not the solution...
handleLayerAppearanceAfterLayoutSubviews() // playing with opacity here
}
How I create layer:
func addGradientLayerToTop() {
if let superview = superview {
self.topGradientLayer = CAGradientLayer()
let colorTop = UIColor.redColor().CGColor
let colorBottom = UIColor.clearColor().CGColor
if let topLayer = self.topGradientLayer {
topLayer.colors = [colorTop, colorBottom]
topLayer.locations = [0.0, 1.0]
topLayer.frame = CGRect(origin: self.frame.origin, size: CGSizeMake(self.frame.width, self.layerHeight))
superview.layer.insertSublayer(topLayer, above: self.layer)
if (self.contentOffset.y == 0) {
// if we are at the top - hide layer
// topLayer.opacity = 0.0 //temporarily disabled, so it is 1.0
}
}
}
}
TableViewWithCALayers works nice everywhere, except using TableView with xib files:
class XibFilesViewController : CustomUIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: TableViewWithCALayers!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerNib(UINib(nibName: "CustomTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "CustomTableViewCell")
self.tableView.layer.masksToBounds = false // this line doesn't help...
}
CustomUIViewController is used in many other ViewControllers where TableViewWithCALayers works good, so it should not create a problem.
Layers at the top and bottom appear for one second, then disappear. Logs from LayoutSubviews() func say that they are visible and opacity are 1.0, but something covers them. What can it be and how to deal with that?
Any help is appreciated!)
topLayer.zPosition = 10000 //doesn't help
topLayer.masksToBounds = false //doesn't help as well
When using nib files it's good practice, and design to add the UIView that you want to draw the layer on into your prototype cell, or header/footed and then have that view confirm to your class that's actually handling the layer.