I have a problem when I use collection view with segmentView.
I have a viewController which has a segmentView with 4 elements ( 0 => All , 1 => Photos , 2 => Audios , 3 => videos )
here's and example :
And I have one collectionView to display the data depend on which category clicked from the segmentController
It's working and displaying the data and here's my collectionView methods to display data
// MARK: Datasource collection method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// All Items
if segmentStatues == 0
{
let cellForAll = collectionView.dequeueReusableCell(withReuseIdentifier: "AllItemsCell", for: indexPath) as! AllItemsCollectionViewCell
// reset elements before declare
cellForAll.cellImage.image = nil
cellForAll.playBtn.isHidden = true
cellForAll.layer.borderWidth = 1
cellForAll.layer.borderColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0).cgColor
// When type is an image
if sociaPosts[(indexPath as NSIndexPath).row].postType == "image"
{
cellForAll.cellImage.sd_setImage(with: URL(string: sociaPosts[(indexPath as NSIndexPath).row].postUrl))
}else if sociaPosts[(indexPath as NSIndexPath).row].postType == "audio"
{
cellForAll.cellImage.image = UIImage(named: "recorde_icon")
}else if sociaPosts[(indexPath as NSIndexPath).row].postType == "video"
{
cellForAll.cellImage.sd_setImage(with: URL(string: sociaPosts[(indexPath as NSIndexPath).row].thumbURL))
cellForAll.playBtn.isHidden = false
}
return cellForAll
}else{
// Cell if Images or videos or audios
let newCell = collectionView.dequeueReusableCell(withReuseIdentifier: "beepbeep", for: indexPath) as! userProfileImageCollectionViewCell
// If the type is images
if segmentStatues == 1
{
// Set image URL
newCell.cellImage.sd_setImage(with: URL(string: imagesPosts[(indexPath as NSIndexPath).row].postUrl))
// Get image likes and comments and shares
newCell.likeCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].shares_count)
} else if segmentStatues == 3
{
// For Video player
var player = newCell.customVid
player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer
player?.newIntitVideoPlayer(forViewController: self, videoURL: videosPosts[indexPath.row].postUrl , videoThumbnail: URL(string: videosPosts[indexPath.row].thumbURL), onReady: nil)
// Set video URL and Thumbnail
// Get video likes and comments and shares
newCell.likeCount.text = String(videosPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(videosPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(videosPosts[(indexPath as NSIndexPath).row].shares_count)
}else if segmentStatues == 2
{
// Audio player
let ad = Bundle.main.loadNibNamed("AudioPlayerView", owner: self, options: nil)?.last as! AudioPlayerView
ad.frame = CGRect(x: (newCell.contentView.frame.size.width - newCell.contentView.frame.size.height * 0.6) / 2, y: 20 , width: newCell.contentView.frame.size.height * 0.6, height: newCell.contentView.frame.size.height * 0.6)
ad.playImageView.image = nil
ad.tag = 6666
newCell.contentView.addSubview(ad)
// Set audio URL
ad.audioPath = URL(string: audiosPosts[indexPath.row].postUrl)
ad.viewInitialization()
// Get Audio likes and comments and shares
newCell.likeCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].shares_count)
}
return newCell
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.segmentStatues == 0
{
return sociaPosts.count
}else if self.segmentStatues == 1
{
return imagesPosts.count
}else if self.segmentStatues == 2
{
return audiosPosts.count
}else
{
return videosPosts.count
}
}
I have 2 custom cells in my collection view one for all items and the other for (images - audios - videos ) view
my problem is data is displaying but cells duplicated when i change between segment in userProfileImageCollectionViewCell
these 2 pictures show the issue
Displaying all audios cell when i click in audio segmnet
But after I click in image or video segment cell duplicated like this
it's happen in audio - images - videos only**(userProfileImageCollectionViewCell)**
and finally here's my userProfileImageCollectionViewCell code :
updateded :
import UIKit
import SDWebImage
class userProfileImageCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var customVid: videoPlayer!
#IBOutlet weak var cellImage: UIImageView!
#IBOutlet weak var likeButton: UIButton!
#IBOutlet weak var likeCount: UILabel!
#IBOutlet weak var commentButton: UIButton!
#IBOutlet weak var commentCount: UILabel!
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var shareCount: UILabel!
override func prepareForReuse() {
cellImage.sd_setImage(with: nil)
self.customVid.subviews.forEach({ $0.removeFromSuperview() })
}
}
The issue is that UICollectionViewCell (as well as UITableViewCell by the way) are reused.
To put it simply :
When a cell disappear from screen when you scroll, it may come back at the top/bottom (according to the scroll direction). But it's not a new "clean" cell, it's the previous one. That's the reuse system.
So, when you did: newCell.contentView.addSubview(ad) you added each times a new subview without checking if there was one already. The subview where juste piling. It's easy to check it with the 3D Hierarchy Debug of XCode.
Since you have a userProfileImageCollectionViewCell file, the best solution is to use prepareForReuse().
Create a IBOutlet (let's call it customVid) that will add as its subview the ad/player.
In prepareForReuse() remove all the subview of customVid.
In collectionView:cellForRowAtIndexPath:, do something like that:
var player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer
player?.newIntitVideoPlayer(forViewController: self, videoURL: videosPosts
newCell.customVid.addSubview(player)
Replicate the same mechanism for the ad (you can use the same subview, it's up to you).
Now, a few suggestions not related to your issue:
Name your class starting with an uppercase: userProfileImageCollectionViewCell => UserProfileImageCollectionViewCell
I'd create methods in userProfileImageCollectionViewCell to make your code more readable. I don't speak Swift, so my next lines of code may not compile, but you should get the idea:
func fillWithVideo(videoParam video: CustomVideoClass) {
var player = newCell.customVid
player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer
player?.newIntitVideoPlayer(forViewController: NOTself, videoURL: video.postUrl, videoThumbnail: URL(string: video.thumbURL), onReady: nil)
self.customVid.addSubview(player)
// Set video URL and Thumbnail
// Get video likes and comments and shares
self.likeCount.text = String(video.likes_count)
self.commentCount.text = String(video.comments_count)
self.shareCount.text = String(video.shares_count)
}
In collectionView:cellForRowAtIndexPath:
//If it's the correct section
let video = videosPosts[(indexPath as NSIndexPath).row] as CustomVideoClass
cell fillWithVideo(video)
Note that it seems that you use the UIViewController, so you may want to add the UIViewController parameter in order to get the newIntitVideoPlayer() method to work, but I didn't want to put it in the previous example to keep it more simple. You may need to replace the lines:
cell fillWithVideo(video andViewController:self)
and
func fillWithVideo(videoParam video: CustomVideoClass andViewController viewController: UIViewController)
player?.newIntitVideoPlayer(forViewController: viewController, videoURL: video.postUrl, videoThumbnail: URL(string: video.thumbURL), onReady: nil)
Etc. for each part specific part. It make just easier to read what you are doing. Handle only in collectionView:cellForRowAtIndexPath: the logic (which section should get which item, etc.) and the rest to the cell that may adapt.
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
EDIT: Gif added for clarity
I have a UITableViewCell. The cell contains two sibling elements—a button and a label. When you click the button, new text is loaded into the label.
If the new text is short, and fits on one line (as in the top cell,) all goes well—you can tap that button pretty much as fast as you want.
But if the new text is long, and has to fill two lines or more (as in the bottom cell,) UIKit automatically animates the new text in, sliding it down from the top line. While this automatic animation is happening, UIKit also disables the button so you can't interact with it.
If you try to rapidly click through a few entries in this scenario, it just feels wrong, as the button won't react to every press.
So my question is:
Is there any way to enable interaction with the button during this animation? I can turn it off completely using UIView.setAnimationsEnabled(false), but then it doesn't look as nice.
EDIT: Since it was requested, here's the code that updates the label. The text gets updated in didSet whenever the NSManagedObject model changes.
extension MasterDataSource {
private func configure(cell: UITableViewCell, for indexPath: IndexPath) {
guard let cell = cell as? MasterCell else {
fatalError("Cell Not Returned")
}
let category = categories.object(at: indexPath)
guard let exercises = category.exercises else {
return
}
guard exercises.count > 0 else {
return
}
guard let activeExercise = category.activeExercise else {
return
}
cell.model = MasterCell.Model(with: activeExercise)
}
}
class MasterCell: UITableViewCell {
#IBOutlet private weak var exerciseLabel: UILabel!
#IBOutlet private weak var nextButton: UIButton!
struct Model {
let exercise: Exercise
init(with exercise: Exercise) {
self.exercise = exercise
}
}
var model: Model? {
didSet {
guard let model = model,
let exerciseTitle = model.exercise.title else {
return
}
exerciseLabel.text = exerciseTitle
}
}
}
I have a UITable View in my program with dequeueReusableCells
I should load several images from server and show them in slide show
I have a custom cell and in configuring each cell I download the images in DispatchQueue.global(qos: .userInitiated).async and in DispatchQueue.main.async I add the downloaded pic to the slide show images
but when I start scrolling some of the cells that shouldn't have any pictures , have the repeated pics of another cell
Do you have any idea what has caused this ?!
I'm using swift and also ImageSlideShow pod for the slide show in each cell
Here is some parts of my code :
In my news cell class I have below part for getting images:
class NewsCell: UITableViewCell{
#IBOutlet weak var Images: ImageSlideshow!
#IBOutlet weak var SubjectLbl: UILabel!
#IBOutlet weak var NewsBodyLbl: UILabel!
func configureCell(news: OneNews) {
self.SubjectLbl.text = news.Subject
self.NewsBodyLbl.text = news.Content
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
for imgId in news.ImagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
if let d = data {
let img = UIImage(data: data!)!
imageSrc.append(ImageSource(image: img))
self.Images.setImageInputs(imageSrc);
}
}
}
}
}
self.Images.slideshowInterval = 3
}
And this is cellForRow method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = generalNewsTableView.dequeueReusableCell(withIdentifier: "NewsCell" , for: indexPath) as! NewsCell
if let news = NewsInfo.sharedInstance.getGeneralNews(){
cell.configureCell(news: news[indexPath.row])
}
return cell
}
getGeneralNews() is a getter that returns an array of news
so what I'm doing in cellForRowAt is that I get the news in the given index path and configure my cell with it .
class NewsInfo {
static var sharedInstance = NewsInfo()
private init(){}
private (set) var generalNews:[OneNews]!{
didSet{
NotificationCenter.default.post(name:
NSNotification.Name(rawValue: "GeneralNewsIsSet"), object: nil)
}
}
func setGeneralNews(allGeneralNews:[OneNews]){
self.generalNews = allGeneralNews
}
func getGeneralNews() -> [OneNews]!{
return self.generalNews
}
}
Each news contains an array of the picture Ids
These are the fields in my OneNews class
var Subject :String!
var Content:String!
var ImagesId:[Int]!
Thanks !
UITableViewCell are reused as you scroll. When a cell goes off the top of the screen, it will be reused for another row appearing at the bottom of the screen.
UITableViewCell has a method prepareForReuse you can override. You can use that method to clear out iamgeViews or any other state that should be reset or cancel downloading of images.
In your case, you probably shouldn't use Data(contentsOf:) since it doesn't give you a way to cancel it. URLSessionDataTask would be a better option since it lets you cancel the request before it finishes.
You can try something like this. The main idea of this code is giving a unique number to check if the cell is reused.
I have renamed many properties in your code, as Capitalized identifiers for non-types make the code hard to read. You cannot just replace whole definition of your original NewsCell.
There was no declaration for imageSrc in the original definition. I assumed it was a local variable. If it was a global variable, it might lead other problems and you should avoid.
(Important lines marked with ###.)
class NewsCell: UITableViewCell {
#IBOutlet weak var images: ImageSlideshow!
#IBOutlet weak var subjectLbl: UILabel!
#IBOutlet weak var newsBodyLbl: UILabel!
//### An instance property, which holds a unique value for each cellForRowAt call
var uniqueNum: UInt32 = 0
func configureCell(news: OneNews) {
self.subjectLbl.text = news.subject
self.newsBodyLbl.text = news.content
let refNum = arc4random() //### The output from `arc4random()` is very probably unique.
self.uniqueNum = refNum //### Assign a unique number to check if this cell is reused
if news.imagesId.count==0 {
self.images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
} else {
var imageSrc: [ImageSource] = [] //###
for imgId in news.imagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
//### At this point `self` may be reused, so check its `uniqueNum` is the same as `refNum`
if self.uniqueNum == refNum, let d = data {
let img = UIImage(data: d)!
imageSrc.append(ImageSource(image: img))
self.images.setImageInputs(imageSrc)
}
}
}
}
}
self.images.slideshowInterval = 3
}
}
Please remember, the order of images may be different than the order of imagesId in your OneNews (as described in Duncan C's comment).
Please try.
If you want to give a try with this small code fix, without overriding the prepareForReuse of the cell, just change in configure cell:
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
// STUFF
}
in
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
if news.ImagesId.count > 0{
// STUFF
}
so every cell will start with the placeholderIcon even when reused