CollectionView with a TableView is clipped when rotating screen - swift

I have a CollectionView with one item. This item consists of a tableView with 1 cell. Here is how it looks in Storyboard hierarchy:
In my onboardingTableViewCell I have all displayed to user content: imageView and ShadowView (UIView).
When device is in a portrait mode - everything displayed properly - ShadowView bounds equal its Superview bounds:
But when I switch to landscape because of the shadow I can see that actually content bounds to Safe Area instead of Superview:
I want it to be pinned to Superview bounds, so the shadow looks properly.
But all of my constraints in Storyboard are set to Superview. CollectionView is set in code like that:
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView.collectionViewLayout.invalidateLayout()
let indexPath = IndexPath(item: self.currentPage, section: 0)
DispatchQueue.main.async {
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
What is the problem there might be and how can I fix that?

Related

displaying collectionView Cells starting from bottom - swift programmatically

I'm trying to display cells in a collection view one in top of the other. the layout I want to achieve is described properly in this delegate layout function:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.collectionView.frame.width, height: 190)
}
So let's make an example that we have 20 cells to display.
I want that the first cells displayed are the last ones.
Now I'm starting displaying the first cells and then I scroll to the last ones in this way:
self.collectionView.scrollToItem(at: collectionView.lastIndexpath(), at: .top, animated: true)
extension UICollectionView {
func lastIndexpath() -> IndexPath {
let section = max(numberOfSections - 1, 0)
let row = max(numberOfItems(inSection: section) - 1, 0)
return IndexPath(row: row, section: section)
}
}
How can I start displaying the collection view from the ending elements without having to display first the first elements?

Vertical and Horizontal Scrolling When Embedding a Table View in a Collection View that has a Collection View Header and Horizontal Flow Layout

I am trying to implement an application that allows a user to swipe horizontally between collection view cells while also being able to scroll vertically to see the entire content of a particular cell.
I want all the collection view cells to have a table view embedded within them.
The problem I am running into as of now is that my horizontal scrolling works as I have set the collection view flow layout to horizontal. I know that collection view flow layouts can only support one direction. Because of this, I tried to implement the following solution.
As of now within my project, I have a view controller with a scroll view inside. Embedded within the scroll view, is a collection view. This collection view has its own header implemented via dequeueReusableSupplementaryView.
I know that constraints can often be an issue preventing vertical scrolling to take place so here is a picture of my constraints:
Additionally, here is some code that I have used to implement this system:
Determining the scroll view content size
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.contentSize.height = collectionView.frame.size.height
scrollView.contentSize.width = self.view.frame.size.width
}
Setting up the collection view
func setupCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib(nibName: "TableViewHolderCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "tableViewHolderCollectionViewCell")
collectionView.register(UINib(nibName: "CustomCollectionViewHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CustomCollectionViewHeaderView")
collectionView.delaysContentTouches = true
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.bounces = false
collectionView.isPagingEnabled = true
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionHeadersPinToVisibleBounds = true
layout.sectionInset = UIEdgeInsets(top: 0, left: -self.view.frame.size.width, bottom: 0, right: 0)
collectionView.setCollectionViewLayout(layout, animated: false)
}
Setting up the collection view data
extension TasksAndScheduleViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tableViewHolderCollectionViewCell", for: indexPath) as! TableViewHolderCollectionViewCell
cell.backgroundColor = colorArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 343)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//setting up header view
return headerView
}
}
After trying to debug, I realized that the collection view vertical scroll might have been overriding the vertical scroll of the scroll view I had added to my view controller. In order to solve this, I created a custom class (as seen below) which my collection view implemented. To my knowledge, this was successful at disabling the vertical scroll for the collection view but it was not successful in enabling the other scroll view's vertical scroll.
class CollectionViewVerticalScroll: UICollectionView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let direction = panGestureRecognizer.direction(in: self)
if direction.contains(.Down) || direction.contains(.Up) {
return false
}
return true
}
}
My desired goal is to have the ability to swipe horizontally between these collection view cells that have table views while also being able to vertically scroll the collection view cell and the contents of the table view embedded inside of it. Ideally, the vertical scroll should allow me to scroll the table view cells while also moving the entire view upwards. The closest example to what I am trying to implement that I could find online is twitter's search page. The only difference is that my application has a collection header view and no navigation bar. I have attached a picture below:
I would appreciate any help. Please do let me know if you have any questions or if something doesn't make sense to you.

