Preventing UICollectionView from scrolling outside of contentOffset - ios

I created a collectionView with 4 cells, the cell frames are equal to the entire window frame so that I can scroll through them as they were a PageViewController.
lazy var collectionView : PagesCollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionView = PagesCollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.alwaysBounceHorizontal = false
return collectionView
}()
The goal I want to accomplish is that when I am in the first cell and scroll on the left (contentOffset negative) the collectionView scrolling stops, and when I am on the last cell the collectionView stops scrolling in the right direction, I want to see the cell still.
I tried different procedures:
This one blocked the scrollView only after having scrolled it in the first place, never re-enabling the scrolling in the other direction:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
scrollView.isScrollEnabled = false
}
}
As suggested on other StackOverflow topics:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x == 0){
collectionView.isScrollEnabled = false
}
collectionView.isScrollEnabled = true
}
But this just blocks the scrolling in both directions.
I actually don't know how to solve this.

I actually figured out how to solve this problem, it's really easy.
In my viewDidLoad method where I set the collectionView I set bounces equal to false:
collectionView.bounces = false

Related

Add expandable UITextView cell to UICollectionView

I have collection view when one of it's cell is cell that contain UITextView. What i want is to add textView to this cell that will expand accordingly to entered text.
I created UIView that have text view inside:
private var textView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isUserInteractionEnabled = true
textView.isEditable = true
return textView
}()
Inside i set up it's constraints to superview as following:
private func setConstraints() {
let width: CGFloat = 312
textView.topAnchor.constraint(equalTo: topAnchor).isActive = true
textView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
textView.widthAnchor.constraint(equalToConstant: width).isActive = true
}
Then inside cell i set constraints like that:
func setConstraints() {
textView.topAnchor.equalTo(topAnchor).isActive = true
textView.leftAnchor.equalTo(leftAnchor).isActive = true
textView.rightAnchor.equalTo(rightAnchor).isActive = true
textView.bottomAnchor.equalTo(bottomAnchor).isActive = true
}
I did achieve behaviour when cell is expanding after textView growth, but, i didn't set bottom constraint for UITextView->UIView, that why my collection view doesn't grow (scroll) when i type large bunch of text in my cell.
How to add cell with UITextView inside that will expand CollectionView it belongs to?
I think a better way to use manual estimation text view height and return one in
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
This approach is more difficult than using AutoLayout but gives more control and customization possibilities.

Is it possible to add a drag to reload horizontally for a collection view?

Update:
I belive it may not be possible given the folowing line in apples documentation:
When the user drags the top of the scrollable content area downward
Found here.
Let me know if there is a way to do this.
I am trying to make it so that when the user swipe left (the way you swipe up in many apps with tableViews to reload) in a collection view it will show a loading icon and reload data (the reload data part I can handle myself).
How can I detect this so I can call a reloadData() method?
Note: I am using a UICollectionView which only has one column and x rows. At the first cell if the user swipes left it should show a loading icon and reload data.
I am looking for a way to detect the slide left intended to reload.
What I have tried:
let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
viewDidLoadMethods()
refreshControl.tintColor = .black
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
collectionView.addSubview(refreshControl)
collectionView.alwaysBounceHorizontal = true
But this only works vertically.
I solved this problem with the following, but I should note that there is no default fucntionality like there is for vertical refresh:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
let inset = scrollView.contentInset
let y: CGFloat = offset.x - inset.left
let reload_distance: CGFloat = -80
if y < reload_distance{
shouldReload = true
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let _ = scrollView as? UICollectionView {
currentlyScrolling = false
if shouldReload {
baseVC.showReloading()
reloadCollectionView()
}
}
}

Collection view with autosizing cells stops working after reloading

In my app I have a collection view with cells autosizing horizontally.
Here's some code:
// called in viewDidLoad()
private func setupCollectionView() {
let cellNib = UINib(nibName: SomeCell.nibName, bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: SomeCell.reuseIdentifier)
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
}
The cell has 1 view, which has constraint for heigth. This view subviews a label, which is limited with 2 rows and is not limited by width. The idea here is to allow label to calculate its own width fitting text in 2 rows.
In order to make this work I've added the following code to the cell class:
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
Now, it works perfectly. Cells are autosizng, scrollView is scrolling horizontally etc. Until I call reloadData() at least once. Then cells have size 50x50 and never autosize any more until I leave the screen and come back again.
Do you have any ideas on why is it happening?

Enable UITableView scrolling during a scroll in UIScrollView

I have a UITableView inside a UIScrollView
The table view looks like:
private(set) lazy var tableView: UITableView = {
let tableView = UITableView(frame: CGRect.zero, style: .plain)
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.rowHeight = 70
tableView.backgroundColor = .blue
tableView.bounces = false
tableView.alwaysBounceVertical = false
tableView.isScrollEnabled = false
return tableView
}()
As you see I have disabled the scrolling in the table view because I want it to scroll along with the rest of the views inside the UIScrollView. What I want to achieve though, is to enable the scrolling of the tableView when the UIScrollView has reached certain offset, therefore I have that in the scrollViewDidScroll delegate method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= 160 {
self.view.tableView.isScrollEnabled = true
}
}
The above works partially. The UITableView that just had it's scrolling enabled doesn't scroll, unless I lift my finger off the screen and start scrolling again.
Any ideas how can I get the desired behaviour ?
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behaviour can result because touch events for the two objects can be mixed up and wrongly handled.

Nested UICollectionView does not hide NavigationBar on swipe

I have a UICollectionViewController (embedded in a NavigationViewController), which scrolls a UICollectionView horizontally via paging through some sections:
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView?.backgroundColor = .white
collectionView?.register(FeedCell.self, forCellWithReuseIdentifier: cellId)
//collectionView?.contentInset = UIEdgeInsetsMake(MenuBar.height, 0, 0, 0)
//collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(MenuBar.height, 0, 0, 0)
collectionView?.isPagingEnabled = true
Each section or page contains another UICollectionView (inside the FeedCell) which scrolls vertically through some UICollectionViewCells.
Inside the UICollectionViewController, I set
navigationController?.hidesBarsOnSwipe = true
which was working as long as there was only one UICollectionView. But since the (Top)CollectionView is scrolling horizontally and is containing additional (Sub)CollectionView, that are scrolling vertically, this feature seems not to work any longer.
I would like the NavigationBar to hide when the (Sub)CollectionView is scrolling vertically. Is there any hack to achieve this?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let navigationBar = self.navigationController?.navigationBar {
let clampedYOffset = contentOffset.y <= 0 ? 0 : -contentOffset.y
navigationBar.transform = CGAffineTransform(translationX: 0, y: clampedYOffset)
self.additionalSafeAreaInsets.top = clampedYOffset
}
}
This is a solution that I came up with. Basically modify the transform of the NavigationBar to move it out the way when necessary. I also modify the additionalSafeAreaInset, as this will automatically shift all your content up to fill the space left by the navigation bar.
This function will be called as part of the UICollectionViewDelegate protocol.
This was suitable for my purposes - but if you want the navigation bar to appear when the user rapidly scrolls up (like in safari) you will have to add some additional logic.
Hope this helps!
You can try the code like this (Swift 3.0):
extension ViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let isScrollingUp = scrollView.contentOffset.y - lastY > 0
lastY = scrollView.contentOffset.y
self.navigationController?.setNavigationBarHidden(isScrollingUp, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
// show navigation bar ?
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// show navigationBar ?
}
}

Resources