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?
Related
I wanted to create a custom view. So I designed my view like so..it's called TagResolutionView.xib
This is how I designed my view
And the TagResolutionView.swift file for that looks like this
import UIKit
#IBDesignable class TagResolutionView: UIView {
#IBOutlet var tagResolutionView: UIView!
#IBOutlet var tableview: UITableView!
required init?(coder: NSCoder) {
super.init(coder: coder)
tableview = UITableView()
}
override init(frame: CGRect) {
tableview = UITableView()
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBOutlet weak var delegate: UITableViewDelegate? {
get {
return tableview.delegate
}
set {
tableview.delegate = newValue
}
}
#IBOutlet weak var dataSource: UITableViewDataSource? {
get {
return tableview.dataSource
}
set {
tableview.dataSource = newValue
}
}
func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String) {
tableview.register(cellClass, forCellReuseIdentifier: "cellClass")
}
func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell? {
return tableview.dequeueReusableCell(withIdentifier: identifier)
}
override func layoutSubviews() {
super.layoutSubviews()
tableview.frame = self.bounds
self.addSubview(tableview)
}
}
This is how I inserted the view in my main viewcontroller where I want the custom view to appear..
This is the view
This is how I configured it in the viewcontroller..
In the viewDidLoad..
stdProcedViewHeight.constant = 533 //Since the view I took above (as given in the pic) is less than the height of the actual TagResolutionView.xib, I increased its height here programatically.
standardsProceduresView.delegate = self
standardsProceduresView.dataSource = self
standardsProceduresView.registerClass(cellClass: UpdatingListTableViewCell.self, forCellReuseIdentifier: "cellClass") //I have also made .xib & .swift files for UpdatingListTableViewCell
Also added these methods..
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UpdatingListTableViewCell = self.standardsProceduresView.dequeueReusableCellWithIdentifier(identifier: "cellClass") as! UpdatingListTableViewCell
// cell.nameLbl.text = "MyName"
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
But I'm not able to get the desired output by doing all of this...All I get is just an empty tableview of the set height(height of 533 here) and nothing else of the design...
The reason why your table view doesn't show up is that you're resetting all of its content by calling tableView = UITableView() in your code.
It seems like you're using Storyboards, so assuming you properly connected your UITableView to the property outlet #IBOutlet var tableview: UITableView!, your table view will appear just fine and you don't need to initialize it via code.
I can also see that you're setting the frame of your table view and adding it to the view hierarchy in the layoutSubviews() method. That's definitely not something you need/want to do, first because you're using Storyboards and it means that your table view will already be part of the view hierarchy at this point.
I'd also mention that you should generally never add a subview in the layoutSubviews() method, because this method may be called multiple times and it means your view would be added multiple times to the view hierarchy, which would end up wasting memory.
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")
}
}
I am trying to programmatically design the layout of my custom UITableView cells. However, I get some strange behavior: despite setting the rowHeight, the first four rows are the default height, and then the rest are what I specified. This impacts the design of my cell, because I programmatically lay out labels based in part on the row height.
I initialize the tableview as follows:
func gameTableInit() {
gameTableView = UITableView()
gameTableView.delegate = self
gameTableView.dataSource = self
let navFrame = self.navigationController?.view.frame
gameTableView.frame = CGRect(x: navFrame!.minX, y: navFrame!.maxY, width: self.view.frame.width, height: self.view.frame.height - navFrame!.height - (self.tabBarController?.view.frame.height)!)
gameTableView.rowHeight = gameTableView.frame.height/4 //HEIGHT OF TABLEVIEW CELL
gameTableView.register(GameCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(gameTableView)
}
This is my cellForRowAtIndexPath function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! GameCell
print("height of cell: \(cell.frame.height)")
var game = Game()
game.awayTeam = "CLE"
game.homeTeam = "GSW"
cell.setUIFromGame(g: game)
return cell
}
The print statement prints:
height of cell: 44.0
height of cell: 44.0
height of cell: 44.0
height of cell: 44.0
After some scrolling, it then prints the expected:
height of cell: 166.75
...
I have created a custom tableview cell called GameCell
class GameCell: 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 func layoutSubviews() {
super.layoutSubviews()
positionUIElements()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//on left
var awayTeamAbbrev = UILabel()
var awayTeamLogo: UIImage!
//on right
var homeTeamAbbrev = UILabel()
var homeTeamLogo: UIImage!
func positionUIElements() {
//away team abbreviation is centered on upper left half of cell
awayTeamAbbrev.frame = CGRect(x: self.frame.minX, y: self.frame.minY, width: self.frame.midX, height: self.frame.height/4)
awayTeamAbbrev.textAlignment = .center
awayTeamAbbrev.adjustsFontSizeToFitWidth = true
//awayTeamLogo
//home team abbreviation is centered on upper right half of cell
homeTeamAbbrev.frame = CGRect(x: self.frame.midX, y: self.frame.minY, width: self.frame.midX, height: self.frame.height/4)
homeTeamAbbrev.textAlignment = .center
homeTeamAbbrev.adjustsFontSizeToFitWidth = true
//homeTeamLogo
self.contentView.addSubview(awayTeamAbbrev)
self.contentView.addSubview(homeTeamAbbrev)
}
func setUIFromGame(g: Game) {
awayTeamAbbrev.text = g.awayTeam!
homeTeamAbbrev.text = g.homeTeam!
}
}
I have tried many of the suggested answers online like calling layoutIfNeeded, but that didn't work anywhere I tried it.
While accepted answer can help in some cases I would like to share some other ways to fix this issue:
Check Content Hugging Priority of your cell's components (especially if you're using stack views inside cell)
In your cell add:
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
I found a working solution: In my GameCell class, I overrided layout subviews:
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.layoutIfNeeded()
positionUIElements()
self.awayTeamAbbrev.preferredMaxLayoutWidth = self.awayTeamAbbrev.frame.size.width
self.homeTeamAbbrev.preferredMaxLayoutWidth = self.homeTeamAbbrev.frame.size.width
}
44 is defailt height for UITableViewCell in UITableView, try to override func
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
or return exact value, in your case 166.75
I had the same problem using UITableViewDiffableDataSource. The tableView set the correct cell height after I attempt to scroll manually the content.
The tableView was inside on an UIViewController. The dataSource was applied on the viewDidLoad method.
I reloaded the tableView content using tableView.reloadData() inside the viewWillAppear and the problem was solve.
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")
}
}