UITableViewCell with a UIView subclass creates multiple layers on cell - swift

I have an custom UITableViewCell created with (.xib) and have UIView subclass instance inside cell created from code
table delegate method :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCellOne", forIndexPath: indexPath) as! CustomOneCell
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.addBehavior(self.modulesForSubjects[indexPath.row])
// remove subviews
for subview in cell.subviews{
subview.removeFromSuperview();
}
// then add your view
cell.wiView = woView
return cell
}
my full UIView subclass :
class ModuleView: UIView {
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func addBehavior (ArrayOfmodulesForSubject: [SubjectsModules]) {
var number :CGFloat = 30
var number2 :CGFloat = 30
print(ArrayOfmodulesForSubject.count)
for (i, index) in ArrayOfmodulesForSubject.enumerate() {
let yourLabel1 = UILabel(frame: CGRectMake(number, 40 , 35 , 30))
let yourLabel2 = UILabel(frame: CGRectMake(number2, 60 , 140 , 30))
yourLabel1.textColor = UIColor.whiteColor()
yourLabel1.backgroundColor = UIColor.redColor()
yourLabel1.text = String(index.score)
yourLabel2.textColor = UIColor.whiteColor()
yourLabel2.backgroundColor = UIColor.blackColor()
yourLabel2.text = index.date
self.addSubview(yourLabel1)
self.addSubview(yourLabel2)
number += 30
number2 += 70
}
}
here is what i get after some scrolling:
UPDATE rnsjtngus solution, unfortunately also didn't helped (maybe im doing something wrong)
class CustomOneCell: UITableViewCell {
var arr = [SubjectsModules]()
#IBOutlet weak var wiView: ModuleView!
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
// Initialize your custom cell
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setupWoView(self.arr)
}
override func awakeFromNib() {
print("AWAKED FROM NIB")
for subview in subviews{
subview.removeFromSuperview()
if superview != nil {
superview!.removeFromSuperview()
}
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupWoView(arrayOfmodulesForSubject: [SubjectsModules]) {
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.backgroundColor = UIColor.blueColor()
self.addSubview(woView)
}
}

Simple but, bad solution is
just remove subviews from cell before you add your own view
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCellOne", forIndexPath: indexPath) as! CustomOneCell
let woView = ModuleView(frame: CGRect(x: 10, y: 10, width: 400, height: 400))
woView.addBehavior(self.modulesForSubjects[indexPath.row])
// remove subviews
for subview in cell.subviews{
subview.removeFromSuperview();
}
// then add your view
cell.addSubview(woView)
return cell
}

When you call dequeue the method dequeues an existing cell if one is available or creates a new one using the class or nib if not.
This means you may well get one that has already had a subview added to it and when you dequeue it again your code would then add a second one. You need to start by removing any existing subviews.

Related

Programmatic swift: TableView inside a TableView

I'm currently trying to put a tableview inside a tableview, but programmatically (in order to understand better wtf I'm doing in the storyboard way of doing this). I'm currently stuck at showing the inner table, any help please?
class ContactsViewController: UIViewController, UITabBarDelegate,
UITableViewDataSource, UITableViewDelegate {
private let contacts = ContactAPI.getData()
let contactsTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contactsTableView)
contactsTableView.translatesAutoresizingMaskIntoConstraints = false // enable Auto-Layout
contactsTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contactsTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contactsTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
contactsTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contactsTableView.dataSource = self
contactsTableView.delegate = self
contactsTableView.register(ContactsCell.self, forCellReuseIdentifier: "contactCell")
contactsTableView.rowHeight = 1000
contactsTableView.estimatedRowHeight = 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactsCell
cell.jobLabel.text = contacts[indexPath.row].jobTitle
cell.nameLabel.text = contacts[indexPath.row].name
return cell
}
}
The external TableViewCell is given by
class ContactsCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(image)
contentView.addSubview(nameLabel)
contentView.addSubview(jobLabel)
contentView.addSubview(tablello)
}
// working code that defines image, nameLabel, jobLabel
let tablello: UITableView = {
let tab = UITableView()
let data = innerTable()
tab.dataSource = data
tab.delegate = data
tab.frame = CGRect (x: 0, y: 0, width: 300, height: 300)
tab.register(gInnerCell.self, forCellReuseIdentifier: "gennaroCell")
tab.translatesAutoresizingMaskIntoConstraints = false
return tab
}()
}
And relative classes
class innerTable: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gennaroCell", for: indexPath) as! gInnerCell
cell.labelloNapoli.text = "\(indexPath.row)"
print("AO GENNĂ ")
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("loaded inner")
}
}
class gInnerCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(labelloNapoli)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let labelloNapoli:UILabel = {
let lable = UILabel()
lable.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
return lable
}()
}
I've tried turning tablello into a separate View, and adding there the table as a subview, but (although it printed the inside print() ) it messes up all my UI.
In general what are you trying to accomplish by putting a tableView inside of another tableView? Are you able to display the same information using a custom tableViewCell? Or better yet use a CollectionView as they are very flexible when it comes to UI. Sorry for not answering the question; Just trying to get a better understanding of what you are trying to accomplish UI wise.

