table view with dynamic table rows and with NSLayoutConstraint’s programmatically - ios

I implemented a table view with dynamic table rows and with NSLayoutConstraint’s programmatically. However, I encounter differences between the iPhone 5/5s/6s and iPhone 6s plus simulator.
What I basically did:
Creating a UIView (containerView) on a ScrollView (scrollView)
Creating a UITableView (infoTableView) on the containerView
Defining dynamic row heights for infoTableView
Registering a UITableViewCell (InfoTableViewCell) on infoTableView
Creating two UILabels (infoLabel and infoText) on a InfoTableViewCell
Defining horizontal and vertical constraints to infoText
When running this code below on an iPhone 5/5s/6s simulator the table row height is determined properly, and the labels are properly constrained to the table row. However, when I simulate the code on an iPhone 6 the data is not displayed correctly.
See Example.
How could this difference be explained? Are the constraints set correctly? Or am I missing some code?
A related question is about the total tableview height. I set this height in viewDidLayoutSubviews, but it is currently set to constant value since the TableHeight seems not yet been initialized here. How should the height be determined?
I hope that anybody could help.
My code:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate, UITableViewDataSource, UITableViewDelegate {
var scrollView : UIScrollView!
var containerView : UIView!
var infoTableView: UITableView!
var infoLabels = [String]()
var infoData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView()
scrollView.delegate = self
scrollView.contentSize = CGSizeMake(view.bounds.width, 1000)
containerView = UIView()
infoLabels = ["Label1", "Label2", "Label3"]
infoData = ["Label1Test1 Label1Test2 Label1Test3 Label1Test4 Label1Test5",
"Label3Test1 Label2Test2 Label2Test3 Label2Test4 Label2Test5",
"Label3Test1 Label3Test2 Label3Test3 Label3Test4 Label3Test5"]
// Create information TableView
infoTableView = UITableView()
infoTableView.registerClass(InfoTableViewCell.self, forCellReuseIdentifier: "Cell")
infoTableView.delegate = self
infoTableView.dataSource = self
infoTableView.estimatedRowHeight = 25
infoTableView.rowHeight = UITableViewAutomaticDimension
containerView.addSubview(infoTableView)
scrollView.addSubview(containerView)
view.addSubview(scrollView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
containerView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height)
// --------------------------------------------
// How to determine height of table view??????
let infoTableHeight: CGFloat = 200
//infoTableView.frame.size.height => Is nil during initialization!!!
infoTableView.frame = CGRectMake(5, 50, self.view.bounds.width - 10, infoTableHeight)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let textCellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! InfoTableViewCell
let infoLabel = infoLabels[indexPath.row]
let infoDataItem = infoData[indexPath.row]
cell.infoLabel.text = infoLabel
cell.infoText.text = infoDataItem
cell.selectionStyle = .None
return cell
}
}
class InfoTableViewCell: UITableViewCell {
var infoLabel = UILabel()
var infoText = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
infoText.textAlignment = NSTextAlignment.Left
infoText.font = UIFont.systemFontOfSize(14)
infoText.baselineAdjustment = .AlignCenters
infoText.numberOfLines = 0
infoText.translatesAutoresizingMaskIntoConstraints = false
infoText.lineBreakMode = NSLineBreakMode.ByWordWrapping
self.contentView.addSubview(infoText)
let views = ["infoText" : infoText]
let hConstraint = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[infoText]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
self.contentView.addConstraints(hConstraint)
let vConstraint = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[infoText]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
self.contentView.addConstraints(vConstraint)
infoLabel.textAlignment = NSTextAlignment.Left
infoLabel.font = UIFont.boldSystemFontOfSize(14)
infoLabel.baselineAdjustment = .AlignCenters
infoLabel.numberOfLines = 0
self.contentView.addSubview(infoLabel)
}
required init(coder decoder: NSCoder) {
super.init(coder: decoder)!
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
super.layoutSubviews()
let width = frame.width-120
let height = frame.size.height
infoLabel.frame = CGRectMake(10, 0, 100, height)
infoText.frame = CGRectMake(110, 0, width, height)
}
}

Related

How to make a horizontal UICollectionView have the same spacing between dynamic cells

