I have a UITableView with 3 cells, which will eventually serve as a dashboard. I am trying to configure the top cell with a circular progress view using KDCircularProgress - I have a UIView which I position with constraints, and then programmatically add the circular progress object.
However, when I rotate the device to landscape, the progress view shifts (see first image). I have tried various combinations of setNeedsLayout(), layoutSubviews and layoutIfNeeded() but no luck.
I also tried reloadData() in willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval), which gets me slightly further in that the view is correctly resized (see second image), however it has created a duplicate. Extract from cellForRowAt method below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let topCell = tableView.dequeueReusableCell(withIdentifier: "topCell", for: indexPath) as! DashboardTopTableViewCell
//Progress config
topCell.progressBar = KDCircularProgress(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: topCell.circularProgressContainer.frame.width, height: topCell.circularProgressContainer.frame.height)))
let colorForProgress: UIColor = UIColor(red: 69.0/255.0, green: 110.0/255.0, blue: 178.0/255.0, alpha: 0.8)
topCell.progressBar?.progressColors = [colorForProgress]
let progressAsAngle = ((percentageComplete/100)*360)
topCell.progressBar?.animate(toAngle: progressAsAngle, duration: 2.0, completion: nil)
topCell.circularProgressContainer.addSubview(topCell.progressBar!)
return topCell
So I am a bit stuck - any suggestions?
Avoid using addSubview in cellForRow. As the cell is reused, this extra added view will not get removed and on reloading cell, the views would be overlapped. You will not see the impact when overlapping is of same size and position, but as in your case you are rotating, you are able to see it. Add the views statically in XIb or view wherever you want but not in cellForRowAt.
However you can change the properties of the subviews in cellForRow.
If you add subviews in cellForRowAt you will feel the jerk while scrolling tableView.
Related
I kind of have a weird layout here, it's kind of like this (also see pics):
-UITableViewCell 1
----UIView 2
--------UITableView 3
The controller the the UITableView (1) is like that:
//mainTableView (1) controller
var cellHeights = [CGFloat]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let card = CardSource.orangeCards[indexPath.row]
cell.configureCardInCell(card)
cellHeights.insert(card.frame.height + 15, at: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath.row]
}
but the problem is that when the screen first loads, the UIViews overlap because the cells are too small because the smaller table view (the one in the UIView) hasn't loaded yet and it's height isn't defined. Proof of this is that when I scroll to the bottom of the main table view then scroll back up cellForRowAt is called again and the two views don't overlap anymore (see pics). So what I basically want is a way to load the small table view and define it's height before the bigger table view loads (or if you have any other solutions, that'd be welcome too)
I know my question isn't very clear, I'm not really good at explaining stuff, so don't hesitate to ask me questions in the comments.
Many thanks!
When the view first loads
After scrolling down then back up
EDIT:
I found this:
static var pharmacyOrangeCard: CardView {
let view = Bundle.main.loadNibNamed("Pharmacy Orange Card", owner: self, options: nil)?.first as! PharmacyTableCardView
print(view.frame.height)
return view
}
prints the correct height. But then, when I try to access it from the controller above, it gives me a smaller number! In the meanwhile, I applied these constraints:
self.translatesAutoresizingMaskIntoConstraints = false
self.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 35).isActive = true
card.centerXAnchor.constraint(equalTo: cell.centerXAnchor).isActive = true
card.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
But I don't think that affects height, does it?
EDIT 2:
Okay, so I've changed this constraint:
self.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 35).isActive = true
to this:
card.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 17.5).isActive = true
card.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -17.5).isActive = true
So these constraints seem to play a role because now I have this:
enter image description here
By the way, I don't know if that matters but I'm using XIB files for each of these "cards", and the height isn't constrained, so maybe that plays a role?
SOLVING EDIT:
I solved the problem by doing:
override func viewDidAppear(_ animated: Bool) {
mainTableView.reloadData()
}
Once a cell loaded on the screen, you cannot change height for that cell for better UI-Experience,
and in hierarchy heightForRowAt get called before cellForRowAt.
So you had 2 options to choose for a solution to your problem
first:: get your heights values ready before your table view try to loads cells in it (get heights array ready before setting delegate and datasource values to your tableView)
second:: whenever you need to update your tableView cells to re-established with respect to new height values, call this each time after you have updated your height values
yourTableView.reloadData()
So I have a UIProgressview in storyboard and I set the height constraint via storyboard and it's the height I expect and everything with that is sunshine and rainbows.
My problem comes into play when I'm setting the progress of it, I call
cell.progressBar.setProgress(22, animated: true)
(I'm using tableview cells btw for multiple progress bars)
and it fills the bar as expected BUT it also animates the height constraint so it goes from a height of 9 to a height of 25 or whatever I set.
I don't want this behavior.
Also it's worth mentioning that when I have other cells with progress bars in them and I only need to set 1, the other progress bars don't animate their height constraint, they simply have an empty bar with correct height (what I expect).
And I want to note that I'm setting the progress for every cell (variable number of cells) in cellForRowAtIndexPathso I'm not sure why setting the progress animates the height constraint and not setting the progress has the height constraint already fixed like I want. I do want the fill animation just without the height constraint being animated also.
Any help would be greatly appreciated.
Code for cell for row if it helps
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "basic_result_cell", for: indexPath) as! BasicResultCell
UIView.animate(withDuration: 1.0, animations: {
cell.progressBar.setProgress((Int(25))%, animated: true)
})
return cell
}
Thanks to Badhan for pointing it might be a thread issue.
here's what I did to fix it. Probably due to it being a closure or something. Anywho this works.
UIView.animate(withDuration: 1.0, animations: {
DispatchQueue.main.async {
cell.progressBar.setProgress((Int(25))%, animated: true)
}
})
bottom is want result page
bottom is my story board...
bottom is my result page ...
my story board how to first image file...?
divider is not work... I'm try
// saperator
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
let additionalSeparatorThickness = CGFloat(20)
let additionalSeparator = UIView(frame: CGRectMake(0,
cell.frame.size.height + additionalSeparatorThickness, cell.frame.size.width, additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.grayColor()
cell.addSubview(additionalSeparator)
}
background color not work
-> storay board settings firstView and TableView background color
If you want draw the cell like the picture you post, you should not use the cell's content view directly, you should try to add another custom view in your cell content view, and set the content view's background color to UIColor.clearColor(), then set the custom view's layer.cornerRadius to 5 maybe.
Hope it can help you.
The problem
I created a UICollectionViewController with a custom UICollectionViewCell.
The custom cell contains a large and rectangular UIView (named colorView) and a UILabel (named nameLabel).
When the collection is first populated with its cells and I print colorView.frame, the printed frames have incorrect values. I know they are incorrect, because the colorView frames are larger than the cell frame themselves, even though the colorView gets drawn correctly.
However, if I scroll the collectionView enough to trigger a reuse of a previously created cell, the colorView.frame now has correct values!
I need the correct frames because I want to apply rounded corners to the colorView layer and I need the correct coloView size in order to do this.
By the way, in case you are wondering, colorView.bounds also has the same wrong size value as the colorView.frame.
The question
Why are the frames incorrect when creating the cells?
And now some code
This is my UICollectionViewCell:
class BugCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var colorView: UIView!
#IBOutlet weak var nameLabel: UILabel!
}
and this is the UICollectionViewController:
import UIKit
let reuseIdentifier = "Cell"
let colors = [UIColor.redColor(), UIColor.blueColor(),
UIColor.greenColor(), UIColor.purpleColor()]
let labels = ["red", "blue", "green", "purple"]
class BugCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as BugCollectionViewCell
println("ColorView frame: \(cell.colorView.frame) Cell frame: \(cell.frame)")
cell.colorView.backgroundColor = colors[indexPath.row]
cell.nameLabel.text = labels[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let width = self.collectionView?.frame.width
let height = self.collectionView?.frame.height
return CGSizeMake(width!, height!/2)
}
}
The collection view is setup in order to show two cells at a time, vertically, each cell containing a large rectangle painted with a color and a label with the color name.
When I just run the above code on the simulator, I get the following printed result:
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,0.0,375.0,333.5)
ColorView frame: (0.0,0.0,320.0,568.0) Cell frame: (0.0,343.5,375.0,333.5)
It is a weird result - colorView.frame has a height of 568 points, while the cell frame is only 333.5 points tall.
If I drag the collectionView down and a cell gets reused, the following result is printed:
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,1030.5,375.0,333.5)
ColorView frame: (8.0,8.0,359.0,294.0) Cell frame: (0.0,343.5,375.0,333.5)
Something, which I can’t understand, happened along the way that corrects the frame of colorView.
I think it has something to do with the fact that the cell is loaded from the Nib, so instead of using the init(frame: frame) initializer the controller uses the init(coder: aCoder) initializer, so as soon as the cell is created it probably comes with some default frame which I can't edit anyhow.
I’ll appreciate any help that allows me to understand what is happening!
I am using Xcode 6.1.1. with the iOS SDK 8.1.
You can get the final frames of your cell by overriding layoutIfNeeded() in your custom Cell class like this:
override func layoutIfNeeded() {
super.layoutIfNeeded()
self.subView.layer.cornerRadius = self.subView.bounds.width / 2
}
then in your UICollectionView data Source method cellForRowAtIndexPath: do this:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.setNeedsLayout()
cell.layoutIfNeeded()
I had the same issue with a UICollectionViewCell using auto layout constraints.
I had to call layoutIfNeeded before I was configuring my subview that relied on the views frame width.
Had this issue with Core Graphics drawing in iOS 10, Swift 3.0.1.
Add this method to UICollectionView subclass:
override func didMoveToSuperview() {
super.didMoveToSuperview()
setNeedsLayout()
layoutIfNeeded()
}
My problem was that Core Graphics shapes were not calculated properly, because a layoutSubviews() wasn't called.
Ok, I understand now that the cell is created before auto layout defines its frames. That is the reason why at the moment of creation the bounds are wrong. When the cells are reused the frames have been already corrected.
I was having this problem while creating a custom UIView that placed some layers and subviews in specific coordinates. When instances of this UIView were created, the placement of the subviews were all wrong (because auto layout hadn't kick off yet).
I found out that instead of configuring the view subviews on init(coder: aCoder) I had to override the method layoutSubviews(). This is called when auto layout asks each view to layout its own subviews, so at this point at least the parent view has the correct frame and I can use it for laying the subviews correctly.
Probably if I had used constraints on the custom view code instead of dealing myself with frame sizes and positioning then the layout would have been done properly and it wouldn't be necessary to override layoutSubviews().
I'd suggest making a subclass of whatever you're doing. I needed a gradient over an UIImageView in my cell and it was calculating it wrongly. I tried the suggestion with layoutSubviews but it was also causing issues where it seems like it would apply gradient twice.
I made a UIImageView subclass and it works as wanted.
class MyOwnImageView: UIImageView{
override func layoutSubviews() {
super.layoutSubviews()
let view = UIView(frame: frame)
let width = bounds.width
let height = bounds.height
let sHeight:CGFloat = 122.0
let shadow = UIColor.black.withAlphaComponent(0.9).cgColor
let topImageGradient = CAGradientLayer()
topImageGradient.frame = CGRect(x: 0, y: 0, width: width, height: sHeight)
topImageGradient.colors = [shadow, UIColor.clear.cgColor]
view.layer.insertSublayer(topImageGradient, at: 0)
let bottomImageGradient = CAGradientLayer()
bottomImageGradient.frame = CGRect(x: 0, y: height - sHeight, width: width, height: sHeight)
bottomImageGradient.colors = [UIColor.clear.cgColor, shadow]
view.layer.insertSublayer(bottomImageGradient, at: 0)
addSubview(view)
bringSubviewToFront(view)
}
}
I haven't been able to find any examples of this online. How can I achieve the following effect? Instead of the standard slide-to-left effect when tapping a table row, I'd like the view controller transition animation to look like the following:
User taps a cell in the table
The cell starts growing to fill the screen, pushing other rows above and below it "offscreen".
As the cell grows, cells elements (text, images, etc.) cross-fade into the new view's contents until the new view completely fills the screen.
I'd like to also be able to interactively transition back into the table view by dragging up from the bottom edge, such that the reverse of the above is achieved. i.e. view starts shrinking back into a normal table view cell as the "offscreen" cells animate back into position.
I've thought about taking a snapshot of the table and splitting it up at the points above and below the cell, and animating these snapshots offscreen as part of a custom view controller transition. Is there a better way? Ideally I'd like to not take snapshots, as I may want to have animations, etc., still happening in the table view rows as they fade offscreen.
First thing first, i have seen your post today and his is written in swift programming language.
the follwing code is to expand the selected cell to the full screen, where the subviews will fade out and background image will expands.
First complete cellForRowAtIndexPath, then in didSelectRowAtIndexPath,
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
isSelected = true
selectedCellIndex = indexPath.row
tableView.beginUpdates()
var cellSelected: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellSelected.frame = tableCities.rectForRowAtIndexPath(indexPath)
println("cell frame: \(cellSelected) and center: \(cellSelected.center)")
let newCell = cellSelected.frame.origin.y - tableOffset
println("new cell origin: \(newCell)")
var tempFrame: CGRect = cellSelected.frame
variableHeight = cellSelected.frame.origin.y - tableOffset
tempFrame.size.height = self.view.frame.height
println("contentoffset: \(tableView.contentOffset)")
let offset = tableView.contentOffset.y
println("cell tag: \(cellSelected.contentView.tag)")
let viewCell: UIView? = cellSelected.contentView.viewWithTag(cellSelected.contentView.tag)
println("label: \(viewCell)")
viewCell!.alpha = 1
UIView.animateWithDuration(5.0, delay: 0.1, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
tableView.setContentOffset(CGPointMake(0, offset + self.variableHeight), animated: false)
tableView.contentInset = UIEdgeInsetsMake(0, 0, self.variableHeight, 0)
tableView.endUpdates()
cellSelected.frame = tempFrame
viewCell!.alpha = 0
println("contentoffset: \(tableView.contentOffset)")
}, completion: nil)
}
then update your heightForRowAtIndexPath, as
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if isSelected && (selectedCellIndex == indexPath.row) {
return self.view.frame.height
}else {
return 100
}
}
Ignore this if already solved. and this might be helpful to others.