Custom Table View outlets throwing nil

I am trying to implement a simple iOS app using the VIPER pattern following this tutorial. The app is a simple table view which displays data from a json req
I have created the table view and can successfully show data from my object using the default cell.textLabel. I am however trying to create a custom table view cell and so created the nib and class. I have connected all the outlets up correctly to the class from the nib and the code for this class is as follows:
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableView"
static func nib() -> UINib {
return UINib(nibName: "CustomTableViewCell", bundle: nil)
}
#IBOutlet var level: UILabel!
#IBOutlet var levelNumber: UILabel!
#IBOutlet var progress: UIProgressView!
#IBOutlet var leftProgress: UILabel!
#IBOutlet var rightProgress: UILabel!
public func configure(levelString: String, levelNum: String, prog: Float, left: String, right: String) {
level.text = levelString
levelNumber.text = levelNum
progress.setProgress(prog, animated: true)
leftProgress.text = left
rightProgress.text = right
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
The problem is, when I run the app, these outlets are erroring: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value on level.text inside the configure function. I can suppress this error by doing this in my view:
table.register(CustomTableViewCell.nib(), forCellReuseIdentifier: CustomTableViewCell.identifier) however this just shows an empty tableView without the custom rows or any data. If I do table.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier) then this crashes. What way should I be doing it and how can I get the data to display in the tableView? relevant code added below:
let tableView: UITableView = {
let table = UITableView()
table.register(CustomTableViewCell.nib(), forCellReuseIdentifier: CustomTableViewCell.identifier)
table.isHidden = true
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
label.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
label.center = view.center
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return achievements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as! CustomTableViewCell
cell.configure(levelString: "Level",
levelNum: achievements[indexPath.row].level,
prog: Float(achievements[indexPath.row].progress/achievements[indexPath.row].total),
left: String(achievements[indexPath.row].progress),
right: String(achievements[indexPath.row].total))
return cell
}
Outlet Setup:
Files Owner:
I appreciate this might not be the correct answer for everyone, but this is what worked for me. I was struggling with trying to debug this and for the sake of time, I concluded that just creating everything programmatically would be easier. There are numerous tutorials for this, but I took the following structure:
Stevia library used to set constraints for components and add subview
import Stevia
import UIKit
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableViewCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupFieldsAndConstraints()
}
func setupFieldsAndConstraints() {
subviews {
customLabel
}
setupCustomLabel()
}
var customLabel: UILabel = {
let level = UILabel()
return level
}()
func setupCustomLabel() {
customLabel.style { l in
l.textAlignment = .left
l.textColor = .white
l.Top == 50
l.Left == 20
}
}
func setValues(object: Object) {
label.text = object.name
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And this is referenced in your view as it normally would be. I chose to add mine to a function that encapsulates any table configuration and then called in viewDidLoad:
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
And then for the cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier) as! CustomTableViewCell
cell.setValues(object: Object[indexPath.row])
return cell
}

Reusable UICollectionViewCell not changing UIImageView property (showing duplicates)

