Nested UICollectionView does not hide NavigationBar on swipe - ios

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 ?
}
}

Related

How to only show a a shadow under navigation bar once user beings scrolling?

Essentially I would like to enable a shadow radius below the navigation bar once the user begins to scroll. The navigation bar resides in a TableView controller, when the view controller is first opened the navigation controller should be in its normal state but once the user begins to scroll the shadow appears.
The following is the code I have so far for creating the shadow below the navigation bar:
//Adds Shadow below navigation bar
self.navigationController?.navigationBar.layer.masksToBounds = false
self.navigationController?.navigationBar.layer.shadowColor = UIColor.lightGray.cgColor
self.navigationController?.navigationBar.layer.shadowOpacity = 0.8
self.navigationController?.navigationBar.layer.shadowOffset = CGSize(width: 0, height: 2.0)
self.navigationController?.navigationBar.layer.shadowRadius = 2
How can it be enabled only when user begins to scroll?
You need to add those lines to display a shadow to a function and call that function from the following delegate method:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
Also it might help to call the opposite of your showShadow fucntion which will remove the shadow in the following delegate method:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
Add this to your View Controller:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.layer.masksToBounds = false
self.navigationController?.navigationBar.layer.shadowColor = UIColor.lightGray.cgColor
self.navigationController?.navigationBar.layer.shadowOpacity = 0
self.navigationController?.navigationBar.layer.shadowOffset = CGSize(width: 0, height: 2.0)
self.navigationController?.navigationBar.layer.shadowRadius = 2
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.navigationController?.navigationBar.layer.shadowOpacity = 0.8
}
if you want to remove the shadow when the scrolling stops, you can reset the values to normal in this method:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.navigationController?.navigationBar.layer.shadowOpacity = 0
}
You might also want to do the same when user is dragging the TableView instead of scrolling, in that case, add these two as well:
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.navigationController?.navigationBar.layer.shadowOpacity = 0.8
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.navigationController?.navigationBar.layer.shadowOpacity = 0
}
The navigation bar will have a shadow automatically as long as the scroll view in question is the first subview, or if you pass the scroll view to setContentScrollView() in iOS 15+.

iOS Unable to Disable Horizontal Scrolling when swipe the screen in scrollView?

In my application I have to implement ScrollView and page control, I used ScrollView for both vertical scrolling and horizontal scrolling. when I drag the screen with scroll horizontal means it works fine but when I drag the screen with scroll Vertical means it has some glitches like(scrolling both vertically and horizontally) unable to stop that or unable to find the issue.
So I decided to place two buttons named next and previous for horizontal scrolling for next page and previous page in page control so I want to stop horizontal scroll when dragging the screen, but I don't know how to stop horizontal scrolling(not vertical scrolling).
Here I have posted the code for Scrolling in page control and Next and previous button actions.
I have declared and called the ScrollView Delegate.
UIScrollViewDelegate
override func viewDidLoad() {
super.viewDidLoad()
configurePageControl()
scrollMainHolderView.delegate = self
scrollMainHolderView.isPagingEnabled = true
}
ScrollView Method is:
//MARK:- Scrollview delegate -
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControlNew.currentPage = Int(pageNumber)
self.scrollMainHolderView.contentSize=CGSize(width: self.view.frame.size.width * CGFloat(templateMutArray.count), height: CGFloat((globalYarr[Int(pageNumber)] as? CGFloat)!))
}
Set up code for Page control,
func configurePageControl() {
self.pageControlNew.numberOfPages = templateMutArray.count
self.pageControlNew.currentPage = 0
self.pageControlNew.tintColor = UIColor.red
self.pageControlNew.pageIndicatorTintColor = UIColor.black
self.pageControlNew.currentPageIndicatorTintColor = UIColor.green
}
Code for next and previous button action is,
#IBAction func pagePreviousBtnTapped(_ sender: Any) {
isHorizontalSCrolling = true
scrollMainHolderView.delegate = self
let scrollBounds = self.scrollMainHolderView.bounds
let contentOffset = CGFloat(floor(self.scrollMainHolderView.contentOffset.x - scrollBounds.size.width))
self.movScrollToFrame(contentOffset: contentOffset)
}
#IBAction func pageNextBtnTapped(_ sender: Any) {
isHorizontalSCrolling = true
scrollMainHolderView.delegate = self
let scrollBounds = self.scrollMainHolderView.bounds
let contentOffset = CGFloat(floor(self.scrollMainHolderView.contentOffset.x + scrollBounds.size.width))
self.movScrollToFrame(contentOffset: contentOffset)
}
From what i understand from the comments is that you want to stop horizontal scrolling. That is actually pretty straight forward.
You can stop horizontal scrolling or vertical scrolling in the ScrollViewDelegate Method. Here it is how,
Setting the contentOffset.x value to zero will prevent the scrollview scroll in horizontal direction.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
sender.contentOffset.x = 0.0
}

