So I am pretty new to iOS development. I try to create everything programmatically so my Storyboard is empty. I'm currently trying to get a TableView with custom cells. The TableView is running and looking fine when I use the standard UITableViewCell. I created a very simple class called "GameCell". Basically, I want to create a cell here with multiple labels and maybe some extra UIObjects in the future (imageView etc.). For some reason, the custom cells do not show up.
Game cell class:
class GameCell: UITableViewCell {
var mainTextLabel = UILabel()
var sideTextLabel = UILabel()
func setLabel() {
self.mainTextLabel.text = "FirstLabel"
self.sideTextLabel.text = "SecondLabel"
}
}
Here the additional necessary code to get the number of rows and return the cells to the TableView which I have in my ViewController. self.lastGamesCount is just an Int here and definitely not zero when I print it.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.lastGamesCount
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! GameCell
In my viewDidLoad() I register the cells like this:
tableView.register(GameCell.self, forCellReuseIdentifier: cellID)
When I run everything the Build is successful I can see the navigation bar of my App and all but the TableView is empty. I go back to the normal UITableViewCell and the cells are showing up again. What am I missing here? Any help is appreciated.
Thanks!
The problem is you need to set constraints for these labels
var mainTextLabel = UILabel()
var sideTextLabel = UILabel()
after you add them to the cell
class GameCell: UITableViewCell {
let mainTextLabel = UILabel()
let sideTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setLabel()
}
func setLabel() {
self.mainTextLabel.translatesAutoresizingMaskIntoConstraints = false
self.sideTextLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(mainTextLabel)
self.contentView.addSubview(sideTextLabel)
NSLayoutConstraint.activate([
mainTextLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
mainTextLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
mainTextLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor,constant:20),
sideTextLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
sideTextLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
sideTextLabel.topAnchor.constraint(equalTo: self.mainTextLabel.bottomAnchor,constant:20),
sideTextLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor,constant:-20)
])
self.mainTextLabel.text = "FirstLabel"
self.sideTextLabel.text = "SecondLabel"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Related
I've got a tableViewCell that I need to have an array passed into the tableViewCell but not just passed into a text label or something like that. I'll let my code show.
My TableViewController:
let subjectsDict = ["Spanish": ["Lesson 1", "Lesson 2"], "Math":["Problem set 1", "Problem set 2"], "Science": ["Lab"]]
let subjectArray = ["Spanish", "Math", "Science"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as? SubjectTableViewCell else {
return UITableViewCell()
}
cell.subjectList = subjectsDict[subjectArray[indexPath.row]]
return cell
}
And my tableViewCell looks like this.
class subjectTableViewCell: UITableViewCell {
var subjectList: [String] = []
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style , reuseIdentifier: reuseIdentifier)
setUpTable()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
override func awakeFromNib() {
super.awakeFromNib()
setUpTable()
}
func setUpTable() {
print(subjectList)
}
//other code for creating the cell
}
But when I print the subjectList from the subjectTableViewCell it prints none
Your code makes no attempt to update the cell's content with the value of subjectList. All you show is a print.
Also note that your print is called before any attempt to set subjectList is made. And remember that cells get reused. setUpTable will only be called once but subjectList will be set over and over as the cell gets used.
The simplest solution is to update the cell when subjectList is set.
var subjectList: [String] = [] {
didSet {
textLabel?.text = subjectList.joined(separator: ", ")
}
}
I'm assuming you are using the standard textLabel property. If you have your own label then update accordingly.
If you just want to invoke setUpTable() when your subjectList in the cell gets updated, try using:
var subjectList: [String] = [] {
didSet {
setUpTable()
}
}
You're trying to print subjectList in the moment you initialise your table view cell so in this moment you haven't set subjectList yet. If you want to print subjectList you can do it after you set it.
After this line gets executed:
cell.subjectList = subjectsDict[subjectArray[indexPath.row]]
I'm trying to create a dynamic UITableView in a xib, so what I think might work is to put the table view inside a blank UIView. Then I would subclass this UIView and make it adhere to the protocols UITableViewDelegate and UITableViewDataSource. Can someone guide me with this, because I've tried many things, but none of them worked. Many Thanks!
EDIT:
I'll show you what I tried before (sorry, don't have the original code):
class TableControllerView: UIView, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
tableView.frame = self.frame
tableView.delegate = self
tableView.dataSource = self
}
func tableView(...cellForRowAt...) {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.titleLabel?.text = "test title"
return cell
}
}
but in this case the table view ended up being too big and not aligned with the view, even though I set it's frame to be the same as the view
EDIT 2 :
My code now looks like this:
class PharmacyTableView: UIView, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var pharmacyTableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//PROVISOIRE depends on user [medications].count
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = "text label test"
cell.detailTextLabel?.text = "detail label test"
return cell
}}
table view is initialized but only shows up when height anchor is constrained, which I don't want because it might grow or shrink depending on user data. I guess I'll be done after solving this?
P.S. : Also, thank you very much to the people that took the time to help me :)
EDIT 3:
So, I've changed the class of the table view to this:
class IntrinsicResizingTableView: UITableView {
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}
}
And now everything works fine! Finally!
Your question is unclear. Based on your final statement the following code is what you should use to keep the table view and its superview aligned.
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
If this is not the answer you're looking for, please clarify your problem. Also it would help if you explain what you're doing this for? Why do you need this superview?
I'm trying to add some views in code to the contentView of a table view cell. However nothing shows up. I get an empty cell. Below is the code of me adding subviews to the content view of a custom cell.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
func setupViews() {
//feedback_title setup
feedback_title.text = Constants.IndividualStudiesPage.FEEDBACK_TITLE
feedback_title.numberOfLines = 0
feedback_title.lineBreakMode = .ByTruncatingTail
feedback_title.preferredMaxLayoutWidth = standard_height_width
//feedback_box setup
feedback_box.text = Constants.IndividualStudiesPage.DEFAULT_FEEDBACK
// TODO: Change to infinte (0) number of lines. Height constraint interferes with this
feedback_box.numberOfLines = 2
feedback_box.preferredMaxLayoutWidth = standard_height_width
contentView.addSubview(feedback_title)
contentView.addSubview(feedback_box)
}
Here's the interesting thing. When I add some dummy view like an empty UILabel to the content view in the storyboard, then everything shows up including the views I added in the code. Here's a picture to show you what I mean:
But when I don't add any dummy view then nothing shows up...Why and how can I fix this? Thanks in advance.
EDIT:
The feedback_cell and feedback_box are instantiated like so at the top of the class
class FeedbackCell: UITableViewCell {
var feedback_title : UILabel = UILabel.newAutoLayoutView()
var feedback_box : UILabel = UILabel.newAutoLayoutView()
newAutoLayoutView is a PureLayout function that essentially just instantiates the labels to empty views. I then update the AutoLayout constraints, also using PureLayout in the updateConstraints function:
override func updateConstraints() {
if !did_update_constraints {
//prevent labels from being compressed below intrinsic height but also from taking too much height
NSLayoutConstraint.autoSetPriority(UILayoutPriorityRequired){
self.feedback_title.autoSetContentHuggingPriorityForAxis(.Vertical)
self.feedback_title.autoSetContentCompressionResistancePriorityForAxis(.Vertical)
self.feedback_box.autoSetContentHuggingPriorityForAxis(.Vertical)
self.feedback_box.autoSetContentCompressionResistancePriorityForAxis(.Vertical)
}
//height constraints
feedback_title.autoSetDimension(.Height, toSize: standard_height_width)
//feedback_title constraints
feedback_title.autoPinEdgeToSuperviewMargin(.Leading)
feedback_title.autoPinEdgeToSuperviewMargin(.Trailing, relation: NSLayoutRelation.LessThanOrEqual)
feedback_title.autoPinEdgeToSuperviewMargin(.Top)
//feedback_box constraints
feedback_box.autoPinEdge(.Top, toEdge: .Bottom, ofView: feedback_title, withOffset: 10, relation: .GreaterThanOrEqual)
feedback_box.autoPinEdgeToSuperviewMargin(.Leading)
feedback_box.autoPinEdgeToSuperviewMargin(.Trailing, relation: NSLayoutRelation.LessThanOrEqual)
feedback_box.autoPinEdgeToSuperviewMargin(.Bottom)
did_update_constraints = true
}
super.updateConstraints()
View hierarchy debugger:
Tableview code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.row) {
case 0:
return setupDescriptionCell(tableView, indexPath: indexPath)
case 1:
return setupFeedbackCell(tableView, indexPath: indexPath)
case 2:
return setupStatsCell(tableView, indexPath: indexPath)
case 3:
return setupSurveyCell(tableView, indexPath: indexPath)
default:
return setupDefaultCell(tableView, indexPath: indexPath)
}
}
func setupFeedbackCell(tableView : UITableView, indexPath : NSIndexPath)->UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cell_IDs[1], forIndexPath: indexPath) as! FeedbackCell
//in the future download feedback in view did load and adjust here
return cell
}
I'm have a table view and I'm using the tableView.dequeueReusableCellWithIdentifier to reuse the cells but still tableView is very slow.
and by slow, I mean it takes about 500 milliseconds to put 9 of my views in the tableView. and it's tested on apple A7 X64 processor so it must be pretty slower on older processors.
the reason that it's slow is because there are a few sub views and constraints.
but I've seen more complex tableCells with better performance, so there must be something I can do.
like caching a cell or something else??
any ideas?
sample code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
tableView.registerNib(UINib(nibName: "ChatCell", bundle: nil), forCellReuseIdentifier: "ChatCell")
let cell = tableView.dequeueReusableCellWithIdentifier("ChatCell") as! ChatCell
return cell
}
the reason that it's slow is because there are a few sub views and constraints.
Personally, I don't suggest you use constraints in cell, especially when there're many subviews, it'll cost much CPU time and lead the scrolling lag. Instead, you can calculate manually based on cell frame.
And for more suggestion, i suggest you take time to read this post: Simple Strategies for Smooth Animation on the iPhone.
The call to registerNib is normally done only once in viewDidLoad, not every time you are asked for a cell in cellForRowAtIndexPath. Not sure how slow that call is, but it might be the reason for your slow response.
I think you are using effects (like shadow or round corners or etc) or having heavy calculations on UI
Edit: Code Sample added
//Add in your init func
tblView.registerClass(MSCustomVerticalListCell.self, forCellReuseIdentifier: NSStringFromClass(MSCustomVerticalListCell))
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tblView.dequeueReusableCellWithIdentifier(NSStringFromClass(MSCustomVerticalListCell), forIndexPath: indexPath) as! MSCustomVerticalListCell
//add data binding
cell.item = dataSource[indexPath.row]
return cell
}
Your data binding class (Data Model):
class MSC_VCItem
{
var Title:String!
var Action:String!
var SubTitle:String!
var Icon:String!
init(title:String!,subTitle:String!,icon:String!,action:String!)
{
self.Title = title
self.SubTitle = subTitle
self.Icon = icon
self.Action = action
}
}
And Finally you custom table cell:
class MSCustomVerticalListCell : UITableViewCell {
let padding = 5
let imageWidth = 50
var customImageView: UIImageView!
var customTitleLabel: UILabel!
var customSubtitleLabel: UILabel!
var item: MSC_VCItem? {
didSet {
if let it = item {
customTitleLabel.text = it.Title
customSubtitleLabel.text = it.SubTitle
UIImage.loadFromCacheOrURL(it.Icon, callback: { (image: UIImage) -> () in
self.customImageView.image = image
})
setNeedsLayout()
}
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.clearColor()
customTitleLabel = UILabel(frame: CGRectZero)
self.addSubview(customTitleLabel)
customSubtitleLabel = UILabel(frame: CGRectZero)
contentView.addSubview(customSubtitleLabel)
customImageView = UIImageView(frame: CGRectZero)
customImageView.image = UIImage(named: "default")
contentView.addSubview(customImageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
//Write your UI here like bg color or text color
}
}
I am trying to use auto sizing UITableView cells in swift with snapKit! relevant flags set on the UITableView are as follows:
self.rowHeight = UITableViewAutomaticDimension
self.estimatedRowHeight = 70.0
I have a UITextField defined in my customUITableviewCell class like:
var uidTextField: UITextField = UITextField()
and the initial setup of the text field in my custom UITableViewCell looks like this:
self.contentView.addSubview(uidTextField)
uidTextField.attributedPlaceholder = NSAttributedString(string: "Woo Hoo", attributes: [NSForegroundColorAttributeName:UIColor.lightGrayColor()])
uidTextField.textAlignment = NSTextAlignment.Left
uidTextField.font = UIFont.systemFontOfSize(19)
uidTextField.returnKeyType = UIReturnKeyType.Done
uidTextField.autocorrectionType = UITextAutocorrectionType.No
uidTextField.delegate = self
uidTextField.addTarget(self, action: "uidFieldChanged", forControlEvents: UIControlEvents.EditingChanged)
uidTextField.snp_makeConstraints { make in
make.left.equalTo(self.contentView).offset(10)
make.right.equalTo(self.contentView)
make.top.equalTo(self.contentView).offset(10)
make.bottom.equalTo(self.contentView).offset(10)
}
when I run the code it shows up cut off and gives me an error in the console that reads:
Warning once only: Detected a case where constraints ambiguously
suggest a height of zero for a tableview cell's content view. We're
considering the collapse unintentional and using standard height
instead.
Is there something wrong with my autoLayout constraints or is this an issue with UIControls and autosizing of UITableView cells?
In SnapKit (and Masonry) you have to use negative values to add a padding to the right or bottom of a view. You are using offset(10) on your bottom constraint which causes the effect that the bottom 10pt of your text field will get cut off.
To fix this you have to give your bottom constraint a negative offset:
uidTextField.snp_makeConstraints { make in
make.left.equalTo(self.contentView).offset(10)
make.right.equalTo(self.contentView)
make.top.equalTo(self.contentView).offset(10)
make.bottom.equalTo(self.contentView).offset(-10)
}
Or you could get the same constraints by doing this:
uidTextField.snp_makeConstraints { make in
make.edges.equalTo(contentView).inset(UIEdgeInsetsMake(10, 10, 10, 0))
}
When you are using the inset() way you have to use positive values for right and bottom inset.
I don't understand why SnapKit uses negative values for bottom and right. I think that's counterintuitive and a bit confusing.
EDIT: This is a little example that is working fine (I hardcoded a tableView with 3 custom cells that include a UITextField):
ViewController:
import UIKit
import SnapKit
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 70
tableView.registerClass(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
tableView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(view)
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath)
return cell
}
}
CustomTableViewCell:
import UIKit
class CustomTableViewCell: UITableViewCell {
let textField = UITextField()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
textField.attributedPlaceholder = NSAttributedString(string: "Woo Hoo", attributes: [NSForegroundColorAttributeName:UIColor.lightGrayColor()])
textField.textAlignment = NSTextAlignment.Left
textField.font = UIFont.systemFontOfSize(19)
textField.returnKeyType = UIReturnKeyType.Done
textField.autocorrectionType = UITextAutocorrectionType.No
contentView.addSubview(textField)
textField.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(contentView).inset(UIEdgeInsetsMake(10, 10, 10, 0))
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}