Is there a callback for ViewCell fully laid out in swift? - swift

I have a UITableView with cells, the relevant part of the layout looks like this:
everything is fine until i try to add shadows to that button(orange) and view ($-), this happens:
The problem is that the view is not fully set in awakeFromNib, and it also not fully set during
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
So when can i add the shadow to those views without having to worry about the size not being set correctly yet?
Aditional Info: if i set it during the cell setup, it does not work at first, but after scrolling it gets fixed for as long as the view is open.
Edit 1: how do i add the shadows? I call a func for the cell that calls the next method passing the view as parameter, i think that #DonMag suggestion should work, subclassing should work, but is there no way to know when all the view is laid out in a TableView?
static func addShadowToView(view: UIView, CornerRadius: CGFloat){
view.layer.cornerRadius = CornerRadius
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.gray.cgColor
view.layer.shadowOffset = CGSize(width: 0.1, height: 0.1)
view.layer.shadowOpacity = 0.5
let shadowPath = UIBezierPath(roundedRect: view.bounds,
cornerRadius: view.layer.cornerRadius)
view.layer.shadowPath = shadowPath.cgPath
}

Related

Does iOS 14 have some breaking changes that disable vertical scroll for Collection View? [duplicate]

I have a search form that uses a tableview. After updating Xcode 12 today the UISwitch, UITextField, UISlider no longer work when nested inside a UITableViewCell. Is there a property that has changed that I need to set to make this work again?
To be sure it wasn't just my project, I created a new project and nestled a UITextField inside of it and it doesn't work either.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let textField = UITextField(frame: CGRect(x: 5, y: 5, width: 400.0, height: 25.0))
textField.delegate = self
textField.backgroundColor = .blue
cell.addSubview(textField)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("this will get called even when selecting the UITextField")
}
func textFieldDidBeginEditing(_ textField: UITextField) {
print("this is never called")
}
Your code was always wrong:
cell.addSubview(textField)
You must never add a subview to a cell. Add the subview to the cell's contentView.
The same happened to me since I upgraded to iOS 14.
This has worked for me when I add the subViews directly to the cell,
cell.contentView.isUserInteractionEnabled = true
Had similar issue, and been going on around for it... what was issue with my code is that under UITableViewCell I was doing this:
didSet {
if contentView.backgroundColor == backgroundColor { return }
contentView.backgroundColor = backgroundColor
for v in otherView.subview { v.backgroundColor = backgroundColor }
}
Removing this line here contentView.backgroundColor = backgroundColor did the trick. Cell is now visible and there is no duplicated contentView
Maybe this will help someone, since I found only answers regarding adding subviews directly to cell instead to cell.contentView
EDIT 1:
Okay, just wanted to update you on situation, issue was that my subviews where of type UIStackView and I had used subview where I actually should have used arrangedSubviews
Hope this will help someone

UITableViewCell style goes away after scrolling down

I am trying to figure out why my custom styling for table cells disappears after scrolling down in a table view and them back up. What do I need to do to have the style persist?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomTrendingCell = trendingTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomTrendingCell
cell.selectionStyle = .none
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(searchForTrendingTerm(sender:)))
cell.iconView_search.isUserInteractionEnabled = true
cell.iconView_search.tag = indexPath.row
cell.iconView_search.addGestureRecognizer(tapGesture)
cell.trendingLabel.text = trendingSearchTerms[indexPath.row]
cell.elevate(elevation: 4.0) //where my style is being set
return cell
}
extension UIView {
func elevate(elevation: Double) {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: elevation)
self.layer.shadowRadius = abs(CGFloat(elevation))
self.layer.shadowOpacity = 0.4
}
}
The top 2 items in the screenshot below have been scrolled down and up. The drop shadow styling has been removed. The bottom 2 have the correct styling and have not been scrolled down.
Screenshot example
One possible solution here is to explicitly specify the zPosition of each cell's layer.
You would want to ensure that the upper cell has the higher position so that it's content (shadow) lies over the lower cell.
In your cell for row at function add:
cell.layer.zPosition = CGFloat(numberOfRows - indexPath.row)

Make a UITableViewCell Coloured to specific percentage