Swift: change tableview height on scroll

I have a VC as shown in the image
It has a UICollectionView on top, and a UITableView at the bottom.
CollectionView has 1:3 of the screen and TableView has 2:3 of the screen(set using equalHeight constraint).
I want to change the height of the UICollectionView when the tableView is scrolled.
When the tableView is scrolled up,I want to change the multiplier of equalHeights constraint to like 1:5 and 4:5 of collectionView and tableView respectively.This will ensure that height of tableView increases and collectionView decreases
When the tableView is scrolled down, the multiplier of equalHeights constraint should reset to default.
I've tried adding swipe and pan gestures to tableView, but they are unrecognised.
How to achieve this functionality?
P.S: Would love to achieve this functionality by using a pan gesture, so that dragging up and down changes the heights progressively.
This is the view hierarchy
EDIT/UPDATE
This is the code that I'm using.
class MyConstant {
var height:CGFloat = 10
}
let myConstant = MyConstant()
MainScreenVC
override func viewWillAppear(_ animated: Bool) {
myConstant.height = self.view.frame.size.height
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset < scrollView.contentOffset.y) {
NotificationCenter.default.post(name: Notifications.decreaseHeightNotification.name, object: nil)
self.topViewConstraint.constant = -self.view.frame.height / 6
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
} else if (self.lastContentOffset > scrollView.contentOffset.y) {
NotificationCenter.default.post(name: Notifications.increaseHeightNotification.name, object: nil)
self.topViewConstraint.constant = 0
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
}
self.lastContentOffset = scrollView.contentOffset.y
}
Cell.Swift
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(self, selector: #selector(decreaseHeight), name: Notification.Name("decreaseHeightNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(increaseHeight), name: Notification.Name("increaseHeightNotification"), object: nil)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint.constant = (myConstant.height / 3) - 10
widthConstraint.constant = heightConstraint.constant * 1.5
}
#objc func decreaseHeight() {
heightConstraint.constant = (myConstant.height / 6) - 10
widthConstraint.constant = (heightConstraint.constant * 1.5)
self.layoutIfNeeded()
}
#objc func increaseHeight() {
heightConstraint.constant = (myConstant.height / 3) - 10
widthConstraint.constant = (heightConstraint.constant * 1.5)
self.layoutIfNeeded()
}
Now when I scroll both simultaneously, the screen freezes. Also is there a better way of resizing the collectionViewCell size?
I haven't tested it but you can do this. Use Autolayout in this view. it will work better with that.
Set the tableview constraint as Top, Left, Bottom, Right => 0, 0, 0, 0 with the main view and put collectionView under the tableview with constraint as Top,Left,Right, height => 0, 0, 0, x with the main view.
Note: Tableview is on top of the collectionView.
Connect your height constraint outlet of CollectionView and also define your defaultOffset variable
#IBOutlet weak var defaultHeightConstraint: NSLayoutConstraint
var defaultOffSet: CGPoint?
In viewDidLoad,
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset = UIEdgeInsetsMake(collectionView.size.height, 0, 0, 0)
}
In viewDidAppear, write
override func viewDidAppear(_ animated: Bool) {
defaultOffSet = tableView.contentOffset
}
In ViewDidScroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = tableView.contentOffset
if let startOffset = self.defaultOffSet {
if offset.y < startOffset.y {
// Scrolling down
// check if your collection view height is less than normal height, do your logic.
let deltaY = fabs((startOffset.y - offset.y))
defaultHeightConstraint.constant = defaultHeightConstraint.constant - deltaY
} else {
// Scrolling up
let deltaY = fabs((startOffset.y - offset.y))
defaultHeightConstraint.constant = defaultHeightConstraint.constant + deltaY
}
self.view.layoutIfNeeded()
}
}
Hope it helps.
Since UITableView is a subclass of UIScrollView, your table view's delegate can receive UIScrollViewDelegate methods.
You don't need to use PanGesture
First implement UITableviewDelegate and in that
var oldContentOffset = CGPoint.zero
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y - oldContentOffset.y
if (self.oldContentOffset < scrollView.contentOffset.y) {
// moved to top
} else if (self.oldContentOffset > scrollView.contentOffset.y) {
// moved to bottom
} else {
// didn't move
}
oldContentOffset = scrollView.contentOffset
}
Here you can write you logic to change constant value of your constraint
UPDATE/EDIT
Another thing is don't use multiplier for tabelview height.with collection view it is fine you can give height constraint with self.view in ratio of 1/3 . for tableview just give leading ,trailing top (UICollectionView) and bottom.
So when you change height constant of UICollectionView Tableview height automatically decrease and vice-versa for decrease.
You can not change the multiplier from the outlet as it is read only. so you have to constant value to self.view.frame.height / 3 will work for you
Hope it is helpful to you
For a much cleaner approach, why don't you use a UIScrollView as the container view and add the UICollectionView and UITableView inside it and give the collectionView a height constraint while disabling tableView's scroll till the height constraint of your collectionView becomes 0.
Here's the snippet:
Extend your VC with UIScrollViewDelegate and use:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y <= yourLimit {
heightConstraint.constant = collectionViewHeight - scrollView.contentOffset.y
tableView.isScrollEnabled = false
} else if scrollView.contentOffset.y > yourLimit {
heightConstraint.constant = collectionViewHeight
tableView.isScrollEnabled = true
}
}
Try this snippet after customizing small things in it. It should work mostly, if it doesn't there is another approach which I'd share once this doesn't work.
Why don't you just add Custom Table Cell with your collection view inside, and when indexPath.row == 0 , return your custom table cell.

