As i have used this code to get current page index in pageControl. But this function can't distinguish if scrollView is for Which collectionView.
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if collectionView == self.brandCollectionView {
brandPageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
} else {
brandPageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}
}
Solution is working :
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == self.collectionView {
//code for this scrollView1
} else {
//code for this scrollView2
}
}
Related
I have collection view inside a table view.
It looks like a spreadsheet:
Collection view scrolls horizontally and table view vertically.
I want all the collection view to scroll at once.
Can anyone help me with the solution?
protocol CollectionViewScrollDelegate{
func didScrollCollectionView(contentOffset: CGPoint)
}
extension CellTableView : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if delegate != nil{
delegate?.didScrollCollectionView(contentOffset: collectionView.contentOffset)
}
}
}
var requiredContentOffset:CGPoint!
extension MyVC : CollectionViewScrollDelegate{
func didScrollCollectionView(contentOffset: CGPoint) {
requiredContentOffset = contentOffset
let cells = tblView.visibleCells as! Array<CellTableView>
for cell in cells {
cell.collectionView.contentOffset = contentOffset
}
}
}
extension MyVC : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let cells = tblView.visibleCells as! Array<CellTableView>
for cell in cells {
cell.collectionView.contentOffset = requiredContentOffset
}
}
}
I'm trying to make my UITabBarController scroll to the top of the page when pressed twice. I have tried several times to get this to work with no luck. As of now, I only have one class for the UITabBarController on the storyboard that is linked to the code. Am I supposed to link UITabBar as well? Here's my code that I've attempted so far.
import UIKit
class TabViewController: UITabBarController, UITabBarControllerDelegate {
var pressedCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func unwindToMain(segue: UIStoryboardSegue) {}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("Selected item")
}
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("Selected view controller")
}
func tabBarController(_ TabViewController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
For example:
var pressedCount: Int = 0
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
pressedCount += 1
if pressedCount > 1 {
scrollToTop(view: self.view)
} else {
//do something for first press
}
}
Customize the UITabBar and then use the notification time to make the outside world respond!Or record the number of clicks through the UITabBarController agent and then notify the outside world to respond!
I recently implemented into my code a way to scroll to the top of UIViewController by tapping an icon in UITabBar twice. The code is located inside my UITabBarController code. It works great, however the unfortunate side effect I've found is that every time I open up a page on my app, it's now at the top of UIViewController instead of the place where I last left off. I'm sure there's an error somewhere in this code
import UIKit
class TabViewController: UITabBarController, UITabBarControllerDelegate {
var pressedCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func unwindToMain(segue: UIStoryboardSegue) {}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
pressedCount += 1
if pressedCount > 1 {
scrollToTop()
} else {
//do something for first press
}
print("Selected item")
}
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("Selected view controller")
}
func tabBarController(_ TabViewController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
Problem is, that when your ViewController disappear, it still has pressedCount property set to 2
So to viewWillAppear add this line to reset this:
pressedCount = 0
also fix if statement in tabBar didSelect item to reset pressedCount every time user presses tabBar item twice
if pressedCount > 1 {
scrollToTop()
pressedCount = 0
} else {
//do something for first press
}
I have a parentViewController that contains a childViewController in it. The childViewController has a UICollectionView. I'd like to observe the scroll and have it conform to another protocol to speak to the parentViewController. I can get this to work for the scroll if I call the method in the UICollectionViewDelegate, but I want to be able to have it across any generic ViewController that conforms to the delegate that has a scrollView inside of it. Is this possible?
Code Below:
protocol HomeScrollDelegate: UIScrollViewDelegate {
func showButtons()
func hideButtons()
weak var homeScrollDelegate: HomeScrollDelegate? { get set }
}
extension HomeScrollDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if targetContentOffset.memory.y > scrollView.contentOffset.y {
homeScrollDelegate?.hideButtons()
} else {
homeScrollDelegate?.showButtons()
}
}
}
class HomeCollectionViewController: UIViewController {
weak var homeDelegate: HomeScrollDelegate?
etc etc
}
extension HomeCollectionViewController: UICollectionViewDelegate {
weak var homeScrollDelegate: HomeScrollDelegate? {
get {
return self.homeScrollDelegate
}
set {
self.homeScrollDelegate = homeDelegate
}
}
func showButtons() {}
func hideButtons() {}
}
}
ParentViewController
// set childViewController.homeDelegate = self when creating children
extension ParentViewController: HomeScrollDelegate {
weak var homeScrollDelegate: HomeScrollDelegate? {
get {
return self.homeScrollDelegate
}
set {
if let homeController = UIViewController() as? HomeCollectionViewController {
self.homeScrollDelegate = homeController.homeDelegate
}
}
}
func showButtons() {
// show buttons
}
func hideButtons() {
// hide Buttons
}
}
None of this gets called. I feel like I'm doing something terribly incorrect.
Below is code that works great, but I want something slightly more generic. I know I can easily set up a protocol and just have any viewcontroller that needs to, conform to it like so:
protocol HomeScrollDelegate {
func showButtons()
func hideButtons()
}
class HomeCollectionViewController: UIViewController {
weak var homeDelegate: HomeScrollDelegate?
etc etc
}
extension HomeCollectionViewController: UICollectionViewDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if targetContentOffset.memory.y > scrollView.contentOffset.y {
homeScrollDelegate?.hideButtons()
} else {
homeScrollDelegate?.showButtons()
}
}
}
ParentViewController
extension ParentViewController: HomeScrollDelegate {
func showButtons() {
// show buttons
}
func hideButtons() {
// hide Buttons
}
}
My goal in doing this is so that I don't have to override scrollViewWillEndDragging(_:_:_:) in every UIScrollView delegate because I will have other childViewControllers that are not UICollectionViews but will have scrollViews.
I'm subclassing UIScrollView to add some features such as double tap to zoom and an image property for gallery purposes. But in order to do the image part my subclass has to be its own delegate and implement the viewForZoomingInScrollView.
But then when someone uses my scroll view subclass, they might like to get delegate notifications as well to see scrollViewDidScroll or what have you.
In Swift, how do I get both of these?
Here is a Swift version of this pattern:
Although forwardInvocation: is disabled in Swift, we can still use forwardingTargetForSelector:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func respondsToSelector(aSelector: Selector) -> Bool {
return super.respondsToSelector(aSelector) || _userDelegate?.respondsToSelector(aSelector) == true
}
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
if _userDelegate?.respondsToSelector(aSelector) == true {
return _userDelegate
}
else {
return super.forwardingTargetForSelector(aSelector)
}
}
func viewForZoomingInScrollView(scrollView: MyScrollView) -> UIView? {
return scrollView.viewForZooming()
}
// Just a demo. You don't need this.
func scrollViewDidScroll(scrollView: MyScrollView) {
scrollView.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
private var _delegateProxy = _DelegateProxy()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate:UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
self._delegateProxy._userDelegate = newValue;
/* It seems, we don't need this anymore.
super.delegate = nil
super.delegate = _delegateProxy
*/
}
}
func viewForZooming() -> UIView? {
println("self viewForZooming")
return self.subviews.first as? UIView // whatever
}
func didScroll() {
println("self didScroll")
}
}
Here's a simple working Playground version in Swift 3 that acts purely as an observer rather than only as an interceptor like the other answers here.
The distinction is that the original scroll view delegate should have all of its delegate methods called like normal versus them being hijacked by another delegate.
(You can copy/paste this into a playground and run it to test)
import UIKit
final class ScrollViewObserver: NSObject, UIScrollViewDelegate {
// MARK: - Instantiation
init(scrollView: UIScrollView) {
super.init()
self.scrollView = scrollView
self.originalScrollDelegate = scrollView.delegate
scrollView.delegate = self
}
deinit {
self.remove()
}
// MARK: - API
/// Removes ourselves as an observer, resetting the scroll view's original delegate
func remove() {
self.scrollView?.delegate = self.originalScrollDelegate
}
// MARK: - Private Properties
fileprivate weak var scrollView: UIScrollView?
fileprivate weak var originalScrollDelegate: UIScrollViewDelegate?
// MARK: - Forwarding Delegates
/// Note: we forward all delegate calls here since Swift does not support forwardInvocation: or NSProxy
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Run any custom logic or send any notifications here
print("proxy did scroll")
// Then, forward the call to the original delegate
self.originalScrollDelegate?.scrollViewDidScroll?(scrollView)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidZoom?(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDragging?(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
self.originalScrollDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.originalScrollDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDecelerating?(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndDecelerating?(scrollView)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.originalScrollDelegate?.viewForZooming?(in: scrollView)
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
self.originalScrollDelegate?.scrollViewWillBeginZooming?(scrollView, with: view)
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
self.originalScrollDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return self.originalScrollDelegate?.scrollViewShouldScrollToTop?(scrollView) == true
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidScrollToTop?(scrollView)
}
}
final class TestView: UIView, UIScrollViewDelegate {
let scrollView = UIScrollView()
fileprivate(set) var scrollObserver: ScrollViewObserver?
required init() {
super.init(frame: .zero)
self.scrollView.delegate = self
self.scrollObserver = ScrollViewObserver(scrollView: self.scrollView)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("view's original did scroll delegate method called")
}
}
let testView = TestView()
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 100), animated: true)
testView.scrollObserver?.remove()
print("removed the observer")
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 200), animated: true)
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 300), animated: true)
This prints
proxy did scroll
view's original did scroll delegate method called
removed the observer
view's original did scroll delegate method called
view's original did scroll delegate method called
Swift 4+ version of rintaro's excellent answer:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || _userDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if _userDelegate?.responds(to: aSelector) == true {
return _userDelegate
}
return super.forwardingTarget(for: aSelector)
}
//This function is just a demonstration, it can be replaced/removed.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
(scrollView as? MyScrollView)?.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
fileprivate let _delegateProxy = _DelegateProxy()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate: UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
_delegateProxy._userDelegate = newValue
}
}
func didScroll() {
print("didScroll")
}
}
I don't know about any 100% Swift solution for this.
Taking this ObjC answer to the same problem, and trying to port it to Swift it turns out that is not possible since NSInvocation is not available in Swift.
What we can do is to implement the suggested MyScrollViewPrivateDelegate in ObjC(don't forget to import it in the bridging header file) and the scroll view subclass in Swift like the following:
MyScrollView.swift
import UIKit
class MyScrollView: UIScrollView {
private let myDelegate = MyScrollViewPrivateDelegate()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = myDelegate
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = myDelegate
}
override var delegate: UIScrollViewDelegate? {
set {
myDelegate.userDelegate = newValue
super.delegate = nil
super.delegate = myDelegate
}
get {
return myDelegate.userDelegate
}
}
func viewForZooming() -> UIView {
return UIView()// return whatever you want here...
}
}
MyScrollViewPrivateDelegate.h
#import <UIKit/UIKit.h>
#interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate>
#property (weak, nonatomic) id<UIScrollViewDelegate> userDelegate;
#end
MyScrollViewPrivateDelegate.m
#import "MyScrollViewPrivateDelegate.h"
#import "YOUR_MODULE-Swift.h"
#implementation MyScrollViewPrivateDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// you could check if the user delegate responds to viewForZoomingInScrollView and call it instead...
return [(MyScrollView *)scrollView viewForZooming];
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:_userDelegate];
}
#end