change width of tableviewcell swift - uitableview

I have a tableView using IB with custom cells and prototype cells.
I'm trying to make the cells a little shorter in width than the tableView.frame to leave a little space between the left and right corners.
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
cell.bounds = CGRectMake(10, self.tableView.frame.origin.y, 30, self.tableView.frame.size.height)
cell.layer.bounds = CGRectMake(10, self.tableView.frame.origin.y, 30, self.tableView.frame.size.height)
cell.textLabel?.bounds = CGRectMake(10, self.tableView.frame.origin.y, 30, self.tableView.frame.size.height)
Update: here is a good example explaining how to add a subView to your tableView.
http://natashatherobot.com/ios-frame-vs-bounds-resize-basic-uitableview-cell/
Update 2: Looks like there isn't an easy way to do this. There are 3 ways of achieving this as far as I know:
Add a rounded and a shorter image to your cell that has the same exact color and matches your background.
You could subclass tableViewCell and then play with the layoutSubviews, this way you can make it shorter before it draws the cell. I've done it but the scrolling performance sucks.
The best way is to ditch the tableView altogether and re-do it with a collectionView.

The cells in the tableview are supposed to be as wide as their container.
If you need your cells to have a different width than the table view, I would suggest adding a view as subview to cell.contentView and make that view as wide as you need while making sure the contentView has clear background and no separator and all (so that it appears it is not there).
Another solution would be to have the tableView not as wide as it's superview by adding the left/right padding to it. But the you would have the issue that on the left and right side, where the padding is, you won't be able to scroll the tableView
I consider the cleanest solution to use a collectionView. It is not that much different than a tableView and you can configure the entire size of the cell, not just the height.
Hope this helps you fix your problem. Let me know if you need more help.

Swift 3:
For people still looking for a good solution, there's an easier and more effective alternative to using layoutSubviews or re-doing the whole thing with collectionView.
If you have already subclassed the tableViewCell, then in your TableViewController class you can add this to add a plain white border to each side of the table view cell.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// make sure in storyboard that your cell has the identifier "cell" and that your cell is subclassed in "TableViewCell.swift"
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
// the following code increases cell border on all sides of the cell
cell.layer.borderWidth = 15.0
cell.layer.borderColor = UIColor.white.cgColor
return cell;
}
If you want to add different sized borders to each side of the cell, you can also do this:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
// the following code increases cell border only on specified borders
let bottom_border = CALayer()
let bottom_padding = CGFloat(10.0)
bottom_border.borderColor = UIColor.white.cgColor
bottom_border.frame = CGRect(x: 0, y: cell.frame.size.height - bottom_padding, width: cell.frame.size.width, height: cell.frame.size.height)
bottom_border.borderWidth = bottom_padding
let right_border = CALayer()
let right_padding = CGFloat(15.0)
right_border.borderColor = UIColor.white.cgColor
right_border.frame = CGRect(x: cell.frame.size.width - right_padding, y: 0, width: right_padding, height: cell.frame.size.height)
right_border.borderWidth = right_padding
let left_border = CALayer()
let left_padding = CGFloat(15.0)
left_border.borderColor = UIColor.white.cgColor
left_border.frame = CGRect(x: 0, y: 0, width: left_padding, height: cell.frame.size.height)
left_border.borderWidth = left_padding
let top_border = CALayer()
let top_padding = CGFloat(10.0)
top_border.borderColor = UIColor.white.cgColor
top_border.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: top_padding)
top_border.borderWidth = top_padding
cell.layer.addSublayer(bottom_border)
cell.layer.addSublayer(right_border)
cell.layer.addSublayer(left_border)
cell.layer.addSublayer(top_border)
return cell;
}
Hope this helps.

I found the other answers unhelpful/incorrect, but found what I needed in one of the answers here:
How to set the width of a cell in a UITableView in grouped style
The key is, if you have a custom cell (which OP has, and most apps would have anyway), then you can override the setFrame method to whatever width you need. No need to redesign your app or do anything tricky.

Related

Corner radius not properly fit in dynamic grouped tableView swift