UIScrollView in UITableViewCell doesn't zoom at touch origin

I have a UIScrollView inside a UITableViewCell to allow zooming on a UIWebView inside the cell. When the view is zoomed in, the UITableViewCell is enlarged to fit the new size of the web view after being zoomed in. This is being done by changing the height constraint on the UIScrollView and implementing UITableView's automatic row height.
The issue right now is that zooming inside the scroll view does not zoom at the origin of the pinch, but somewhere above, so the view does not get zoomed in where the user intended.
Here is the view hierarchy:
[UITableView]
[CustomUITableViewCell]
[UIView] (Content View)
[UIView]
[UIScrollView]
[UIWebView]
[UIView]
The cell is both a UIWebViewDelegate and a UIScrollViewDelegate:
Custom UITableViewCell:
func configure() { // Called from tableView:cellForRowAt:
scrollView.delegate = self
webView.delegate = self
webView.scrollView.isScrollEnabled = false
webView.scalesPageToFit = true
webView.scrollView.bounces = false
webView.scrollView.clipsToBounds = false
let htmlTemplate = TableViewCell.messageTemplate
webView.loadHTMLString(htmlTemplate, baseURL: Bundle.main.bundleURL)
}
// UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return webView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateWebViewFrame()
scrollViewHeightConstraint.constant = webView.frame.height
layoutIfNeeded()
delegate?.updateCellHeights()
}
The table's view controller is the delegate for the cell, which implements updateCellHeights:
func updateCellHeights() {
// https://stackoverflow.com/a/30110230/482536
let offset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(offset, animated: false)
}
How do I make the scroll view zoom to the pinch origin?
Is this even the right approach to this problem? I tried using the scrollView inside the UIWebView directly, but couldn't get the UIWebView to resize to the inner scrollView correctly.

Easiest way to disable back scrolling in scrollview

I want to stop backward scrolling on ScrollView after user scrolls to the next page. How can I do that.
I tried the following two codes, but the first one does not have any effect
self.scrollView.contentOffset = CGPointMake(self.view.frame.width,0)
and the second only disables the forward scrolling.
self.scrollView.contentSize = CGSizeMake( 2 * scrollWidth, scrollHeight);
To disable scrolling in one direction you implement the UIScrollViewDelegate method scrollViewDidScroll and put your logic there. For instance this TableViewController can only ever scroll down, because if the user tries to scroll up, we just overwrite the contentOffset, effectively undoing their scroll before they see it.
class ViewController: UITableViewController {
var lastScrollPosition = CGPoint.zero
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y > lastScrollPosition.y else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
lastScrollPosition = scrollView.contentOffset
}
}
If your cell is equal in size to your screen, you can apply the following option, which is very smooth:
var lastScrollPosition = CGPoint.zero
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == lastScrollPosition.x + UIScreen.main.bounds.width {
lastScrollPosition.x += UIScreen.main.bounds.width
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.x > lastScrollPosition.x else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
}

Resources