Custom UITableViewCell with optionals - swift

I'm creating custom cell for my tableView. I made a swift file:
import Foundation
import UIKit
class CellForPost: UITableViewCell {
#IBOutlet weak var postLikes: UILabel!
#IBOutlet weak var postText: UILabel!
#IBOutlet weak var postDate: UILabel!
#IBOutlet weak var postPhoto: UIImageView!
}
and implemented it in delegate method:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
cell.postPhoto.image = UIImage.init(data: (posts[indexPath.item].postPhoto)! as NSData)
cell.postText.text = posts[indexPath.item].postText
cell.postLikes.text = String(posts[indexPath.item].postLikes!)
cell.postDate.text = timestampToDate(posts[indexPath.item].postDate!)
return cell
}
everything works great when post has full content, but when for example there is no photo(which is optional in post struct) it crashes with message
fatal error: unexpectedly found nil while unwrapping an Optional value
I understand this message, so I tried to make
#IBOutlet weak var postPhoto: UIImageView?
like an optional value, but it doesn't work, 'cos compiler wants me to unwrap values before inserting to cell.
P.S. If it's possible to give a short advice about deleting imageView at all when it is nil and resize row height to fit.

You don't need touch your outlets declarations, you need check for nil in cellForRowAtIndexPath method (and setup nil as value thought)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
var image: UIImage? = nil
if let imageData = posts[indexPath.item].postPhoto {
image = UIImage.init(data: imageData)
}
cell.postPhoto.image = image
cell.postText.text = posts[indexPath.item].postText
var postLikes: String? = nil
if let likesData = posts[indexPath.item].postLikes {
postLikes = String(likesData)
}
cell.postLikes.text = postLikes
var postDate: String? = nil
if let dateData = posts[indexPath.item].postDate {
postDate = timestampToDate(dateData)
}
cell.postDate.text = postDate
return cell
}

You need to check that your object has value before assigning it to the IBOutelet. You need to change your cellForRowAtIndexPath something like this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
if let data = posts[indexPath.item].postPhoto) as? NSData {
cell.postPhoto.image = UIImage.init(data: data)
}
else {
cell.postPhoto.image = nil
}
if let postText = posts[indexPath.item].postText) as? String {
cell.postText.text = postText
}
else {
cell.postText.text = ""
}
if let postLikes = posts[indexPath.item].postLikes) as? Int {
cell.postLikes.text = String(postLikes)
}
else {
cell.postLikes.text = ""
}
if let postDate = posts[indexPath.item].postDate) as? NSDate {
cell.postDate.text = timestampToDate(postDate)
}
else {
cell.postDate.text = ""
}
return cell
}
Hope this will help you.

Related

Some cells are blank when I get data from API and showing it in collection view

I have a collection view and I'm fetching data from themoviedb api. I get the cast for each movie. And each cell contains profile image view, name label and character label. The problem is that some cells are blank when I get the data. Though I've configured all views in the cell, the problem was not resolved.
Very similar to the problem on this link
CollectionView duplicate cell when loading more data
Image of the problem
here is cellForItemAt function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch type {
case .cast:
if let castCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CreditsCollectionViewCell", for: indexPath) as? CreditsCollectionViewCell {
castCell.configureCastCell(with: self.cast[indexPath.row])
return castCell
}
return UICollectionViewCell()
case .crew:
if let crewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CreditsCollectionViewCell", for: indexPath) as? CreditsCollectionViewCell {
crewCell.configureCrewCell(with: self.crew[indexPath.row])
return crewCell
}
return UICollectionViewCell()
default:
return UICollectionViewCell()
}
}
CreditsViewController class
import UIKit
import SDWebImage
class CreditsCollectionViewCell: UICollectionViewCell {
//MARK: - Outlets
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var characterLabel: UILabel!
#IBOutlet weak var spinner: UIActivityIndicatorView!
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
characterLabel.text = nil
imageView.image = nil
}
//MARK: - Configure cell for the cast
public func configureCastCell(with cast: MovieCast) {
if let spinner = spinner {
spinner.startAnimating()
}
guard let profilePath = cast.profilePath else {
return
}
let path = "https://image.tmdb.org/t/p/original" + profilePath
if let profileUrl = URL(string: path) {
DispatchQueue.main.async {
self.imageView.sd_setImage(with: profileUrl, completed: nil)
if let spinner = self.spinner {
spinner.removeFromSuperview()
}
}
}
self.nameLabel.text = cast.name
self.characterLabel.text = cast.character
}
}