I have a dynamic collectionView, and essentially the spacing between cells needs to be the same regardless the width of the cell.
Found similar answers here and on the internet, but all were for vertical scrolling collectionViews. So, I went on and tried to work further on one of those answers to achieve what I want, with no much luck.
Currently, my collectionView has the same spacing between cells, but after each cell, it moves to the next row, although I'm not changing or manipulating the y offset of the attributes. Also, not all cells are visible.
Please, can you point out what I'm doing wrong? Thanks.
The subclass of UICollectionViewFlowLayout that I'm using is:
class TagsLayout: UICollectionViewFlowLayout {
let cellSpacing: CGFloat = 20
override init(){
super.init()
scrollDirection = .horizontal
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.scrollDirection = .horizontal
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
guard let attributesToReturn = attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
return nil
}
var leftMargin = sectionInset.left
var maxX: CGFloat = -1.0
attributesToReturn.forEach { layoutAttribute in
if layoutAttribute.frame.origin.x >= maxX {
leftMargin = sectionInset.left
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + cellSpacing
maxX = max(layoutAttribute.frame.maxX , maxX)
}
return attributesToReturn
}
}
As I said in my comment, you are using code for a "left-aligned vertical scrolling" collection view.
A horizontal scrolling collection view lays out the cells like this:
your code is calculating a new origin.x for each cell in sequence, resulting in this:
You could modify your custom flow layout to keep track of a maxX for each "row" ... but, if you have a lot of cells as soon as you scroll so the first few "columns" are out-of-view, those cells will no longer be factored into the layout.
So, you could attempt to "pre-calculated" the frame widths and x-origins of all your cells, and get close to your goal:
Two more issues though...
First, assuming your cells contain longer strings than shown in these images, the collection view doesn't do a good job of figuring out which cells actually need to be shown. That is, the collection view will use the estimated items size to decide if a cell will need to be rendered. If the modification to the cells origin.x values would not fall within the expected range, certain cells will not be rendered because the collection view won't ask for them.
Second, if you have varying-width tags, you could end up with something like this:
and rotated to landscape for emphasis (the top row actually goes all the way to 24):
You may want to re-think your approach and either go with a vertical-scrolling left-aligned collection view, or a horizontal-scrolling collection view with equal-width cells, or some other approach (such as a normal scroll view with subviews laid-out via your own code).
I did create classes using the "pre-calculate" approach -- here they are if you want to give it a try.
Simple cell with a label:
class TagCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
])
// default (unselected) appearance
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
label.textColor = .black
// let's round the corners so it looks nice
contentView.layer.cornerRadius = 12
}
}
Modified custom flow layout:
class TagsLayout: UICollectionViewFlowLayout {
var cachedFrames: [[CGRect]] = []
var numRows: Int = 3
let cellSpacing: CGFloat = 20
override init(){
super.init()
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
commonInit()
}
func commonInit() {
scrollDirection = .horizontal
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// guard let attributes = super.layoutAttributesForElements(in: rect) else {
// return nil
// }
// we want to force the collection view to ask for the attributes for ALL the cells
// instead of the cells in the rect
var r: CGRect = rect
// we could probably get and use the max-width from the cachedFrames array...
// but let's just set it to a very large value for now
r.size.width = 50000
guard let attributes = super.layoutAttributesForElements(in: r) else {
return nil
}
guard let attributesToReturn = attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
return nil
}
attributesToReturn.forEach { layoutAttribute in
let thisRow: Int = layoutAttribute.indexPath.item % numRows
let thisCol: Int = layoutAttribute.indexPath.item / numRows
layoutAttribute.frame.origin.x = cachedFrames[thisRow][thisCol].origin.x
}
return attributesToReturn
}
}
Example controller class with generated tag strings:
class HorizontalTagColViewVC: UIViewController {
var collectionView: UICollectionView!
var myData: [String] = []
// number of cells that will fit vertically in the collection view
let numRows: Int = 3
override func viewDidLoad() {
super.viewDidLoad()
// let's generate some rows of "tags"
// we're using 3 rows for this example
for i in 0...28 {
switch i % numRows {
case 0:
// top row will have long tag strings
myData.append("A long tag name \(i)")
case 1:
// 2nd row will have short tag strings
myData.append("Tag \(i)")
default:
// 3nd row will have numeric strings
myData.append("\(i)")
}
}
// now we'll pre-calculate the tag-cell widths
let szCell = TagCell()
let fitSize = CGSize(width: 1000, height: 50)
var calcedFrames: [[CGRect]] = Array(repeating: [], count: numRows)
for i in 0..<myData.count {
szCell.label.text = myData[i]
let sz = szCell.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .defaultLow, verticalFittingPriority: .required)
let r = CGRect(origin: .zero, size: sz)
calcedFrames[i % numRows].append(r)
}
// loop through each "row" setting the origin.x to the
// previous cell's origin.x + width + 20
for row in 0..<numRows {
for col in 1..<calcedFrames[row].count {
var thisRect = calcedFrames[row][col]
let prevRect = calcedFrames[row][col - 1]
thisRect.origin.x += prevRect.maxX + 20.0
calcedFrames[row][col] = thisRect
}
}
let fl = TagsLayout()
// for horizontal flow, this is becomes the minimum-inter-line spacing
fl.minimumInteritemSpacing = 20
// we need this so the last cell does not get clipped
fl.minimumLineSpacing = 20
// a reasonalbe estimated size
fl.estimatedItemSize = CGSize(width: 120, height: 50)
// set the number of rows in our custom layout
fl.numRows = numRows
// set our calculated frames in our custom layout
fl.cachedFrames = calcedFrames
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
// so we can see the collection view frame
collectionView.backgroundColor = .cyan
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
collectionView.heightAnchor.constraint(equalToConstant: 180.0),
])
collectionView.register(TagCell.self, forCellWithReuseIdentifier: "cell")
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension HorizontalTagColViewVC: UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TagCell
c.label.text = myData[indexPath.item]
return c
}
}
Note that this is Example Code Only!!! It has not been tested and may or may not fit your needs.