I have created a Grouped TableView dynamically based on data. Based on data tableView cell generated automatic height so every cell has different rowHeight. I have set it accordingly by using self.tableView.rowHeight = 50
But Issue is I am using corner radius, but I don't want to use corner radius on every cell.
I am using grayBox UIView and all cells displayed in it. Corner radius apply to start of cell or grayBox and only at end of cell of grayBox but it applied to every cell. How can I do that corner radıus apply on start and bottom?
viewDidLoad() code for tableView Row Height
self.tableView.rowHeight = 50
Dynamic Grouped TableView code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.backgroundColor = UIColor.clear
cell.selectionStyle = .none
let grayBox = UIView(frame: CGRect(x: 5, y: 0, width: self.view.frame.size.width - 11, height: 50))
grayBox.backgroundColor = ("#cfd8dc").toColor()
grayBox.layer.cornerRadius = 5
grayBox.layer.borderColor = UIColor(red:0.80, green:0.80, blue:0.80, alpha:1.0).cgColor
grayBox.layer.borderWidth = 1.0
cell.contentView.addSubview(grayBox)
return cell
}
1- Use dequeue
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
Instead of
let cell = UITableViewCell()
2- Clear subviews here by removing with tag
let grayBox = UIView(frame: CGRect(x: 5, y: 0, width: self.view.frame.size.width - 11, height: 50))
grayBox.tag = 333
cell.contentView.subviews.forEach {
if $0.tag == 333 {
$0.removeFromSuperview()
}
}
cell.contentView.addSubview(grayBox)
3- For corner raduis
if indexPath.row == 0 || indexPath.row == arr.count - 1 {
grayBox.layer.cornerRadius = 5
}
else {
grayBox.layer.cornerRadius = 0
}

Swift: Continuous resizing of tableViewCell while user types in a UITextView