Why is the data in the collection view cells gets changing when I scroll the table view or the collection view?

I am trying to embed the collection view in the table view. When the page gets loaded I will retrieve the data field by field from the database and reloads the data whenever I retrieve the single field from the database. Here while reloading the table view I need to check the value i.e "oneimage" so if that value is not empty it should set to the collection view cell. The problem is whenever I scroll the table view the data in the collection view cells get swapped. Here is the code below
import UIKit
import Firebase
import FirebaseFirestore
import FirebaseAuth
import SDWebImage
struct values {
var quesvalue: String
var answvalue: String
var ImageUrl = [String]()
}
class QuestionsCell: UITableViewCell,UICollectionViewDelegate {
#IBOutlet weak var collectionview: UICollectionView!
#IBOutlet weak var card: UIView!
#IBOutlet weak var question: UILabel!
#IBOutlet weak var answer: UILabel!
#IBOutlet weak var speakbutton: UIButton!
#IBOutlet weak var collectionviewh: NSLayoutConstraint!
var imageArray = [String] ()
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
extension QuestionsCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource &
UICollectionViewDelegate>(dataSourceDelegate: D, forRow row: Int) {
collectionview.delegate = dataSourceDelegate
collectionview.dataSource = dataSourceDelegate
print("collectionviee.tag",collectionview.tag,row)
collectionview.tag = row
collectionview.contentOffset = .zero // Stops collection view if it was scrolling.
}
}
class CollectionViewCell: UICollectionViewCell{
#IBOutlet weak var backcard: UIView!
#IBOutlet weak var imageview: UIImageView!
var task: URLSessionDataTask?
override func awakeFromNib() {
super.awakeFromNib()
}
override func prepareForReuse(){
imageview.image = nil
}
}
class ViewController: UIViewController ,UITableViewDelegate,
UITableViewDataSource,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var tableview: UITableView!
var JSONArray = [String:Any]()
var quesArray = [String]()
var ansArray = [String]()
var answer : String!
var imagesarray = [String]()
var open : [values] = []
var oneimage = [String]()
var storedOffsets = [Int: CGFloat]()
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
tableview.rowHeight=UITableView.automaticDimension
tableview.estimatedRowHeight=150
Firestore.firestore().collection("User").document("7ngPwZin2wg7j5JZtI0hKJO8uSA2").collection("Popop").document("7ngPwZin2wg7j5JZtI0hKJO8uSA2").collection("Answers").document("Earlyyears").getDocument() { (document, error) in
if let document = document, document.exists {
self.open.removeAll()
self.imagesarray.removeAll()
self.oneimage.removeAll()
if let b1 = document.data()!["Name"] as? [String: Any] {
print("1",b1)
if let firstName = b1["Answer"] as? String {
print("firstName is",firstName)
if firstName != "No answer recorded"{
self.answer = firstName
self.ansArray.append(firstName)
if let imageurlarray = b1["ImageURL"] as? [String] {
self.imagesarray = imageurlarray
print("imageurl array in meaning feild is",imageurlarray)
self.open.insert(values(quesvalue: self.quesArray[0],answvalue: self.answer,ImageUrl: self.imagesarray), at: 0)
self.tableview.reloadData()
}
}
}
}
if let b2 = document.data()!["Meaning"] as? [String: Any] {
print("1")
if let firstName = b2["Answer"] as? String {
print("firstName is",firstName)
if firstName != "No answer recorded"{
self.answer = firstName
self.ansArray.append(firstName)
if let imageurlarray = b2["ImageURL"] as? [String] {
self.imagesarray = imageurlarray
print("imageurl array in meaning feild is",imageurlarray)
self.open.insert(values(quesvalue: self.quesArray[1],answvalue: self.answer,ImageUrl: self.imagesarray), at: 1)
self.tableview.reloadData()
}
}
}
}
} else {
print("Document does not exist")
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ansArry.count is",open.count)
return open.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("entered into cellfor row at")
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! QuestionsCell
print("quesrray,ansArray are",quesArray,ansArray,open)
if open.count > indexPath.row{
cell.question.text = open[indexPath.row].quesvalue
cell.answer.text = open[indexPath.row].answvalue
print("cell.ques.text",cell.question.text)
oneimage = open[indexPath.row].ImageUrl
print("onimage before checking",oneimage)
if !oneimage.isEmpty{
print("entered into oneimage not empty",oneimage)
cell.collectionview.isHidden = false
cell.collectionviewh.constant = 160
cell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row)
}
else{
print("dont show collection view")
cell.collectionview.isHidden = true
cell.collectionviewh.constant = 0
}
}
else{
print("<")
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("imagesarray.count is",oneimage.count)
print("oneimage.count")
return oneimage.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CollectionViewCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as? CollectionViewCell)!
if oneimage.count > indexPath.row{
if oneimage != [""] {
let image = oneimage[indexPath.row]
print("oneimage is",image)
print("entered into oneimage not empty")
cell.imageview.sd_setImage(with: URL(string: image))
}
}
return cell
}
Here are the screenshots of my output.
As I mentioned in the comments, this is because of the reusability. That means when a cell goes out from bottom/top, the same cell (containing the previous setup) comes in from top/bottom. So if you set something async, like a remote image on it, it may be visible on incorrect cell. You should make sure you are selecting correct cell when you are about to set the image on it.
For example you should change this:
cell.imageview.sd_setImage(with: URL(string: image))
to something like this:
(collectionView.cellForItem(at: indexPath) as? CollectionViewCell)?.imageview.sd_setImage(with: URL(string: image))
This will ask the collectionView for the real cell instead of the reused one. I don't know how sd library works, but you may want to do this in the completionHandler of the library.
Maybe this article could help you.

