UICollectionViewCells are tilted towards the right if their height is set differently - swift

I was running into an issue a few days ago that I have not been able to resolve even upon thorough internet search. My app is designed to present mathematical computation results in a UICollectionView where each UICollectionViewCell resembles a specific part of the results. These results are retreived from an Array that is passed by the computation code. For example, if you were to enter "2+2" it would display the input interpretation "2+2" rendered in MathJax in the first cell and then the result "4" in the second, also rendered in MathJax.
Output on input "2+2"
The app can also plot mathematical functions which requires the second cell to be bigger to be able to display the whole SVG. I therefore tried setting an individual height to the cells by using the sizeForItemAt-Method as follows:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height = Double()
if computationResults[indexPath.item].cellSize == "n" {
height = view.frame.height/5
}
else if computationResults[indexPath.item].cellSize == "b" {
height = view.frame.height/3
}
else {
height = view.frame.height/5
}
return CGSize(width: view.frame.width, height: height)
}
This code checks if it is specified for the cell to be normal sized ("n") or big ("b"). When running the code the height of the cells indeed changes and the graph also fits perfectly fine but the whole view seems to be tilted to the right and I cannot figure out why.
As you can see the cells are cut off in a weird way and appear to be shifted to the right.
I have no idea anymore what this could be. I have tried the following things:
restarting Xcode
redoing all the constraints on the Custom Cell .nib-file
setting estimated size to "none" in the attributes inspector
trying without any constraints on the Custom Cell
deleting the Custom Cell .nib-file and redoing it
setting the contentInset of the UICollectionView to CGPointZero
All that didn't fix it and I don't know what else to try. I only am very sure of it having to do with generally two cells having distinct heights. Upon inspecting the Debug Hierarchy I've also noticed the safe areas of both cell views weirdly passing the bounds of the actual ViewController but I can't really derive a cause from that.
As you can see the safe area is kinda too big. Same goes for the first cell's safe area by the way.
If it helps any, I'm using a Custom Cell via .nib-file and I have configured a FlowLayout that is configured as follows:
private func setupCollectionLayout() {
if collectionViewFlowLayout == nil {
collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.sectionInset = UIEdgeInsets.zero
collectionViewFlowLayout.minimumInteritemSpacing = 0
collectionViewFlowLayout.minimumLineSpacing = 0
collectionview.setCollectionViewLayout(collectionViewFlowLayout, animated: true)
}
}
I'm running Swift 5 and Xcode 14.0. If you have any questions feel free to ask and I hope somone can help me out.

Related

Apply sizeForItemAt to only one collectionView

I'm using sizeForItemAt to set the cell size for ONE view controller, but it seems that I have to return a result also for the other CollectionViews, the problem is that I don't have the size value for the others since it is defined in the storyboard. I'm trying this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if categorie_cv == collectionView {
let size: CGSize = categorie[indexPath.row].size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17.0)])
return CGSize(width: size.width + 40.0, height: categorie_cv.bounds.size.height)
} else if collectionView == risultati {
return THIS VALUE IS SET IN THE STORYBOARD
} else {
return THIS VALUE IS SET IN THE STORYBOARD
}
}
No, unfortunately you can not tell it to automatically use the intrinsic size of the element derived from its layout.
However if your layout would really allow for the intrinsic size to be configured non-ambiguously through AutoLayout/it's content, you can call UIView.intrinsicContentSize, possibly mixing with layoutIfNeeded call. I am not positive that this solution is 100% working though.
If I were you I would seek not the way to assign a delegate and implement sizeForItemAt but rather do define this size ALSO in the matters of the cell itself.
And by the way, actually, it's a bit of "will turn out NOT as you expected it to" to let the cells to determine their their content size. This can be very helpful and logical with UITableView, but layout calculation in case of UICollectionView is better to be determined by simpler, calculative approach.
That is, it's cheap to compute the width to be half of bounds width of collectionView and equal to height - rather then let the content freely layout itself using very dynamic content-compression rules and constraint priority evaluation. Typically, having all cells have absolutely nothing in common in terms of their aspect ratios, widths OR heights will really tend to output the undesirable results.

