Scrolling collection view with UIViews on top? - ios

I have a UICollectionViewController and I added two UIViews as subviews. One is the purple UIView and above it is another white UIView, with the blue collection view getting scrolled up behind both.
Below those UIViews, I made the collectionView.contentInset from the top 300 (that's the total size of two UIViews' height). What I'm trying to accomplish is to scroll the collection view along with the two UIViews above. It's almost similar to the solution on this thread (Move a view when scrolling in UITableView), except when I override scrollViewDidScroll, the whole frame gets scrolled up and cells go behind the two Views. All I want is to scroll up the UIViews, and then scroll through the collection views. I feel like this might involve nested scroll views.
This was how I overrode scrollViewDidScroll:
var rect = self.view.frame
rect.origin.y = -scrollView.contentOffset.y - 300
self.view.frame = rect
EDIT: I posted a video that demonstrates what I want to do per iOS Tumblr app: https://youtu.be/elfxtzoiHQo

I have achieved the same requirement through some basic steps as below.
//Declare the view which is going to be added as header view
let requiredView = UIView()
//Add your required view as subview of uicollectionview backgroundView view like as
collectionView.backgroundView = UIView()
collectionView.backgroundView?.addSubview(requiredView)
//After that control the frame of requiredHeaderView in scrollViewDidScroll delegate method like
func scrollViewDidScroll(scrollView: UIScrollView) {
let per:CGFloat = 60 //percentage of required view to move on while moving collection view
let deductValue = CGFloat(per / 100 * requiredView.frame.size.height)
let offset = (-(per/100)) * (scrollView.contentOffset.y)
let value = offset - deductValue
let rect = requiredView.frame
self.requiredView.frame = CGRectMake(rect.origin.x, value, rect.size.width, rect.size.height)
}

It sounds like what you want is a header.
you can specify a class or nib for the header with either of these:
self.collectionView.registerClass(_:, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier:)
registerNib(_:, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: )
you should also specify a reference height if you are using a flow layout: self.flowLayout.headerReferenceHeight = ...
then you can provide the header via your UICollectionViewController in: collectionView(_:, viewForSupplementaryElementOfKind:, at:) by checking for the section header kind.
Here is a decent tutorial on this for reference: https://www.raywenderlich.com/78551/beginning-ios-collection-views-swift-part-2

You have a library CSStickyHeaderFlowLayout
From the ReadMe:
UICollectionView replacement of UITableView. Do even more like Parallax Header, Sticky Section Header. Made for iOS 7.

Try this.
headerViewYConstraint is header view's top y constraint.
Stores the last contact offset.
var lastContentOffset: CGFloat = 0
override func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView != contentScrollView {
if scrollView.dragging || scrollView.decelerating {
let newOffset = scrollView.contentOffset.y
let headerViewHeight = headerView.frame.width
if headerViewHeight > 0 && scrollView.contentSize.height > view.frame.height + headerViewHeight{
var topOffset = newOffset == 0 ? 0.0 : (headerViewYConstraint.constant + lastContentOffset - newOffset)
topOffset = min(0, max(topOffset, -headerViewHeight))
if headerViewYConstraint.constant > topOffset || newOffset < headerViewHeight || lastDirectionalContentOffset - newOffset > cellHeight(){
headerViewYConstraint.constant = topOffset
}
} else {
headerViewYConstraint.constant = 0
}
lastContentOffset = newOffset
}
}
}

Related

How to Make TableView scroll inside ScrollView behave naturally

I need to do this app. The view hierarchy goes like this
UIScrollView
-UIView (Main View)
--UIView (Top View Container)
--UITableview
When scrolling up the Main View, If table view has many cells, the table view should go to the top, and once it reaches the top. The user should be able to scroll the table view cells. I was able to achieve it but it doesn't scroll naturally.
Attached my code https://github.com/iamshimak/FinchHomeScreenRedesign.git
First, never put tableview inside a scrollview, it's a bad practice. You could just use tableview header and embed any type of view do you want before the tableview cells.
here's a snipeste on how I deal with it:
//MARK: ConfigureTableView
private func configureTableView(){
let footerView = UIView()
footerView.frame.size.height = 50
footerView.backgroundColor = .white
self.tableView.tableFooterView = footerView
self.tableView.tableHeaderView = self.headerView
var newFrame = headerView.frame
newFrame.size.width = view.bounds.width
newFrame.size.height = 300
headerView.frame = newFrame
tableView.backgroundView = UIView()
tableView.backgroundView?.addSubview(backgroundTableView)
}
as you can see, I embedded a UIView as a footer and another UIView named headerView as a header
but if you insist of using a tableview inside a scrollview, you can try using a scrollview delegate and detech which scrollview is scrolling
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if scrollView == self.scrollView {
if yOffset >= scrollViewContentHeight - screenHeight {
// logic when using scrollview
}
}
if scrollView == self.tableView {
if yOffset <= 0 {
// logic when using tableview scrollView
}
}
}