Xcode Swift Table View: Use of Slider and SegmentedControl as custom cells

I am currently using numerous custom cells in a tableview, of which consist of sliders and segmented controls. My issue is that in the simulator, when these rows are selected, the values of the sliders and pickers are reset to default. So for example, if I changed the value of the slider to 10, and then selected the row of the slider, the slider's value would reset to 0. I am trying to diagnose this issue so when the row is selected, the value remains the same; however, I am not sure where this problem arises. I have a hunch that it has something to do with .dequeueReusableCell or .reloadData, but I do not have a great grasp of what they do to start debugging.
Below is my code for cellForRowAt indexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
// I am using identifiers to set Titles within the TableView
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
eventType = ((cellDescriptors[0] as! NSMutableArray)[0] as! NSDictionary)["primaryTitle"]! as! String
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
cell.textField.placeholder = currentCellDescriptor["primaryTitle"] as? String
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellValuePicker" {
cell.textLabel?.text = currentCellDescriptor["primaryTitle"] as? String
}
cell.delegate = self
return cell
}
Code from CustomCell, my class for all my cells
//outlet properties
#IBOutlet weak var moneyLabel: UILabel!
#IBOutlet weak var moneySlider: UISlider!
#IBOutlet weak var privacy: UISegmentedControl!
#IBAction func sliderValueChanged(_ sender: UISlider) {
moneyValue = Int(sender.value)
moneyLabel.text = String(moneyValue)
moneyAmount = moneyLabel.text!
}
#IBAction func privacyChanged(_ sender: AnyObject) {
switch privacy.selectedSegmentIndex
{
case 0:
print("public")
privacyDescription = "public"
case 1:
print("private")
privacyDescription = "private"
default:
break;
}
}

Swift, preventing tableview cell resueable

How to solve the table view data overlapped issue?
I have searched some Object C version answer, but it seems not working for Swift. I tried to get cell==nil, but it gives me an error
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let current_patient = patients[indexPath.row]
let cell = myTable.cellForRowAtIndexPath(indexPath) as! PatientTableViewCell
if (cell){
let cell = myTable.dequeueReusableCellWithIdentifier("patientCell") as! PatientTableViewCell
}
let cell = myTable.dequeueReusableCellWithIdentifier("patientCell", forIndexPath: indexPath) as! PatientTableViewCell
if(cell != nil){
var subviews = cell.contentView.subviews
subviews.removeAll()
}
//configure cell
let type = current_patient.enrollable_type
cell.patientTypelbl.text = type
return cell
}
After two-days struggling, I figure it out finally. The key to solve it is to find which cell is been resued. What I am doing now is give a var identifier to cell.
class PatientTableViewCell: UITableViewCell {
#IBOutlet weak var followupBtn: UIButton!
#IBOutlet weak var viewBtn: UIButton!
#IBOutlet weak var titleType: UILabel!
#IBOutlet weak var titleID: UILabel!
#IBOutlet weak var patientTypelbl: UILabel!
#IBOutlet weak var patientIDlbl: UILabel!
**var identifier = false**
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let current_patient = patients[indexPath.row]
var cell = myTable.dequeueReusableCellWithIdentifier("patientCell") as! PatientTableViewCell
if(cell.identifier == true){
cell = myTable.dequeueReusableCellWithIdentifier("patientCell") as! PatientTableViewCell
}
//config cell
cell.identifier = true //important
I hope it can help someone else. :)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let current_patient = patients[indexPath.row]
let cell: PatientTableViewCell! = tableView.dequeueReusableCellWithIdentifier("patientCell") as? PatientTableViewCell
//configure cell
let type = current_patient.enrollable_type
cell.patientTypelbl.text = type
return cell
}