UICollectionView Cell with a thick border

I am attempting to create a round collection view cell.
In the cell I have a red UIView that is 0 from top, leading, trailing, and bottom. Inside of that cell I have another view which is white and it is 4 from the top, leading, trailing, and bottom. Within that view is a UILabel and a UIImageView.
The goal would be to have 2 cells per column and it is has a red ring around a white circle and text and an image.
To create the round UIViews I have an extension for UIView like this
extension UIView {
func createRoundView() {
layer.cornerRadius = frame.size.width/2
clipsToBounds = true
}
}
Inside of my cellForItemAt I say
cell.whiteBackgroundView.createRoundView()
cell.colorStatusView.createRoundView()
The goal is on the left, but what is happening is on the right.
Here is my Storyboard
The constraints are all blue, nothing red.
And to get the 2 cell per column I use this delegate method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionCellSize = collectionView.frame.size.width - padding
return CGSize(width: collectionCellSize/2, height: collectionCellSize/2)
}
Any idea what I might be doing wrong?
In the end it doesn't matter if I have a view within a view. I change the ring or border color based on other data....so I just need to be able to easily change that border color.
This code is not enough, to tell what's going on there... try to set all
backgroundColors to .clear
then set those properties to your view
customView.layer.cornerRadius = half of your width
customView.layer.borderWidth = 5
customView.layer.borderColor = UIColor.red.cgColor
Using #blinkmeoff answer I figured it out mostly (although follow up question to come)
Inside the cell class I added the following
self.backgroundViewContainer.layoutIfNeeded()
self.backgroundViewContainer.layer.cornerRadius = min(backgroundViewContainer.frame.size.width, backgroundViewContainer.frame.size.height)/2
self.backgroundViewContainer.clipsToBounds = true
I kept getting weird shapes by just doing the /2 so I added the min(ba...
and that got me perfect circles

Cell loaded in cellForItemAt, but not in visibleCells

I have a custom UICollectionView and the cells are loaded in cellForItemAt but when I try to get all the visible cells by using visibleCells I'm not getting all the cells.
For example, in cellForItemAt, I'm setting the alpha of the labels in the cells to 0. When panned, I want the alpha of those labels change to 1:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
handleLabel(scrollView, active: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if pickerIsActive { handleLabel(scrollView, active: false) }
}
private func handleLabel(_ scrollView: UIScrollView, active: Bool) {
guard let pickerView = scrollView as? UICollectionView else { return }
let cells = pickerView.visibleCells.flatMap { $0 as? CustomCell }
panningIsActive = active
UIView.animate(duration: 0.3) {
cells.forEach { $0.label.alpha = $0.isSelected || active ? 1 : 0 }
}
}
And cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.label.alpha = 0
return cell
}
What can I do to change all the "loaded" cells instead of just the "visible" cells?
The visibleCells are only the on screen cells. This used to be everything initialized in cellForItem:at: but as of iOS 10 UICollectionView now prefetches to improve scrolling performance (see WWD 2016 video) which maybe why you are having this problem. Anyways it sounds like all you want to do is animate the cells to fade in when they come on screen. You can either move your animation logic to willDisplayCell or subclass UICollectionViewCell. UIColectionViewCell inherits from UIView, so you can override didMoveToSuperView in your UICollectionViewCell and call your animation method there, which will cause the cell to animate as it appears.
I am using Xcode 11.4 and Swift 5, and I had the exactly the same issue: .visibleCells is not giving me all the loaded cells.
By reading #Josh Homann's answer and the comments below, I figured out 2 solutions.
The first solution is same as the solution you reached at: customize cell appearance in collectionView(_:willDisplay:_:) after it's loaded but before it's displayed on the screen.
Another quick and dirty solution is to simply uncheck UICollectionView's 'Prefetch' option in attributes inspector.
This fixes the issue because by disabling prefetching, UICollectionView will stop pre-loading cells that are not displayed on the screen, so .visibleCells are now all the loaded cells. This solution will work fine if you're simply loading static or small local data in the cells. If you're prefetching large data (e.g. images) from network for upcoming cells, you probably need Prefetching Enabled, then solution 1 is your go-to option.
It sounds like you might want to try using layoutAttributesForElements(in:).
You'll need to implement your own collection view layout subclass (rather than using the delegate methods) but I think it will be worth it in the long term.
Rather than manually managing the animations (via UIView.animateWithDuration) you use this method to tell the collection view what properties cells should have at different positions, and as people pan the collection view, the correct properties are automatically applied.
I tried to find a good Swift reference for this, but I could't, but here's a post in Objective-C that you can follow if you want to try this approach:
https://bradbambara.wordpress.com/2014/05/24/getting-started-with-custom-uicollectionview-layouts/