Custom header content animate while scrolling in swift

I have created custom header but I don't know how to animate while scrolling.
Please check below image and let me know how to animate while scrolling.
This animated header in Fotmob app.
First of all add header view as UIvVew and add UIScrollView or UITableView below headerView same as screenshot and follow below step.
set a fixed height constraint to the header view (125 for example) and attach it to top, left and right.
make the UIScrollView below to use all the available space so set to zero top, bottom, left and right constraints.
 connect the header view height constraint to the ViewController in order to have something like:
#IBOutlet var headerViewHeightConstraint: NSLayoutConstraint!
set the UIScrollView delegate to the ViewController
declare two properties to limit the maximum and the minimum height of the header view, fox example:
let headerViewMaxHeight: CGFloat = 125
let headerViewMinHeight: CGFloat = 44 + UIApplication.shared.statusBarFrame.height
The entire workaround is based on update the header view height constraint while the UIScrollView is scrolling, so let’s implement the UIScrollViewDelegate and the most important delegate for our case, the scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let headerViewMinHeight: CGFloat = 44 + UIApplication.shared.statusBarFrame.height
let yPos = mainScrollView.contentOffset.y
let newHeaderViewHeight: CGFloat = headerViewHeightConstraint.constant - yPos
if newHeaderViewHeight > headerViewMaxHeight {
// Here, Manage Your Score Format View
headerViewHeightConstraint.constant = max(headerViewMaxHeight, newHeaderViewHeight)
} else if newHeaderViewHeight < headerViewMinHeight {
headerViewHeightConstraint.constant = headerViewMinHeight
} else {
headerViewHeightConstraint.constant = newHeaderViewHeight
scrollView.contentOffset.y = 0 // block scroll view
}
}
I have created the same, Check the below image
Overview
TableView
Sample MVVM pattern
Swift 5.0 above
Xcode 11 above
Find the GIT URL for code
HeaderAnimation

iOS tableview inside pageviewcontroller inside scrollview

I want to put a tableView inside a pageviewcontroller which is inside a scrollview.
So I have in my storyboard :
All constraints are respected like this
I disabled the tableview scroll but my scroll doesn't scroll, my tableView in TestVC1 not expand the PagerPlace in TestVC
How can I make my scrollview scroll and its content size depends on the tableview height + my red view?
EDIT
I tried your solution, then I got a storyboard like this :
storyboard
Then my scrollview doesn't scroll, I don't know why,
In order to make your effect perfect.
Scroll View -> UIPageViewController's view -> UITableView
Scroll View has a subview of UIPageViewController's view,
UIPageViewController has many page, one page ( a controller's view ) has a subview of UITableView
Yeah. You can change the solution.
mainScrollView ( vertical slide ) -> contentScrollView ( horizontal slide ) -> contentStackView ( has many pages) -> UITableView ( one page )
mainScrollView is UIScrollView, slides in vertical,
contentScrollView is UIScrollView, slides in horizontal
contentScrollView.isPagingEnabled = true
that simulates UIPageViewController
contentStackView has many pages, one page is your UITableView
To make it work like this:
How can I make my scrollview scroll and its content size depends on the tableview height + my red view?
for the part above the UITableView
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
// vertical
let offsetY = scrollView.contentOffset.y
if offsetY >= sillValue {
scrollView.contentOffset = CGPoint(x: 0, y: sillValue)
currentChildScrollView?.am_isCanScroll = true
scrollView.am_isCanScroll = false
} else {
let negScroll = (scrollView.am_isCanScroll == false)
if negScroll{
scrollView.contentOffset = CGPoint(x: 0, y: sillValue)
}
}
}
}
for the UITableView part
use KVO to controller the base scroll view's offset Y ,
and UITableView's offset Y is by default.
let keyValueObservation = currentChildScrollView?.observe(\.contentOffset, options: [.new, .old], changeHandler: { [weak self] (scrollView, change) in
guard let self = self, change.newValue != change.oldValue else {
return
}
self.childScrollView(didScroll: scrollView)
})
internal func childScrollView(didScroll scrollView: UIScrollView){
let scrollOffset = scrollView.am_originOffset.val
let offsetY = scrollView.contentOffset.y
if scrollView.am_isCanScroll == false {
scrollView.contentOffset = scrollOffset
}
else if offsetY <= scrollOffset.y {
scrollView.contentOffset = scrollOffset
scrollView.am_isCanScroll = false
mainScrollView.am_isCanScroll = true
}
}
the full code in github

Zoom and Scroll support UICollectionView

