Swift: How to anchor view to the top when scrolling - ios

I have an UIView in ScrollView superview. I want this UIView to stick to the top and stays there when users scrolls down.
Note that it should start in the middle of a screen and go up respectively when scrolling down.
I've seen many questions and answers but none of them solved my problem
iOS: Add subview with a fix position on screen
Simple way to change the position of UIView?
My code in scrollViewDidScroll method
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > anchor.frame.origin.y {
var fixedFrame:CGRect = self.anchor.frame
fixedFrame.origin.y = (self.navigationController?.navigationBar.frame.height)!
self.anchor.frame = fixedFrame
}
}

you can do this by saving a initial offset value of Y for your anchor view, then compare it in the scrollview delegate event.
self.initialOffSet = self.anchor.frame.origin.y
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var newFrame = self.anchor.frame
newFrame.origin.y = max(self.initialOffSet!, scrollView.contentOffset.y)
self.anchor.frame = newFrame
}

Why dont you put that view out of your scrollview?It will stay at the top only even when you will be scrolling your scrollview?

Related

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

Connect Two ScrollView’s Together with different Content Offset Values

To Scroll 2 different scrollView together, Many questions have been already answered regarding using the scrollViewDidScroll Method and passing the content offset of one scrollview to other.
But My Question here is a bit different, Let’s say I have 2 ScrollView A and B both with horizontal scrolling only.
When the view loads ScrollView A has contentOffSet say (x,y) and B scrollview’s content offset : (m,n).
As per my understanding content Offset is the new (x,y) value while scrolling.
Now I can’t pass the content Offset value of A to B here to scroll them together as they loads at different points due to content requirement.I need the exact x points displaced while scrolling in A, then may be pass it to B.
I have also tried getting the velocity from pangesture of A and passing it to B, which doesn’t work smoothly.
How can I achieve a smooth scrolling for both views ?
It's not entirely clear what you're trying to do. But if you want to be updated about when scroll view A changes it's contentOffset you can subclass UIScrollView and pass the data through a delegate or a closure.
class ScrollView: UIScrollView {
var contentOffsetChanged: ((CGPoint)->())?
override var contentOffset: CGPoint {
didSet {
if let contentOffsetChanged = contentOffsetChanged {
contentOffsetChanged(contentOffset)
}
}
}
}
Update
After reading the comment you left me and the one you wrote to iOS Geek, it seems contradictory. There are a few possibilities for how your math would work out so don't take my exact solution as the answer, but more of the design. I think this is the design you're interested in.
class Controller: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
var scrollViewB: UIScrollView?
var initialOffset: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
func loadScrollViewB() {
initialOffset = scrollView.contentOffset
scrollViewB = UIScrollView()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.scrollView {
var contentOffset: CGPoint = .zero
contentOffset.x = initialOffset.x - scrollView.contentOffset.x
scrollViewB?.contentOffset = contentOffset
}
}
}

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
}

Move SubView of ChildViewController using scrollViewDidScroll

I am using a viewController to handle two ChildViewControllers, each containing a UITableView. Would it be possible to set the the position y of a SubView of viewController (e.g. a UILabel) depending on the scrollView.contentOffset of the current ChildViewController?
It works fine with its own subviews already,..
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.testConstt.constant = scrollView.contentOffset.y
}
Thanks for helping!
Just observe the correct scroll view using a conditional statement. I assume the scroll views of the children are table views, so you may do something like this:
let tableViewA = UITableView(...)
let tableViewB = UITableView(...)
let someScrollView = UIScrollView()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == tableViewA {
// observe a specific table view's scroll view and do something
} else if scrollView == someScrollView {
// observe a specific scroll view and do something
}
}
Remember, UITableView is a subclass of UIScrollView so they can be treated the same in scrollViewDidScroll(_ scrollView:).

Why does View A with bottom constraints not move when View B changes

I have a Button inside a UIView. Set up with bottom constraints. Once I change the height of the UIView, I expect the Button to move since it has (equal) Constraints to the Bottom.
Bottom Constraints set:
Button nested inside UIView.
Once I move the UITableView, the following code get's called:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = -scrollView.contentOffset.y
if offset > 100 {
bgView.frame.size.height = offset
bgView.layoutIfNeeded() // not a solution
getBtn.layoutIfNeeded() // not a solution
}
view.bringSubview(toFront: tableView)
if scrollView.contentOffset.y == -100.0 {
view.bringSubview(toFront: bgView)
}
}
The Button keeps its position.
What am I missing? Help is very appreciated.
As requested:
Create an IBOutlet for bgView Height Constraint and then change its constant
#IBOutlet weak var bgViewHeightConstraint: NSLayoutConstraint!
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = -scrollView.contentOffset.y
if offset > 100 {
bgViewHeightConstraint.constant = offset
view.layoutIfNeeded()
}
...
}

Resources