iOS - IGListKit - implement self sizing cell with UIImageView

I am trying to implement IGListKit based CollectionView. It has 1 UILabel and 1 UIImageView.
Checked official examples too but couldn't manage to make it self sizing.
This is Cell class. In this class tried to add the code which is given in the official repository. The function I've used is preferredLayoutAttributesFitting:
import UIKit
import Stevia
class MainControllerCell: UICollectionViewCell {
lazy var userNameLabel: UILabel = {
let lbl = UILabel()
lbl.numberOfLines = 1
lbl.translatesAutoresizingMaskIntoConstraints = false
return lbl
}()
lazy var photoImageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .white
contentView.sv(
userNameLabel,
photoImageView
)
}
override func layoutSubviews() {
contentView.layout(
8,
|-8-userNameLabel.height(20)-8-|,
8,
|-0-photoImageView-0-|,
8
)
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
var newFrame = layoutAttributes.frame
// note: don't change the width
newFrame.size.height = ceil(size.height)
layoutAttributes.frame = newFrame
return layoutAttributes
}
}
This is SectionController:
import IGListKit
class MainSectionController: ListSectionController {
private var post: Post!
override init() {
super.init()
inset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
minimumLineSpacing = 10
minimumInteritemSpacing = 10
}
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let width = collectionContext!.containerSize.width
return CGSize(width: width, height: 75) // width / post.photo.getImageRatio() + 44
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
let userName = post.userName
let photo = post.photo
let cell: UICollectionViewCell
guard let mainCollectionViewCell = collectionContext?.dequeueReusableCell(of: MainControllerCell.self,
for: self,
at: index) as? MainControllerCell else { fatalError() }
mainCollectionViewCell.userName = userName
mainCollectionViewCell.photoImageView.image = photo
cell = mainCollectionViewCell
return cell
}
override func didUpdate(to object: Any) {
self.post = object as? Post
}
}
And lastly Controller:
import UIKit
import IGListKit
class MainController: UIViewController, ListAdapterDataSource {
lazy var adapter: ListAdapter = {
let updater = ListAdapterUpdater()
return ListAdapter(updater: updater, viewController: self)
}()
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .white
return collectionView
}()
var posts: [Post] = []
override func viewDidLoad() {
super.viewDidLoad()
posts = [Post(timeStamp: 0, userName: "onur", photoUrl: "xxx", photo: UIImage(named: "cat")!),
Post(timeStamp: 1, userName: "onur", photoUrl: "xxx", photo: UIImage(named: "iPhoneMP")!),
Post(timeStamp: 2, userName: "onur", photoUrl: "xxx", photo: UIImage(named: "placeholder")!),
Post(timeStamp: 3, userName: "onur", photoUrl: "xxx", photo: UIImage(named: "random")!)]
adapter.collectionView = collectionView
adapter.dataSource = self
view.addSubview(collectionView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
adapter.performUpdates(animated: false, completion: nil)
}
// MARK: ListAdapterDataSource
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return posts
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return MainSectionController()
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
return nil
}
}
What I am trying to implemet is making a Instagram like layout but less complicated. I won't add new views or anything.
In the preferredLayoutAttributesFitting method, I am getting the original image size. Not the scaled image's size.
The official examples are made with just labels. Before trying to implement this project I tried with just labels and constant sized UIImageView for profile photo. It is working good with it.
Not sure if the problem is with SteviaLayout?
Thanks in advance.

