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
}
Related
I used this code to make UIImageView in a custom UITableViewCell, but the circular shape isn't updated until i pressed on the table cell, I tried the code in both awakeFromNib() and layoutSubviews()
userImage.layer.cornerRadius = userImage.frame.size.width / 2
userImage.layer.masksToBounds = true
userImage.layer.borderColor = UIColor.black.cgColor
userImage.layer.borderWidth = 1
You simply have to override the layoutSubviews() method of your UITableViewCell subclass, as it will be called on drawing updates.
Then just set your UIImageView's cornerRadius as you like:
class MyCustomTableViewCell: UITableViewCell {
private let circularImageView: UIImageView = {
$0.contentMode = .scaleAspectFill
$0.layer.masksToBounds = true
$0.layer.borderColor = UIColor.black.cgColor
$0.layer.borderWidth = 1
return $0
}(UIImageView())
// ...
override func layoutSubviews() {
super.layoutSubviews()
circularImageView.layer.cornerRadius = circularImageView.frame.size.width/2
}
}
if you create you tableviewCell programmatically you have to override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//put your code here
}
update the imageViews cornerRadius just after a little time. Because frame.size.width / 2 will be different at runtime :
DispatchQueue.main.asyncAfter(deadline : .now() + 0.05 {
self.userImage.layer.cornerRadius = userImage.frame.size.width / 2
self.userImage.layer.masksToBounds = true
self.userImage.layer.borderColor = UIColor.black.cgColor
self.userImage.layer.borderWidth = 1
}
I write a code to draw card in a UITableView, each card need to draw itself and have a custom rowheight in the exemple a draw 2 card in 240 height and 8 other with 240/2 of height.
I give you a screen of the problem :
The last but one card have a strange design. When I print the bounds of the frame of it, this show me the same size that the other ...
I give you my code where I draw the card :
func drawBasiqCard() -> CGFloat{
if(cardView.subviews.count == 0){
self.addSubview(cardView)
cardView.frame = CGRect(marginCardWidth,
marginCardHeight,
self.bounds.size.width - (marginCardWidth*2),
basiqCardtHeight - (marginCardHeight*2))
cardView.layer.cornerRadius = 10
print(cardView.bounds.size.height)
}
if(self.ShadowLayerCard == nil){
let rounding = CGFloat.init(10)
let shadowLayer = CAShapeLayer.init()
self.ShadowLayerCard = shadowLayer
shadowLayer.path = UIBezierPath.init(roundedRect: cardView.bounds, cornerRadius: rounding).cgPath
shadowLayer.fillColor = UIColor(rgb: 0xffcc00).cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowRadius = 5
shadowLayer.shadowOpacity = 0.2
shadowLayer.shadowOffset = CGSize.init(width: 0, height: 0)
cardView.layer.insertSublayer(shadowLayer, at: 0)
}
return cardView.bounds.size.height + (marginCardHeight*2)
}
So my question is, the code above is good ? Or my error are not here ? Where is the code can be produce this visual error ?
If you need the code of the UITableView I can show you.
Thank for your reply.
This could happen if you set the shadow of the view before it's bounds has been set.
Your current design is not good, you should add a shadow to the contentView in init and then change the contentView's frame(add some padding) in layoutsubviews.
Here is an example:
import UIKit
class TableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.do {
$0.backgroundColor = .white
$0.addShadow(ofColor: .black, radius: 1.5, offset: .zero, opacity: 0.1)
$0.cornerRadius = 6
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.pin.all(UIEdgeInsets(inset: 5))
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
contentView.backgroundColor = highlighted ? UIColor(rgb: 217) : .white
}
}
It looks like:
I am implementing like a calendar layout with some modification which is shown in the screenshot. To achieve this I have used UICollectionView. The problem is, I have to draw a screen width continuous line(green line in screenshot). The green line should cover the whole width, I know its not showing over the circular cell due to half of the cornerRadius and a vertical line only after the first cell(10 am). Where i have to add the shapelayer, so that it ll seems like a continuous line. Here is the code which I have tried so far.
KKViewController.m
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! KKBookCollectionViewCell
self.bookingCollectionView.backgroundColor = UIColor.whiteColor()
let rectangularRowIndex:NSInteger = indexPath.row % 5
if(rectangularRowIndex == 0 )
{
cell.userInteractionEnabled = false
cell.layer.cornerRadius = 0
cell.timeSlotLabel.text = "10am"
cell.backgroundColor = UIColor.whiteColor()
cell.layer.borderWidth = 0
cell.layer.borderColor = UIColor.clearColor().CGColor
}
else
{
cell.userInteractionEnabled = true
cell.layer.cornerRadius = cell.frame.size.width/2
cell.timeSlotLabel.text = ""
//cell.backgroundColor = UIColor.lightGrayColor()
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.grayColor().CGColor
if cell.selected == true
{
cell.backgroundColor = UIColor.greenColor()
}
else
{
cell.backgroundColor = UIColor.lightGrayColor()
}
}
return cell
}
KKCollectionCell.m
var borderWidth:CGFloat!
var borderPath:UIBezierPath!
override func awakeFromNib() {
super.awakeFromNib()
drawHorizontalLine()
dottedLine(with: borderPath, and: borderWidth)
drawVerticalLine()
dottedLine(with: borderPath, and: borderWidth)
func drawVerticalLine()
{
borderPath = UIBezierPath()
borderPath.moveToPoint(CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y))
//borderPath.addLineToPoint(CGPointMake(self.frame.size.width - 5, self.frame.origin.y + self.frame.size.height - 50))
borderPath.addLineToPoint(CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y + self.frame.size.height))
borderWidth = 2.0
print("border path is %f, %f:\(borderPath)")
}
func drawHorizontalLine()
{
borderPath = UIBezierPath()
borderPath.moveToPoint(CGPointMake(0, self.frame.origin.y))
borderPath.addLineToPoint(CGPointMake(self.frame.size.width + 10, self.frame.origin.y))
borderWidth = 2.0
print("border path is %f, %f:\(borderPath)")
}
func dottedLine (with path:UIBezierPath, and borderWidth:CGFloat)
{
let shapeLayer = CAShapeLayer()
shapeLayer.strokeStart = 0.0
shapeLayer.strokeColor = UIColor.greenColor().CGColor
shapeLayer.lineWidth = borderWidth
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [1,2]
shapeLayer.path = path.CGPath
self.layer.addSublayer(shapeLayer)
}
You can add a new view inside the collection view cell and set the corner radius for that new view. Also you have to reduce the spacing between the cells. Then the line will look like what you expected.
I know it's an old question but it was never properly answered, i was looking for such behaivior and achieved it with the help of another answer.
To achieve that you need to subclass the UICollectionViewFlowLayout (maybe a simple layout also would do but i didn't try).
class HorizontalLineFlowLayout: UICollectionViewFlowLayout {
var insets: CGFloat = 10.0
static let lineWidth: CGFloat = 10.0
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
var attributesCopy: [UICollectionViewLayoutAttributes] = []
for attribute in attributes {
attributesCopy += [attribute]
let indexPath = attribute.indexPath
if collectionView!.numberOfItems(inSection: indexPath.section) == 0 { continue }
let contains = attributes.contains { layoutAttribute in
layoutAttribute.indexPath == indexPath && layoutAttribute.representedElementKind == HorizontalLineDecorationView.kind
}
if !contains {
let horizontalAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: HorizontalLineDecorationView.kind, with: indexPath)
let width = indexPath.item == collectionView!.numberOfItems(inSection: indexPath.section) - 1 ?
attribute.frame.width + insets :
attribute.frame.width + insets * 1.5
let frame = CGRect(x: attribute.frame.minX, y: attribute.frame.minY, width: width, height: 0.5)
horizontalAttribute.frame = frame
attributesCopy += [horizontalAttribute]
}
}
return attributesCopy
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
the func layoutAttributesForElements is called each time, and it makes the lines for the cells you specify and for the frames you specify.
you would also need the decoration view which looks like that:
class HorizontalLineDecorationView: UICollectionReusableView {
static let kind = "HorizontalLineDecorationView"
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .gray
alpha = 0.2
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
in general its just a view that goes behind the cell, respectively to its indexPath, so the lines are not all along the screen but those are some lines gathered togather which look like a full line, you can adjust its width and height, play with that.
notice that frame that is set for an attribute in the layout, that is the frame of the decoration view, and is defined with respect to the cell (attribute).
dont forget to register that decoration view and also to make the layout and pass it to the collecitonview like this:
let layout = HorizontalLineFlowLayout()
layout.register(HorizontalLineDecorationView.self, forDecorationViewOfKind: HorizontalLineDecorationView.kind)
layout.minimumInteritemSpacing = 10.0
layout.minimumLineSpacing = 10.0
layout.sectionInset = .zero
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(DateCell.self, forCellWithReuseIdentifier: "month")
collectionView.delegate = monthDelegate
collectionView.dataSource = monthDelegate
collectionView.backgroundColor = .clear
the end result is
I'm doing everything programmatically. I have a UITableView with its rows being a UITableViewCell subclass. I have been unable to set the accessoryType of my cell as nothing appears on the screen. Later on I want to be able to select a cell, and add a checkmark when selected.
class ContactsTableViewCell: UITableViewCell {
let name = UILabel()
let phoneNumber = UILabel()
let iPADFACTOR:CGFloat = DeviceHelper().isIPAD ? 1.4: 1.0
var rowSelected = false
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
phoneNumber.font = UIFont.systemFontOfSize(15)
phoneNumber.textColor = UIColor.lightGrayColor()
self.contentView.addSubview(name)
self.contentView.addSubview(phoneNumber)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
let margeV:CGFloat = 5.0*iPADFACTOR
let margeH = self.frame.width*0.05
name.frame = CGRectMake(margeH, margeV, self.frame.width - margeH * 4, 18)
phoneNumber.frame = CGRectMake(margeH, name.frame.maxY + 5, self.frame.width - margeH * 4, 15)
}
}
As you can see I'm using the layoutSubviews() method, to set the frame of my labels and I've tried playing with the label size to check if somehow it was hiding the accessoryView without success. I've even tried setting their frame to 0.
I'm also not sure if I'm setting my frames correctly. Should I use self.contentView.frame instead of self.frame ?
Any help is appreciated, thanks.
override func layoutSubviews() {
super.layoutSubviews()
let margeV:CGFloat = 5.0*iPADFACTOR
let margeH = self.frame.width*0.05
name.frame = CGRectMake(margeH, margeV, self.frame.width - margeH * 4, 18)
phoneNumber.frame = CGRectMake(margeH, name.frame.maxY + 5, self.frame.width - margeH * 4, 15)
}
if (YES) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else {
cell.accessoryType = UITableViewCellAccessoryType.None;
}
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)
}
}