I have a UICollectionView with a reusable cell that contains a UIImageView but the collection view displays the same image in the image view for each cell item in the collection.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let path = indexPath.item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "friendAvatar", for: indexPath) as! AvatarCollectionViewCell
cell.avatarView.image = friends[path].userAvatar
return cell
}
class AvatarCollectionViewCell: UICollectionViewCell {
var avatarView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
prepareForReuse()
config()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
avatarView.image = nil
avatarView.removeFromSuperview()
}
func config() {
avatarView.frame = CGRect(x: 0, y: 0, width: 64, height: 64)
avatarView.layer.cornerRadius = 64 / 4
avatarView.clipsToBounds = true
avatarView.contentMode = .scaleAspectFill
addSubview(avatarView)
}
}
First of all, you are removing the image view from its superview in prepareForReuse() but you never add it back. init() will not be called when reusing a cell, and neither will config(). Unless you have a specific reason for it, you shouldn't need to remove the image view from its superview. Just setting its image to nil should be enough.
Also, set a breakpoint and verify that friends[path].userAvatar returns a valid path to an image.

Unable To Create a Custom Table View Cell programatically, Swift

I have followed this stackoverflow question & answer as guide to create a custom cell inside table view (without adding a prototype cell in the storyboard), However I was not able to create a table view with the custom cells, I just get rows of empty cells. I did look at other SO questions but they did not help.
I believe I am doing something wrong with the way I register the cell.
(FYI: When I add a prototype cell to my table view and assign it a class and cell id and if I then disable tableView.register(AppointmentTVCell.self, forCellReuseIdentifier: cellId) it works, I am not interested in this since I want to minimise the use of IB)
Below is my tableViewController in storyboard, to which a table view class AppointmentsTVC has been assigned
Below is my code for the Table View Controller
class AppointmentsTVC: UITableViewController {
let cellId = "reuseIdentifier"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(AppointmentTVCell.self, forCellReuseIdentifier: cellId)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cell for Row at func called")
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! AppointmentTVCell // Creating a cell
cell.caseId.text = "123456"
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150.0
}
}
Below is my code for the custom table view cell
class AppointmentTVCell: UITableViewCell {
let caseId: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
label.numberOfLines = 2
//label.text = "Hello"
label.textColor = UIColor.black
return label
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
contentView.addSubview(caseId)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
use the didMoveToSuperview method
class AppointmentTVCell: UITableViewCell {
let caseId: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
label.numberOfLines = 2
//label.text = "Hello"
label.textColor = UIColor.black
return label
}()
override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview != nil {
contentView.addSubview(caseId)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I have come to know that without a NIB file, awakeFromNib won't be invoked, so the view wasn't being added.
By looking at this Stackoverflow question I modified the code to the below and it worked.
class AppointmentTVCell: UITableViewCell {
var caseId = UILabel()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//Do your cell set up
caseId.frame = CGRect(x: 0, y: 50, width: 100, height: 30)
caseId.text = "Hello"
caseId.font = UIFont(name:"HelveticaNeue-Bold", size: 16)
contentView.addSubview(caseId)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

Layout constraints for custom UITtableViewCell not working

I'm creating custom UITableViewCells with a UILabel in each but can't get the label to display anywhere else than the top left corner of the cell, looks like this.
Constraints don't seem to be applied yet they're being called (reaching breakpoint). I tried to replace the UILabel by an UIImageView and apply the same constraints but nothing appears (i.e. table view cells are blank).
What am I missing?
View for cells:
import UIKit
class myTableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 16, weight: UIFontWeightLight)
label.text = "Sample"
label.backgroundColor = UIColor.red
return label
}()
func setupViews () {
addSubview(label)
//add constraints
let marginsGuide = self.contentView.layoutMarginsGuide
label.leadingAnchor.constraint(equalTo: marginsGuide.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: marginsGuide.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: marginsGuide.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: marginsGuide.bottomAnchor).isActive = true
}
}
View controller:
import UIKit
class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myTableView: UITableView = UITableView()
var myArray = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
//... do stuff incl. loading data into my myArray
let screenSize: CGRect = UIScreen.main.bounds
self.myTableView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
self.myTableView.delegate = self
self.myTableView.dataSource = self
self.myTableView.register(IngredientListTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(myTableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! myTableViewCell
return cell
}
}
Change this
addSubview(label)
to this
contentView.addSubview(label)