I have an app quiz app split up into different categories. The categories can be seen in a UITableView e.g. Category A, Category B, Category C...etc. I am trying to show the percentage of questions that have been answered as a visual representation in the UITableView by colouring the background of a category UITableView cell.
For example if the person has completed 75% of the questions in category A, I want the background of category A in the UITableView to be coloured in a green colour 75% of the background of the Cell from left to right.
The only way would know how to do it is to make background images of a green background and depending on the percentage of questions answered place the relating background image as the background for the cell background.
Is there an easier way? that I could possibly use something like
Cell.backgroundcolor = UIColor.greencolor().75%ofCell.
I know this code isn't correct but it Kind of gets the idea across of what I am trying to do.
I think you can do it without adding a view to you cell by using a layer:
In your cell class you could add the following function:
func addGradientLayer(percentage: Double) {
let color1 = UIColor.green
let color2 = UIColor.clear
let gradientLayer = CAGradientLayer()
// sets the gradient frame as the bounds of your cell
gradientLayer.frame = bounds
gradientLayer.colors = [color1.cgColor, color2.cgColor]
// tells the gradient to go from left to right
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
//this line is the important one: as
//long the second Double is smaller than
//your percentage, you will not get a gradient
//but a clear separation between your two colors.
gradientLayer.locations = [percentage, 0.0]
layer.addSublayer(gradientLayer)
}
you can then call this method within your awakeFromNib() method or in cellForRowAt (tableView delegate):
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.addGradientLayer(percentage: 0.8)
}
Assuming you're using storyboards, add a UIView with backgroundColor = .greenColor() to the cell
Add top, left and bottom constrains for the view equal to the superview, then add a width constraint for the view and create an outlet for it.
Then in the cell you can say:
var pctScore: CGFloat {
didSet {
let viewWidth = cell.contentView.frame.width * pctScore
widthConstraint.constant = viewWidth
}
}
You could add a UIView as subview to the cell and make the background color of the UIView the color that you want.
Before adding the view you set the views width to 0.75 * cellWidth.

Custom UITableViewCell programmatically with SWIFT

Attached you may find a picture of (left) my attempt to create a similar (right) UITableView. they look like buttons but they're only cells that can be clicked on as a tableview cell.
I have tried many different things including adding a container window inside a custom cell, adding a UIImage inside the custom cell but I just can't replicate these cells!
I have tried using a custom cell class, I have tried doing it through IB and I for the crazyiness of me, cannot recreate it.
Would anyone be able to give me a hint on how to create the (inside cell) text-bounding box/square? with the different background colour lighting?
If this can easily be done with IB I'd rather do it this way, but if you have a sample customcell class that I can take a look at that'd be greatly appreciated too!
Thank you for taking the time to look at my question.
I have made a sample for you close to your requirement. Have a look
https://github.com/RajanMaheshwari/CustomTableCell
I would like to do this using a UITableView.
My approach will be taking a custom cell and add a UIView with some constraints from left, right, up and down.
Also I will provide the same background color to UITableView, UIView which is the superview and the cell content view and also make the separator of UITableView as None and Selection of TableCell as None so that the UI looks like
Next after applying every constraint and making a CustomCell and making IBOutlets we will jump to code.
I will do all the shadow and outlining in Custom Cell's awakeFromNib method
This will be my CustomTableViewCell class
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var labelBackgroundView: UIView!
#IBOutlet weak var cellLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
labelBackgroundView.layer.borderWidth = 0.5
labelBackgroundView.layer.borderColor = UIColor.lightGrayColor().CGColor
labelBackgroundView.layer.shadowColor = UIColor.lightGrayColor().CGColor
labelBackgroundView.layer.shadowOpacity = 0.8
labelBackgroundView.layer.shadowRadius = 5.0
labelBackgroundView.layer.shadowOffset = CGSizeMake(0.0, 2.0)
labelBackgroundView.layer.masksToBounds = false;
}
I have two outlets.
One is the label in which you will be displaying the name.
Other is the outer view which you want to display with some outlining and shadow.
The ViewController code will be:
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
var array = [String]()
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
array = ["Wealth","Health","Esteem","Relationship"]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.cellLabel.text = array[indexPath.row]
cell.labelBackgroundView.tag = indexPath.row
cell.labelBackgroundView.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(cellViewTapped))
cell.labelBackgroundView.addGestureRecognizer(tapGesture)
return cell
}
func cellViewTapped(sender:UITapGestureRecognizer) {
let view = sender.view
let index = view?.tag
print(index!)
}
}
Here I have not used didSelectIndex of UITableViewDelegate as I only want the tap on the Outlining LabelBackgroundView and not on complete cell.
So the final outcome is like this
I think you are on the right path. I did something similar in a project. I created a subclass of UIView (too add a shadow to the view) and added a view with this type inside the cell.
class ShadowedView: UIView {
override func awakeFromNib() {
layer.shadowColor = UIColor(red: 157.0/255.0, green: 157.0/255.0, blue: 157.0/255.0, alpha: 0.5).CGColor
layer.shadowOpacity = 0.8
layer.shadowRadius = 5.0
layer.shadowOffset = CGSizeMake(0.0, 2.0)
}
}
Don't forget to add some constraints to the view inside the cell.
You can arrange your collection view layout in "Size insepector"
And customise your image in the cell.