How do you change a fetchRequest to an Array to use in a UITableView

I am tying to put fetched data from coredata in a UITableView but I get this "EXC_BAD_INSTRUCTION" .
Using the let swiftBlogs Array works just fine, so can someone show my how to convert the fetch to an Array or is that not the correct way?
import UIKit
import CoreData
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var timeStampTextField: UITextField!
#IBOutlet var quickQuoteTextField: UITextField!
#IBOutlet var tableViewQuickQuote: UITableView!
let swiftBlogs = ["Ray Wenderlich", "NSHipster", "iOS Developer Tips", "Jameson Quave", "Natasha The Robot", "Coding Explorer", "That Thing In Swift", "Andrew Bancroft", "iAchieved.it", "Airspeed Velocity"]
var tableViewCellArray : Array<AnyObject> = []
var quickQuoteArray : Array<AnyObject> = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
var appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "QuickQuote" )
request.returnsObjectsAsFaults = false
tableViewCellArray = context.executeFetchRequest(request, error: nil)!
}
override func viewWillAppear(animated: Bool) {
quickQuoteTextField.text = ""
timeStampTextField.text = ""
}
#IBAction func clearButton(sender: AnyObject) {
quickQuoteTextField.text = ""
timeStampTextField.text = ""
}
#IBAction func addToQuickQuoteButton(sender: AnyObject) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("QuickQuote", inManagedObjectContext: context)
var newQuickQuote = QuickQuote(entity: ent!, insertIntoManagedObjectContext: context)
newQuickQuote.quickQuote = quickQuoteTextField.text
context.save(nil)
}
#IBAction func timeStampButton(sender: AnyObject) {
timeStamp()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("Time", inManagedObjectContext: context)
var newTime = Time(entity: ent!, insertIntoManagedObjectContext: context)
newTime.time = timeStampTextField.text
newTime.quote = quickQuoteTextField.text
context.save(nil)
}
func timeStamp (){
timeStampTextField.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: NSDateFormatterStyle.FullStyle,
timeStyle: NSDateFormatterStyle.ShortStyle)
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return swiftBlogs.count // return quickQuoteArray.count
}
private let stampCellID: NSString = "cell" //This is the cell itself's identifier.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(stampCellID as String, forIndexPath: indexPath) as! UITableViewCell
var data: NSManagedObject = quickQuoteArray[indexPath.row] as! NSManagedObject
cell.textLabel?.text = data.valueForKey("quickQuote") as? String
// let row = indexPath.row
// cell.textLabel?.text = swiftBlogs[row]
return cell
}
/*
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
var appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context:NSManagedObjectContext = appDel.managedObjectContext!
if editingStyle == UITableViewCellEditingStyle.Delete {
let tv = tableView
context.deleteObject(quickQuoteArray.self[indexPath.row] as! NSManagedObject)
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
context.save(nil)
}
*/
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
You're mixing up your arrays swiftBlogs and quickQuoteArray. Whether or not the table view tries to access an array element quickQuoteArray[indexpath.row] is dependent on if it thinks that index is populated, based on the result from numberOfRowsInSection. In the numberOfRowsInSection method, you are returning the count of swiftBlogs, which is always the 10 or so strings you hand-typed in. So before your request is ever even executed, or the view even has a chance to populate anything else, it's trying to show elements that aren't present in the array you're using in cellForRowAtIndexPath.
In short:
Always use the same array in cellForRowAtIndexPath as you are using in numberOfRowsInSection. Here, you've mixed two different arrays, quickQuoteArray and swiftBlogs.