Introduction
Context:
I'm creating one of my first apps but I've ran into an issue I cannot figure out.
I have tableView with cells packed with quite a few UIElements. All constraints are done using the EasyPeasy library which basically just sets auto layout constraints ( I have tried setting them manually also). The UITextView in question is constrained by various numbers to the left, right, top and bottom, I have no constraints on it for height or width.
in cellForRowAt indexPath: I set the textView delegate for each cells textView to self, using a delegate property declared within the cells custom class. I also tag every textView with its cells indexPath.row (gives textView.tag integer in textViewDidChange method).
Issue/acknowledgments:
After browsing SO a lot I've found a few questions alike this but I have not been able to make them work for me, I have implemented parts of them that felt logic to my case. I believe the problem differencing my situation from those questions lies in that for my cell design to work the cells has to have a height of itemHeight or higher.
I have noticed that as I type into the textview the textview itself increases in height (even below the cells border but its not visible as it reaches that point), however the cell itself doesn't resize.
I've tried with a cell that only contains a textView so the problem must lie in the textViewDidchange or heightForRowAt indexPath methods.
Question:
What am I doing wrong here? Why doesn't the cells height change dynamically as I type in the textView?
Code:
func textViewDidChange(_ textView: UITextView) {
var newframe = textView.frame
newframe.size.height = textView.contentSize.height - textView.frame.size.height + itemHeight[textView.tag]
textView.frame = newframe
let ndxPath = IndexPath(row: textView.tag, section: 0)
let cell = tableView.cellForRow(at: ndxPath) as! EventsCell
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: textView.frame.height)
tableView.beginUpdates()
tableView.setNeedsLayout() //have tried without this line
tableView.layoutIfNeeded() //have tried without this line
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if UITableViewAutomaticDimension > itemHeight[indexPath.row] {
return UITableViewAutomaticDimension
} else {
return itemHeight[indexPath.row]
}
}
TextView constraints:
let containerView : UIView = {
let cv = UIView(frame: .zero)
cv.backgroundColor = .white
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.cornerRadius = 7
return cv
}()
let eventText : GrowingTextView = { // GrowingTextView is a extension to a regular UITextView
let tv = GrowingTextView()
tv.allowsEditingTextAttributes = true
tv.isScrollEnabled = false
var delegate: UITextViewDelegate?
tv.textContainerInset = UIEdgeInsetsMake(1, 1, 0, 1)
tv.autoresizingMask = .flexibleHeight
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
containerView.addSubview(eventText)
contentView.addSubview(containerView)
containerView .easy.layout([Height(CGFloat(95 * itemCount)), Left(8), Right(8)])
eventText .easy.layout([Left(77), Right(5), Top(90), Bottom(4)])
}
Thanks for reading my post.
The constraints that determine the height should be laid out in such a way that the textView is attached directly to the top and bottom of the contentView or to views which are connected to the top and bottom of the contentView, so that autolayout can make out the height by connecting the constraints.
Make sure that you do not mention a height for the textView and disable scrolling. Let automatic dimension take care of all that.
Now all you need to do is call tableView.beginUpdates() and tableView.endUpdates() on textViewDidChange
Here is my repo which demonstrates the same.
OP Edit:
You should store the additional height that you add in a variable in the cell class so the cells can reload an appropriate height when the tableVIew is reloaded.
You should also change textViewDidChange method
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: textView.frame.height)
to
let newFrame = ”originalCellHeight” - ”originalTextViewHeight” + textView.contentSize.height
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: newFrame )`

Image adding in a Custom TableView

Is it possible to design the below UI using tableview ?
I know, I can define numberOfSections in the tableview. In each cell, I can define number of column. But, i am not sure how to add the image in the tableView ? What i understand is the image will be on the right side merging some cells. But how to do it or is it possible or not ?
Any help will be highly appreciated.
You will not be able to add an image to those 4 cells at once.
As I see it, there are two options:
create a single cell for those 4 cells, in which use UIStackViews or just autolayout to lay the contents out along with the image.
Change the tableView to a collectionView with custom layout implementation, then you can have the image as a single cell laid out to the right of those cells.
I myself would chose the first approach, because I believe it will be easier and faster to implement.
Yeah its possible. You need to do some calculation for Y Position, trailing space from tableview, etc
Add your image view into tableView.
let imageWidth: CGFloat = 150.0
let imageHeight: CGFloat = 150.0 //Addition of height of meging cells
let trailingSpace: CGFloat = 25.0
let yPosition: CGFloat = 100 //Calculate Y position depend on NavigationBar, Name cell, etc...
let imgView = UIImageView(frame: CGRect(x: view.frame.width - imageWidth - trailingSpace, y: yPosition, width: imageWidth, height: imageHeight))
imgView.backgroundColor = .clear
imgView.image = UIImage(named: "Waterfall")
imgView.contentMode = .scaleAspectFit
tableView.addSubview(imgView)

Automatic scrolling animation of the text in a UITableViewCell

I have a long string:
"A very long string"
that won't be displayed fully in a UITableView, i.e. it gets truncated to:
"A very long s..."
Is it possible to have this text in a UITableViewCell automatically scroll horizontally and indefinitely so that it loops around? Perhaps this can be achieved using an attributed string?
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
cell.textLabel?.text = "A very long string"
cell.textLabel?.textAlignment = .center
Thanks in advance for any help with this!
EDIT (my attempt at a solution proposed)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
let frame = CGRect(x: 0.0, y: 0.0, width: cell.bounds.width * 2.0, height: cell.bounds.height)
let scrollView = UIScrollView(frame: frame)
cell.addSubview(scrollView)
let label = UILabel(frame: scrollView.bounds)
scrollView.addSubview(label)
label.text = "some very long text that doesn't quite fit"
label.textAlignment = .center
let newFrame = CGRect(x: label.frame.width, y: 0, width: label.frame.width, height: scrollView.frame.height)
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse], animations:( { void in
scrollView.scrollRectToVisible(newFrame, animated: true)}),
completion: ({ completed in }))
}
Scrollview inside a table view is a bad idea. Instead you can enable the automaticDimension and estimated row height and tableView will manage the cell height automatically.
self.tableView.estimatedRowHeight = 44
self.tableView.allowsMultipleSelection = true
So as per the content each cell will manage its own height.
Hope this would be helpful!
You would have to make a custom tableview cell, which has a custom scrollview which handles UILabel scrolling. You would need to assign the text and measure the width. If it is wider than the containing view then add a second duplicate label to its right. You then need to animate the scrollview to the left by the width of the label. On complete, jump back to offset 0,0 and animate again.
Usually, to have special effect as such will require customization of an UILabel. To make my life easier, I will always try to search if there is already a working library out there for me to consume.
A simple Google search brings me MarqueeLabel, hope it helps!

UICollectionView Refresh Issue

Using UICollectionView I add a Label to a cell as a subView with the scroll direction set to horizontal. Inside the Label I add a button whose background is an image. For some odd reason if I scroll the view in one direction and then come back the buttons image seems to either have left remnants to the right of the button. Either that or their is another button slightly shifted to the right under the initial buttons. I have realized the more I played around with the buttons the further right they shift. Any assistance would be appreciated
The issue does not seem to occur if the label is shorter
var padding = String(count: 2, repeatedValue: (" " as Character))
let newLabel = UILabel(frame: CGRectZero)
newLabel.autoresizingMask = .FlexibleHeight
newLabel.backgroundColor = UIColor(red: 56/255, green: 143/255, blue: 212/255, alpha: 1)
newLabel.text = "\(padding)\(title)\(padding)"
newLabel.textColor = UIColor.whiteColor()
let fontName: CFStringRef = "Superclarendon-Regular"
newLabel.font = CTFontCreateWithName(fontName, 15, nil)
newLabel.adjustsFontSizeToFitWidth = true
newLabel.clipsToBounds = true
newLabel.layer.cornerRadius = 4
//Fit TO Text
newLabel.numberOfLines = 1
newLabel.sizeToFit()
//Add Button
if let image = UIImage(named: "Nav_Button_X"){//?//.CGImage
let button = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
var imageWidth = image.size.width
var imageHeight = image.size.height
//Resize label for button
var oldLabelFrame = newLabel.frame
var buttonHeight = self.frame.height - self.sectionInsets.top - self.sectionInsets.bottom
var buttonWidth = newLabel.frame.width + imageWidth
//newLabel.frame = CGRect(x: oldLabelFrame.origin.x, y: oldLabelFrame.origin.y, width: oldLabelFrame.width, height: bh)
newLabel.frame.size = CGSize(width: buttonWidth, height: buttonHeight)
button.frame = CGRectMake(newLabel.frame.width - imageWidth, 0, imageWidth, newLabel.frame.height)
button.setBackgroundImage(image, forState: UIControlState.Normal)
newLabel.addSubview(button)
}
return newLabel
}
EDIT:
I have tried recreating the view using only a button and NSMutableAttributedString for styling, which may or may not be a better solution shrugs, and the issue persists so it may not be an issue of how I construct the button. Are there suggestions?
I subclass and set up the UICollectionView like so
let flowLayout:UICollectionViewFlowLayout = UICollectionViewFlowLayout();
flowLayout.scrollDirection = UICollectionViewScrollDirection.Horizontal
super.init(frame: CGRectMake(0, 0, width, height), collectionViewLayout: flowLayout);
self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier);
self.autoresizingMask = UIViewAutoresizing.FlexibleWidth
self.backgroundColor = UIColor.whiteColor()
self.bounces = true
self.layer.cornerRadius = 5
self.scrollEnabled = true
self.delegate = self;
self.dataSource = self;
self.backgroundColor = UIColor.whiteColor();
and
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell;
cell.backgroundColor = UIColor.clearColor();
let cellItem = subCategories[indexPath.row]
cell.contentView.addSubview(cellItem)
// Configure the cell
return cell
}
Another interesting trait is that I removed the button from odd numbered labels and found that when I perform the action to get the issue the buttons appear on all of the labels
This looks like your cells are being reused and they show views from old cells.
When you call dequeueReusableCellWithReuseIdentifier, the method returns a cell that is not visible anymore in the UICollectionView, but it doesn't clear it's content.
Let's say you have cells A, B and C visible, you add subviews to all of them (with cell.contentView.addSubview(cellItem)). When you scroll until cell A isn't visible anymore, and you call dequeueReusableCellWithReuseIdentifier to get a new cell (call it D), you'll reuse cell A, so it'll have the subviews you added before to cell A, and the subviews you add now to cell D.
To get rid of this, you have two options:
If you're using a custom UICollectionViewCell, you should override prepareForReuse method. There you should remove all contentView's subviews.
If you're using a UICollectionViewCell, you should remove all contentView's subviews before calling cell.contentView.addSubview(cellItem)
If you want to see if you're adding the button multiple times to reused cells, you can reproduce the issue and use the View Hierarchy inspector:
I created a small project to illustrate the issue of "dirty" cells: https://github.com/lucaslt89/DirtyUICollectionViewCellsExample
In my case, I created the view hierarchy in the init method of the cell.
When scrolling slowly, the cells along with the multiline labels would appear perfectly. However, when scrolling too quickly, labels would have the size of the reused cell's label, thus appearing very bad.
I was setting preferredMaxLayoutWidth correctly, but nevertheless the cells would have issues on fast scrolling.
My problem was solved by calling setNeedsLayout inside prepareForReuse:
override func prepareForReuse() {
setNeedsLayout()
super.prepareForReuse()
}

Resources