Pictures getting Mixed when scrolling in UITable View Swift - ios

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

Related

Dynamic UIImageView inside UITableViewCell based on aspect ratio of image coming from URL

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

Ios images and labels keep loading while scrolling

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.

Allow user interaction with UIButton during system-provided animation of UILabel

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
}
}
}

Collection view with segment duplicate cells

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.

Moving data between custom cells in a dynamic table in Swift

Firstly, apologies but I'm new to all of this (iOS Dev & Swift).
I have a number of custom cells that I dynamically load into a tableview. One of my cells is a data picker that, when the date is changed by the user, I want to send the updated data to one of the other cells but I'm stumped. Help please.
Assuming the cells are loaded and visible, you can pass a reference of one cell to another, but you'll need to create a couple of methods within your custom cells.
In my case I have two custom cells, a cell named CellBirthday that contains a label named birthDateLabel, and a cell that contains a DatePicker named CellDatePicker. I want to update birthDateLabel every time the DataPicker value changes.
I'll first load the cells and store a reference of CellBirthday inside CellDatePicker, then when the date picker changes, I'll update the label value inside CellBirthday. Here is the relevant code fragment to load the two cells. In this example, I use the same name for both the cell tag and class name, for example CellBirthday is both the cell tag and the class name specified in the storyboard:
let birthdayCell = tableView.dequeueReusableCell(withIdentifier: "CellBirthday") as! CellBirthday
let datePickerCell = tableView.dequeueReusableCell(withIdentifier: "CellDatePicker") as! CellDatePicker
datePickerCell.setBirthdayCell(BirthdayCell: birthdayCell)
And here are the custom classes:
class CellBirthday: UITableViewCell {
#IBOutlet fileprivate weak var birthDateLabel: UILabel!
var birthdayText: String? {
didSet {
birthDateLabel.text = birthdayText
}
}
}
class CellDatePicker: UITableViewCell {
#IBOutlet fileprivate weak var datePicker: UIDatePicker!
var birthdayCell: CellBirthday?
func setBirthdayCell(BirthdayCell birthdayCell: CellBirthday) {
self.birthdayCell = birthdayCell
}
func getDateString(FromPicker picker: UIDatePicker? = nil) -> String {
var dateText: String = ""
if picker != nil {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("MMMMdy")
dateText = dateFormatter.string(from: picker!.date)
}
return dateText
}
#IBAction func datePickerValueChange(_ sender: UIDatePicker) {
if birthdayCell != nil {
birthdayCell!.birthdayText = getDateString(FromPicker: sender)
}
}
}
Since your cells are dynamically loaded into the table, it is not possible to address a specific cell directly. You should trying changing the underlying data source when the user chooses a date, and call table.reloadData()

Resources