How to increase the UITableView separator height?

I want more space(10px) between each cell. How can I do this?
And I have added this code
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
The best way for me, just add this in cellForRowAtIndexPath or in willDisplayCell
CGRect sizeRect = [UIScreen mainScreen].applicationFrame;
NSInteger separatorHeight = 3;
UIView * additionalSeparator = [[UIView alloc] initWithFrame:CGRectMake(0,cell.frame.size.height-separatorHeight,sizeRect.size.width,separatorHeight)];
additionalSeparator.backgroundColor = [UIColor grayColor];
[cell addSubview:additionalSeparator];
For Swift 3.0:
let screenSize = UIScreen.main.bounds
let separatorHeight = CGFloat(3.0)
let additionalSeparator = UIView.init(frame: CGRect(x: 0, y: self.frame.size.height-separatorHeight, width: screenSize.width, height: separatorHeight))
additionalSeparator.backgroundColor = UIColor.gray
self.addSubview(additionalSeparator)
You should add this to cell's method awakeFromNib() to avoid re-creation.
I have seen many clunky solutions like subclassing UITableView with hidden cells, and other less optimal ones incl. in this thread.
When initializing the UITableView, Set the rowHeight property of UITableView to a height that equals = cell height + desired separator/space height.
Do not use standard UITableViewCell class though, instead, subclass the UITableViewCell class and override its layoutSubviews method. There, after calling super (don't forget that), set the height of the cell itself to desired height.
BONUS UPDATE 18/OCT/2015:
You can be a bit smart about this. The solution above basically puts the "separator" at the bottom of the cell. What really happens is, the row height is managed by the TableViewController but the cell is resized to be a bit lower. This results in the separator/empty space being at the bottom. But you can also centre all the subviews vertically so that you leave the same space at the top and the bottom. For example 1pt and 1pt.
You can also create isFirst, isLast convenience properties on your cell subclass. You would set these to yes in the cellForRowAtIndexPath.
This way you can handle the edge cases for top and bottom separators inside the layoutSubviews method as this would have access to these properties.
This way you can handle the edge cases for top or bottom - because sometimes the design department wants N+1 separators while the number of cells is only N. So you have to either deal with the top one or the boot one in a special way. But it's best do this inside cells instead tableViewHeader or TableViewFooter.
I don't think it's possible using standard API. I guess you would need to subclass the UITableViewCell and add a view that simulates a separator at the bottom of the cell.
You may want to check this question, it seems related and has some sample code:
iPhone + UITableView + place an image for separator
In Swift
The easiest and shortest way for me was to add the snippet below in cellForRowAtIndexPath or in willDisplayCell:
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath)
{
let additionalSeparatorThickness = CGFloat(3)
let additionalSeparator = UIView(frame: CGRectMake(0,
cell.frame.size.height - additionalSeparatorThickness,
cell.frame.size.width,
additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.redColor()
cell.addSubview(additionalSeparator)
}
this is quite old. Nevertheless I will post my approach.
Simply increase your cell height a bit and assign a mask layer to the cell, like that:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "...", for: indexPath)
// Configure the cell...
let maskLayer = CAShapeLayer()
let bounds = cell.bounds
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 2, y: 2, width: bounds.width-4, height: bounds.height-4), cornerRadius: 5).cgPath
cell.layer.mask = maskLayer
return cell
}
So in this example my seperator height will be 4.
Have fun!
You can do this entirely in the storyboard. Here is how:
go to the storyboard and select the tableview
Show the Size Inspector and from there set row height to say 140.
then show the Attributes Inspector and from there set your separator to Single Line and Style Plain and choose a color
then in the storyboard (or in Document Outline) select the cell
and again in the Size Inspector, under the Table View Cell, set custom Row Height to say 120.
That’s all. Your separator will be 20 units tall.
Kinda old thread, but since I only found hacky solutions in this thread,
here the solution that worked best for me (without additional UIView in every cell)
Swift 3:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//configure your cell...
cell.layer.shadowColor = UIColor.red.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.layer.shadowOpacity = 1
cell.layer.shadowRadius = 0
cell.layer.masksToBounds = false
return cell
}
EDIT: Unfortunately this does not work if you scroll up in a table. I leave the answer here anyway, since it might be a solution if your table has limited content.
See Shadow on a UITableViewCell disappears when scrolling for more info.
For a table cell with height of 50 and a space of 5 pix between the rows. Width is 320.
Define the background of the cells to be clear:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor clearColor];
}
Set the height of the cells, this is the size of the row PLUS the delimiter:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 55;
}
And define in cellForRowAtIndexPath a box, with the size of the row (MINUS delimiter) to draw in the background color:
UILabel *headerBackgroundLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
backgroundBox.backgroundColor = [UIColor grayColor];
[cell addSubview:backgroundBox];
I do it a much simpler and more flexible way. Some may call it a hack. I call it pragmatic.
I hide the standard UITableViewSeparator. I then add a subview to my cell, using auto layout pin it to the top. Fix the height to what I desire. Pin it to the edges with a margin either side. Change it's background colour. I have a plain separator with the height i desire.
You may question how efficient this is having another UIView in the cell hierarchy. Is it really going to make a noticeable difference? Probably not - you've just taken the standard separator out of the table hierarchy anyway.
Swift 4
It's not possible to make the default separator higher. Instead you need to add a subview that will look as a separator to each cell (and optionally make the cell higher). You can do it for example in cellForRowAtIndexPath or in a UITableViewCell subclass.
In case you allow to select the cell, you need to add the subview for selected state as well, otherwise the separator would disappear when the cell is selected. That's why selectedBackgroundView is also configured.
Add this into your UITableViewController subclass:
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.backgroundView = UIView(backgroundColor: .white)
cell.backgroundView?.addSeparator()
cell.selectedBackgroundView = UIView(backgroundColor: .blue)
cell.selectedBackgroundView?.addSeparator()
// configure the cell
return cell
}
Add this extensions into the same file at the bottom:
private extension UIView {
convenience init(backgroundColor: UIColor) {
self.init()
self.backgroundColor = backgroundColor
}
func addSeparator() {
let separatorHeight: CGFloat = 2
let frame = CGRect(x: 0, y: bounds.height - separatorHeight, width: bounds.width, height: separatorHeight)
let separator = UIView(frame: frame)
separator.backgroundColor = .gray
separator.autoresizingMask = [.flexibleTopMargin, .flexibleWidth]
addSubview(separator)
}
}
Here's an option that might work for some people
cell.layer.borderColor = UIColor.white.cgColor
cell.layer.borderWidth = 4.0
cell.layer.masksToBounds = true
The easier and safest solution to this problem is to turn off the table separator and use a UITableViewCell as a separator of variable height. Sure, you'll have to do some index math to figure out where items are, but really it's odd / even.
It won't break and you get the benefit of recyclable cells (no extraneous views to clean up).
First make tableview separator none from the storyboard. Then add UILabel/UIView at bottom of cell of height(you needed) using storyboard or Xib
For Swift 4
Implemented King-Wizard's solution to Swift 4:
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let additionalSeparatorThickness = CGFloat(4)
let additionalSeparator = UIView(frame: CGRect(x: 0,
y: cell.frame.size.height - additionalSeparatorThickness, width: cell.frame.size.width, height: additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.groupTableViewBackground
cell.addSubview(additionalSeparator)
}
This is the easiest solution I've found:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
" "
}
then just set the height to whatever you want:
tableView.sectionHeaderHeight = 30.0
I came across a way that has allowed me to effectively change the gap between cells.
In Interface builder I set the row height to be 46.
In the cellForRowAtIndexPath method of my TableView delegate I set the frame of the cell to be a smaller value.
cell.frame=CGRectMake(44,0,tableView.bounds.size.width,44)
This gives me a cell with a height of 44 that fits the width of the tableView but the space provided for the row will be 46 as defined in IB.
I was filling the cell programmatically anyway so this suited me fine.
You should implement
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
delegate method. and return 100.0 there.