I have a requirement to support zoom in UICollectionView.
Requirements:
After zoom in, it has to support to view the UICollectionViewCell’s hidden area ( area out of viewport) by horizontal and vertical scroll.
After Zoom out/in, it has to support the selection of UICollectionViewCell and able to scroll the UICollectionView ( Basically the default UICollectionView behavior on going back to no zoom state. ).
The list of approaches tried:
Added GestureRecognizer
a. Added UIPinchGestureRecognizer to transform the UICollectionView by scale.
b. After Zoom in, it was not possible to move the UICollectionViewcell to view the hidden area.
c. Added UIPanGestureRecognizer to move the center of UICollectionView
d. It was working fine to move the UICollectionView.
e. Now we can’t able to select the UICollectionViewCell and can’t able to scroll UICollectionView.
Added UICollectionView inside UIScrollView
a. Added UIScrollView with delegates.
b. Added UICollectionView as sub view of UIScrollView
c. Zoom out is not happening because UICollectionView (inherited by UIScrollView) consumes the zoom gesture
Added UIColectionView and UIScrollView both as siblings
a. Added UIScrollView and UICollectionView to parent.
b. Bring UIScrollView to front.
c. Zoom is working but not able to pan to see the hidden area.
Please suggest if there any way to fix above approaches or a better strategy to achieve zoom in a collectionView.
I have solved this using a UIScrollView and a UICollectionViewLayout subclass.
1) place a UIScrollView on top of the UICollectionView with the same frame.
self.view.addSubview(scrollView)
scrollView.addSubview(dummyViewForZooming)
scrollView.frame = collectionView.frame
scrollView.bouncesZoom = false
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 3.0
2) Set the contentSize of the UIScrollView and zoomingView to be the same as the UICollectionView
override func viewDidLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.contentSize = layout.collectionViewContentSize
dummyViewForZooming.frame = CGRect(origin: .zero, size: layout.collectionViewContentSize)
scrollView.frame = collectionView.frame
}
3) Remove all gesture recognizers from the UICollectionView and add a delegate for the UIScrollView. Add a tap gesture recognizer to the UIScrollview
collectionView.gestureRecognizers?.forEach {
collectionView.removeGestureRecognizer($0)
}
let tap = UITapGestureRecognizer.init(target: self, action: #selector(scrollViewWasTapped(sender:)))
tap.numberOfTapsRequired = 1
scrollView.addGestureRecognizer(tap)
scrollView.delegate = self
4) When the ScrollView scrolls or zooms, set the contentOffset of the UICollectionView to be the same as the ScrollView contentOffset, set the layoutScale of your UICollectionViewLayout as the zoomscale and invalidate the layout.
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if let layout = self.layout, layout.getScale() != scrollView.zoomScale {
layout.layoutScale = scrollView.zoomScale
self.layout.invalidateLayout()
collectionView.contentOffset = scrollView.contentOffset
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return dummyViewForZooming
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
collectionView.contentOffset = scrollView.contentOffset
}
5) override the prepare method in the UICollectionViewLayout, scan through all your layoutAttributes and set a transform:
attribute.transformedFrame = attribute.originalFrame.scale(layoutScale)
let ts = CGAffineTransform(scaleX: layoutScale, y: layoutScale)
attribute.transform = ts
let xDifference = attribute.frame.origin.x - attribute.transformedFrame.origin.x
let yDifference = attribute.frame.origin.y - attribute.transformedFrame.origin.y
let t1 = CGAffineTransform(translationX: -xDifference, y: -yDifference)
let t = ts.concatenating(t1)
attribute.transform = t
6) ensure you scale the collectionView content size:
override var collectionViewContentSize: CGSize {
return CGSize(width: width * layoutScale, height: height * layoutScale)
}
7) Intercept taps from the tap gesture recognizer and convert the location in view to a point in the collection view, you can then get the indexPath of that cell using indexPathForItem(point:) and select the cell or pass on events to the underlying views of the cell etc..
hope this helps

Collapse a view on scroll up (iOS)

I have a ViewController, which has a tableView, and has two views on top of the table view. I need to collapse a view (outlined in red in the image), when the user scrolls up, and move another view (orange button), up with the table. I'm planning on using the scrollViewDidScroll delegate method to detect the offset of the table, and handle the collapsing with that.
How do I go about implementing this?
//Custom delegate method, which returns offset of scrollViewDidScroll
func didScrollScrollView(offset: CGFloat){
print(offset)
//I have the offset, which is -30 on load. How do I implement collapsing of the view highlighted in red?
}
you need to access the frame of the view or better its heightConstraint.
If you have a reference to the heightConstrain you can do:
var previousOffset: CGFloat = -30
func didScrollScrollView(offset: CGFloat){
let diff = previousOffset - offset
previousOffset = offset
var newHeight = heightConstraint.constant + diff
if newHeight < 0 {
newHeight = 0
} else if newHeight > 60 { // or whatever
newHeight = 60
}
heightConstraint.constant = newHeight
}
If you don't use Autolayout you need to update the frames manualy.
Also set clipsSubviews=true (clippedToBounds) in interface builder or in code

Resources