how to blur only the part of uitableview getting scrolled

I have to implement a menu just like the "example.gif" in the following library link
i'm using this same library to blur the background.
i've used contentInset so that the first three rows show up from the bottom.
Now, my problem is when i start scrolling the entire screen is blurred, whereas i want to blur the part of screen where uitableviewcells are getting scrolled. (Ultimately, the entire screen will be blurred as soon as the first cell reaches the top).
how can i achieve this. If there is any workaround without using the library, it is also welcome. Here is the code--
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate{
#IBOutlet weak var table: UITableView!
var blurView: DKLiveBlurView!
var unsortedCountryArray:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let topInset = self.view.frame.height - 120
// Array to display.
let countryArray = NSLocale.ISOCountryCodes()
for countryCode in countryArray {
let displayNameString = NSLocale.currentLocale().displayNameForKey(NSLocaleCountryCode, value: countryCode)
if displayNameString != nil {
unsortedCountryArray.append(displayNameString!)
}
}
// =============setting background==============
// self.bkgView = UIImageView(frame: self.view.bounds)
// self.bkgView.image = UIImage(named: "bg1")
// self.bkgView.contentMode = .ScaleAspectFill
// self.view.addSubview(self.bkgView)
// self.blurredBkgView = UIImageView(frame: self.view.bounds)
// self.blurredBkgView.image = UIImage(named: "bg1")
// self.blurredBkgView.contentMode = .ScaleAspectFill
// self.view.addSubview(blurredBkgView)
// self.blurredBkgView.alpha = 0.0
//
// blurEffect = UIBlurEffect(style: .Light)
// visualEffectView = UIVisualEffectView(effect: blurEffect)
// visualEffectView.frame = self.blurredBkgView.bounds
// self.visualEffectView.alpha = 0.0
// self.view.addSubview(self.visualEffectView)
self.table.backgroundColor = UIColor.clearColor()
self.table.separatorColor = UIColor.clearColor()
self.table.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0)
self.table.rowHeight = 40
print("view bounds: \(self.view.bounds)\n and table bounds: \(self.table.bounds)")
self.blurView = DKLiveBlurView(frame: self.table.bounds)
self.blurView.originalImage = UIImage(named: "bg1")
self.blurView.scrollView = table
self.blurView.setBlurLevel(6.0)
self.blurView.isGlassEffectOn = true
self.table.backgroundView = self.blurView
}
func scrollViewDidScroll(scrollView: UIScrollView) {
// let height = CGFloat(scrollView.bounds.size.height)
// let position = max(scrollView.contentOffset.y, 0.0)
// let percent = min(position / height, 1.0)
// self.blurredBkgView.alpha = percent;
// print("scrollview bounds: \(scrollView.bounds)")
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
// cell.backgroundView = self.blurView
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return unsortedCountryArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")!
cell.textLabel?.text = unsortedCountryArray[indexPath.row]
cell.textLabel?.textColor = UIColor.blueColor()
cell.backgroundColor = UIColor.clearColor()
cell.selectionStyle = .None
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This much code is blurring while scrolling.
To account for your content inset you need to change the frame you provide to your blurView
Eg
let contentInset = CGFloat(/*Your content inset*/)
let blurFrame = CGRect(x: 0, y: contentInset, width: tableView.frame.width, height: tableView.frame.height - contentInset)
self.blurView = DKLiveBlurView(frame: blurFrame)
EDIT: Old answer
You seem to be using bounds rather than frame for your DKLiveBlurView. This will cause you blur view to start from the top left of the screen (the origin of your view's frame)
Try:
self.blurView = DKLiveBlurView(frame: self.table.frame)
Rather than
self.blurView = DKLiveBlurView(frame: self.table.bounds)

Table View Displays Below Screen

My issue is that the last cell in my TableView is below the screen view and to see it you must scroll up and hold your position. At a neutral position where you dont scroll up, you cant see the last cell. Everything seemed fine until i changed the size of the cells. Here is my code:
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
//MARK : Properties
var tableView = UITableView()
var items: [String] = ["Age", "Gender", "Smoking Hx", "Occup. -Ag", "Family Hx", "Chronic Lung Disease Radiology", "Chronic Lung Disease Hx", "Nodule Border", "Nodule Location", "Satellite Lesions", "Nodule Pattern Cavity", "Nodule Size"]
var navigationBar = NavigationBar()
var gender = GenderView()
override func viewDidLoad() {
super.viewDidLoad()
//Create TableView
tableView.frame = CGRectMake(0, self.view.bounds.height * 0.097, self.view.bounds.width, self.view.bounds.height);
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
//Create Navigation Bar with custom class
self.navigationBar = NavigationBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height * 0.097))
self.view.addSubview(navigationBar)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
//Cell wont turn grey when selected
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return self.view.bounds.height * 0.095
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
}
}
The only thing i could think of causing this issue is that instead of me creating a navigation bar, i created a "navigationBar" using a custom UIView() class. I then start the table view at the bottom of the navigation bar. Any idea how to fix this?
Here is the NavigationBar.swift:
class NavigationBar: UIView {
var navigationBar = UIView()
var header = UILabel()
var lineBorder = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
setUpView()
}
func setUpView(){
//Create Navigation Bar
navigationBar.frame = CGRectMake(0, 0, self.bounds.width, self.bounds.height)
navigationBar.backgroundColor = UIColor.whiteColor()
self.addSubview(navigationBar)
//Create Line Border
lineBorder.frame = CGRectMake(0, self.bounds.height, self.bounds.width, self.bounds.height * 0.005)
lineBorder.backgroundColor = UIColor.grayColor()
self.addSubview(lineBorder)
header.frame = CGRectMake(0, 0, 200, 200)
header.font = UIFont(name: "Helvetica", size: 17)
header.text = "Nodule Risk Calculator"
//header.backgroundColor = UIColor.blackColor()
self.addSubview(header)
header.translatesAutoresizingMaskIntoConstraints = false
header.centerHorizontallyTo(navigationBar, padding: 0)
header.centerVerticallyTo(navigationBar, padding: 9)
}
func hide(){
self.removeFromSuperview()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
tableView.frame = CGRectMake(0, self.view.bounds.height * 0.097, self.view.bounds.width, self.view.bounds.height);
has problem. The origin.y is not 0, and you still feed the whole height. So the table view will have some area below the screen
Try this:
tableView.frame = CGRectMake(0, self.view.bounds.height * 0.097, self.view.bounds.width,
self.view.bounds.height - self.view.bounds.height * 0.097);
self.navigationBar = NavigationBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height * 0.097))
self.view.addSubview(navigationBar)
var yAxis : Float = self.navigationBar.frame.height
var tblHeight : Float = self.view.bounds.height - yAxis
tableView.frame = CGRectMake(0, yAxis, self.view.bounds.width, tblHeight)
self.view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