How to set the table view cell to adjust to label?

I am using Xcode to make an app. Using auto layout, I have a tableview contained in a container view which is part of a view controller embedded un a navigation controller.
This table view is my content page, so the individual tableview cells leads to another tableview which contains bulk of the information I would like to display.
I added UIlabel which contains a paragraph of words to each tableview cells and set the constraints to the respective tableview cells.
In the storyboard, it looks alright having to be able to see all the paragraphs. I have set the line to be 0, and word wrap.
When I run the programme with lets say 4s simulator, the table view cells do not display the whole paragraph but rather, just indicate "..." at the end of each paragraph.
How do I set the settings such that the tableview cells will adjust itself according to the UIlabel after being word wrapped giving a certain screen sizes of phone?
PS: I am new to SWIFT programming and thanks for taking your time to help me.
Have you done this?:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
This is required for self-sizing table view cells. Also make sure of the following things:
a) The number of lines of UILabel is set to 0
b) The autolayout system in the table view cell is proper -- all the views are pinned on all sides and the autolayout can accurately determine the height of the cell.
c) Also do a tableView.estimatedRowHeight = 75 (or your estimated height) for efficiency.
add this two line in viewDidLoad Method and label number of lines 0 and dont give any height.
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
//cell.customTexLabel Number 0f lines 0 and dont give height contrains just Leading,Trailing,TOP and Bottom.
cell.customTextLable?.text = "Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:Here index starts from 0 which means first element can be accessed using index as 0, second element can be accessed using index as 1 and so on. Let's check following example to create, initialize and access arrays:"
return cell
}
Hi #Daniel you can learn how to create self sizing cell here is the demo tutorial for custom dynamic cell
http://developerclouds.com/2016/03/23/table-view-custom-cell-with-self-sizing/

Making UITableViewCell a square based on screen width

I have a table view controller displaying square videos, and I want each table view cell to be a square as well. Is it possible to have each UITableViewCell dynamically resize according to the width of the screen (for example 320x320 for iPhone5 and 375x375 for iPhone6)?
In my view controller's viewDidLoad function I have:
frameWidth = self.view.frame.size.width
self.videosView.rowHeight = UITableViewAutomaticDimension
self.videosView.estimatedRowHeight = frameWidth
The constraints for the view that displays the video inside the UITableViewCell are:
Leading and trailing space to superview, top space to superview, 1:1 aspect ratio
I still can't get the cell to be a square though. Any help would be appreciated!
You can use this tableview method:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.width
}
Note: Method in Swift 3
There is a good reason that this is hard to do. Cells inside UITableView is reused so it can be allocated once. (for performance)
Resizing view is performance wise costly, that's way you only put one number as height of cell.
What you can do is to have if statement and return accordingly to phone version.
try to use it with bounds instead of frame
frameWidth = view.bounds.size.width