Swift animate underline bar when selecting cell in collection view?

I need to add animate underline bar in collection view when selected.
Here is my code below:
class TestViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet var segmentCollectionView: UICollectionView!
var segmentTitle = ["Transport","Hotels","Food","Beverages","Boardings"]
var selectedIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewFitScreen()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return segmentTitle.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SegmentCollectionViewCell", for: indexPath) as! SegmentCollectionViewCell
let segmentValue = segmentTitle[indexPath.item]
cell.nameLbl.text = segmentValue
if selectedIndex == indexPath.item{
cell.underlineBarView.backgroundColor = #colorLiteral(red: 0.1921568627, green: 0.2, blue: 0.3333333333, alpha: 1)
}else{
cell.underlineBarView.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectedIndex = indexPath.item
self.segmentCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
self.segmentCollectionView.scrollToItem(at: indexPath, at: [.centeredHorizontally], animated: true)
self.segmentCollectionView.reloadData()
}
by above method I've achieve this Segment-Selection.
But what I need is to animate like this Segment-Scroll-Animate.How to do like this?. Any help is appreciated.
As per your code, you are changing the color of the UnlineView bar that will show animation like hiding/showing view.
So to achieve your required animation on the underline bar you need to move ur Underline Bar from CollectionView Cell to outer view or move it on its super view.
And then change the position of the view based on cell selection.
Something like the below code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectedIndex = indexPath.item
UIView.animate(withDuration: 1.0) {
//Change position of your UnderlineBar
self.underineBar.frame = #Set Appropriate Frame Here#
self.view.layoutIfNeeded()
}
}
this will animate ur view from the current position to the selected cell position.
Hope this will helps to get ur animation on the underline bar.
The more clean approach is to draw the indicator as part of UICollectionView UICollectionReusableView. There is a nice example of how to work with it in emilwojtaszek/collection-view-layout-examples.
Note: If you want indicator to change its' width relative to menu cell width, this task might be considerably more complicated. Simplest way is to use the Parchment library. It uses scroll events to compute relative portion of currently scrolled page, to get menu cells which are part of transition and only then change width attribute of UICollectionReusableView.

Collection cell wrongly resized in reloadData

I don't know why but the cells of my collectionView are automatically wrongly resized after a reloadData.
After first init, then after reloadData :
I'm using UICollectionViewDelegateFlowLayout to define the size of the cell but it don't take care of it :
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (2 + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / 2
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
When execution come in the cellForItemAt, the cell have the right size, the shadow is created in this function and it have the right width.
One last thing is that only the width is resized, the height stay as I want.
Does any one have an idea why the width is resized wrongly after the reloadData ?
I was having a similar issue when making an extension of the UICollectionViewDelegateFlowLayout and it turns out that setting the collectionView's Estimated Size to "None" in the storyboard (Size Inspector) solved it.
As stated in the Xcode 11 Release Notes:
Cells in a UICollectionView can now self size with Auto Layout
constrained views in the canvas. To opt into the behavior for existing
collection views, enable “Automatic” for the collection view’s
estimated size, and “Automatic” for cell’s size from the Size
inspector.
If deploying before iOS 13, you can activate self sizing collection
view cells by calling performBatchUpdates(_:completion:) during
viewDidLoad(). (45617083)
So, newly created collectionViews have the attribute "Estimated Size" set as "Automatic" and the cell's size is computed considering its subview dimensions, thus ignoring the UICollectionViewDelegateFlowLayout extension methods, even though they are called.

UICollectionView rounded imageView

I have a UIImageView inside a UICollectionView Cell.
I wanted there to be 2 cells per column in the uicollectionview so I used this code....
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)
}
For the image view I wanted it to be round, and this code usually works elsewhere..
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
I have tried putting that in the cellForItemAt, with no luck
Now inside the CollectionView Cell Class I added it like this
override func layoutSubviews() {
super.layoutSubviews()
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}
The image looks like a deflated football.
Is the padding code messing up the rounded image view code?
You need to add self.accountImageView.layoutIfNeeded().
And make sure height and width of your imageview is equal
override func layoutSubviews()
{
super.layoutSubviews()
self.accountImageView.layoutIfNeeded() // add this
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}
If you are using constraint then try this -
override func layoutSubviews() {
super.layoutSubviews()
self.accountImageView.layoutIfNeeded() // Add this line
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}