Custom Table View Cell Drawing Wrong Height When Scrolling

I'm customizing my table view cell inside the willDisplayCell method. For some reason it is drawing some subviews with the wrong height while scrolling (see video). I can't figure out why...
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.contentView.backgroundColor=UIColor.clearColor()
cell.reloadInputViews()
let height = cell.frame.height - 15
var whiteRoundedCornerView:UIView!
whiteRoundedCornerView=UIView(frame: CGRectMake(7,10,self.view.bounds.width-14,height))
whiteRoundedCornerView.backgroundColor=getColorByID(colorID!)
whiteRoundedCornerView.layer.masksToBounds=false
whiteRoundedCornerView.layer.shadowOpacity = 1.55;
whiteRoundedCornerView.layer.shadowOffset = CGSizeMake(1, 0);
whiteRoundedCornerView.layer.shadowColor=UIColor.grayColor().CGColor
whiteRoundedCornerView.layer.cornerRadius=5.0
whiteRoundedCornerView.layer.shadowOffset=CGSizeMake(-1, -1)
whiteRoundedCornerView.layer.shadowOpacity=0.5
whiteRoundedCornerView.layer.shouldRasterize = true
whiteRoundedCornerView.layer.rasterizationScale = UIScreen.mainScreen().scale
if cell.contentView.subviews.count < 6 { // to avoid multible subview adding when scrolling...
cell.contentView.addSubview(whiteRoundedCornerView)
cell.contentView.sendSubviewToBack(whiteRoundedCornerView)
}
}
Here is a video of the issue:
https://vid.me/QeV0
Update:
I also tried to move the code to layoutSubviews but still the same issue...
override func layoutSubviews() {
super.layoutSubviews()
let height = self.frame.height - 15
var whiteRoundedCornerView:UIView!
whiteRoundedCornerView=UIView(frame: CGRectMake(7,10,UIScreen.mainScreen().bounds.width-14,height))
whiteRoundedCornerView.backgroundColor=UIColor.greenColor()//getColorByID(colorID!)
whiteRoundedCornerView.layer.masksToBounds=false
whiteRoundedCornerView.layer.shadowOpacity = 1.55;
whiteRoundedCornerView.layer.shadowOffset = CGSizeMake(1, 0);
whiteRoundedCornerView.layer.shadowColor=UIColor.grayColor().CGColor
whiteRoundedCornerView.layer.cornerRadius=5.0
whiteRoundedCornerView.layer.shadowOffset=CGSizeMake(-1, -1)
whiteRoundedCornerView.layer.shadowOpacity=0.5
whiteRoundedCornerView.layer.shouldRasterize = true
whiteRoundedCornerView.layer.rasterizationScale = UIScreen.mainScreen().scale
if self.contentView.subviews.count < 6 {
self.contentView.addSubview(whiteRoundedCornerView)
self.contentView.sendSubviewToBack(whiteRoundedCornerView)
}
}
If i don't check the tableview.subviews.count it will add tons of subviews to the cell while scrolling.
I want to change the height of the existing subview when the cell is reused.
You have a check to not add the subviews multiple times. When that case happens you already have added the subviews earlier but their height may now be wrong and you must update them.
UITableView reuses cells so a new cell with different height might already contain your subviews but with the wrong height since you don't update it.
You should cleanly separate the set-up of your subview and the layout like so:
class MyCell: UITableViewCell {
private let whiteRoundedCornerView = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUp()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUp()
}
override func layoutSubviews() {
super.layoutSubviews()
let bounds = self.bounds
var whiteRoundedCornerViewFrame = CGRect()
whiteRoundedCornerViewFrame.origin.x = 7
whiteRoundedCornerViewFrame.origin.y = 10
whiteRoundedCornerViewFrame.size.width = bounds.width - 7 - whiteRoundedCornerViewFrame.origin.x
whiteRoundedCornerViewFrame.size.height = bounds.height - 5 - whiteRoundedCornerViewFrame.origin.y
whiteRoundedCornerView.frame = whiteRoundedCornerViewFrame
}
private func setUp() {
setUpWhiteRoundedCornerView()
}
private func setUpWhiteRoundedCornerView() {
let child = whiteRoundedCornerView
child.backgroundColor = .greenColor()
let layer = child.layer
layer.cornerRadius = 5.0
layer.rasterizationScale = UIScreen.mainScreen().scale
layer.shadowColor = UIColor.grayColor().CGColor
layer.shadowOffset = CGSize(width: -1, height: -1)
layer.shadowOpacity = 0.5
layer.shouldRasterize = true
insertSubview(child, atIndex: 0)
}
}
If the data on different rows is of different size then you have to set the height of the tableview cell dynamically
Here is the code:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat{
var height:CGFloat
let constraint1:CGSize = CGSizeMake(self.view.frame.size.width/2-70, 5000.0)
let string="eefdgvfgfhgfgfhgfgjhidffjo;dkfnkjfldjfkjfkjfldfjkkjhbkjflfjihkfjkfjgfkjfkgfkgjlfgnfjgnfgnfgbkgmfjgfkgjfkggjlfgklkgkgkglkgggfkglgkkgjlgkgk"
let answerSize = string.boundingRectWithSize(constraint1, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(17.0)], context: nil)
let questionSize = string.boundingRectWithSize(constraint1, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(17.0)], context: nil)
height=answerSize.height + questionSize.height
return height
}

Resources