scrollViewDidScroll not called in a CollectionView with custom layout - swift

I want to load more data when the user reaches the end of my CollectionView. Unfortunately, scrollViewDidScroll is never called.
For my CollectionView, I use a custom layout and I think the problem could be at this:
if let layout = exploreCollectionView.collectionViewLayout as? CustomLayout {
layout.delegate = self
}
My class:
class MainViewController: UIViewController, UIScrollViewDelegate { .....
I want to check if the function scrollViewDidScroll works:
// Check if a user wants to load more data at the bottom
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrolled")
}
How do I implement the scrollViewDidScroll in my CustomLayout?

scrollViewDidScroll is a UIScrollViewDelegate method. So you need to make self (MyViewController) a delegate of the collectionview:
exploreCollectionView.delegate = self
And then for instance in an extension:
extension MyViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrolled")
}
}

Related

How to make delegate between a cell with a UIView and a view controller?

I have a view controller which holds a tableview. Inside that tableview I have a dynamic cell. Inside of that cell I have a UIView. Inside the UIView I have a label with a tap recognizer which is supposed to perform a segue to a different view controller. I have tried making a delegate between the view controller by putting vc.delegate = self in the viewdidload, but it did not work. I have also tried putting the same line inside cellforrowat, but it still doesn't do anything. How can I make a delegate that communicates from my cell with UIView to this view controller? Also, the thing that will be communicating is my UIView class and not my cell class.
If I understand it correctly. Create a delegate protocol for your cell view, put delegate property to the cell and pass it via the tableView method tableView(_:cellForRowAt:) to the controller.
Protocol implementation:
protocol MyCellViewDelegate: class {
func viewDidTap(_ view: MyCellView)
}
Cell View Implementaiton:
class MyCellView: UIView {
private let tapGesture = UITapGestureRecognizer()
weak var delegate: MyCellViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
tapGesture.addTarget(self, action: #selector (viewDidTap))
}
#objc
func viewDidTap() {
delegate?.viewDidTap(self)
}
}
Cell Implementation:
Then, in your Cell implementation. Pass the delegate reference to the MyCellView, which will be handled in the Controller.
class MyCell: UITableViewCell {
private let myContentView = MyCellView()
weak var delegate: MyCellViewDelegate? {
didSet { myContentView.delegate = delegate }
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
Then you should be able to set delegate in the TableView DataSource delegate methods.
Controller Implementation:
extension MyController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue the cell...
cell.delegate = self
return cell
}
}
And it will force you to add delegates.
extension MyController: MyCellViewDelegate {
func viewDidTap(_ view: MyCellView) {
// Do some stuff
}
}
However, TableView could "steal" your Tap Gesture action. Maybe, if you set tableView.isUserInteractionEnabled = false, it could work.
The easy way is NotificationCenter. Just post the notification and receive at ViewController.
Though, you achieve this as well by using the delegate where you need to pass the information as below. This is very high level where I am assuming you have below view as a separate class.
UIView -> [Cell -> TableView] -> UIViewController
You can use the completion handler as well.

Is it possible to implement functions from one protocol inside extension of another protocol? [duplicate]

This question already has answers here:
Swift override function in extension
(2 answers)
Closed 3 years ago.
I'm creating a custom refresh control and I need to get some UIScrollViewDelegate methods from the UITableViewController,
What I was doing before is creating implementing UIScrollViewDelegate into my CustomRefreshControl and just overriding scrollViewMethods and passing the all the values inside my customRefresh control, but this approach is a bit ugly and not practical since I would have to do it for every class that applies this CustomRefreshControl so I tried to solve it with protocol extensions but it's not working...
class CustomRefreshControl: UIView {
// initializer and all other properties here
// ...
}
extension CustomRefreshControl: UIScrollViewDelegate {
// Here my implementation inside custom refresh control so I can make it behave like I intend,
// But since all scroll methods from UITableViewController are called in the controller itself and I could not figure out a way to send these events directly into my customRefresh
// I implemented these methods here and just passing them from controller to my customRefresh
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// doing some calculations here
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// doing some calculations here
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// doing some calculations here
}
}
How I was doing before on viewController side...
class RefreshingTableViewController: UITableViewController {
lazy var configuration: CustomRefreshControl.Configurations = {
return CustomRefreshControl.Configurations(threshold: 160, scrollView: self, refreshAction: refreshData)
}()
lazy var refresh = CustomRefreshControl(configurations: configuration)
// All other properties and delegate and datasource implementations
//...
}
extension RefreshingTableViewController {
// That was my previous implementation But this solution did please so much
// Because I have to implement this in every single viewController that wants to use this CustomRefreshControl
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
refresh.scrollViewWillBeginDragging(scrollView)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
refresh.scrollViewDidScroll(scrollView)
}
override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
refresh.scrollViewWillEndDragging(scrollView, withVelocity velocity: velocity, targetContentOffset: targetContentOffset)
}
}
So the solution I'm trying to make work right now is a solution with protocols, but I don't know if it's even possible, it is as follows...
protocol CustomRefreshManagerProtocol where Self: UIScrollViewDelegate {
var customRefresh: CustomRefreshControl { get }
}
extension CustomRefreshManagerProtocol {
// Here I'm trying to implement the UIScrollViewDelegate methods, since I specifing that Self: UIScrollViewDelegate I thought it might work
// But none of these functions are being called, so that's what I'm trying to get to work without success.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
customRefresh.scrollViewWillBeginDragging(scrollView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
customRefresh.scrollViewDidScroll(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
customRefresh.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
class RefreshingTableViewController: UITableViewController, CustomRefreshManagerProtocol {
lazy var refresh = CustomRefreshControl(configurations: configuration)
var customRefresh: CustomRefreshControl { refresh }
// ...
}
Anyone has any idea how to make this work or why it's not working?
Extensions cannot override methods at all (it sometimes works, but it's not defined behavior). So the answer is no.

Protocol methods in a class extension are not called under specific conditions

I encountered a weird behavior. The best way I can put it is … Not overridden protocol methods in a class extension are not called while the superclass already conforms to the protocol (via extension). However this happens only while it's build with the release build configuration.
class A: UIViewController {}
extension A: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollViewDidScroll in superclass")
}
}
class B: A {
// A tableView (and its data source and delegate) is set here…
}
extension B: UITableViewDelegate {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
super.scrollViewDidScroll(scrollView)
print("scrollViewDidScroll in subclass")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("scrollViewDidEndDragging")
}
}
The output:
scrollViewDidScroll in superclass
scrollViewDidScroll in subclass
scrollViewDidEndDragging
however if I build it with the release build configuration, the output is
scrollViewDidScroll in superclass
scrollViewDidScroll in subclass
I can solve the problem if I don't use the extension for protocol conformance approach in the class B and just use the regular way instead (put the methods that implement a protocol into the class).
The question is … how come?
I too encountered the same behaviour.
Xcode version - 12.4
Swift version - 4.2
I also encounter that this behaviour is shown only when applicationDidBecomeActive method is called in the application.(Mostly when you have navigation to some other Screen like authentication etc)
Fix: You need to assign the ViewController as delegate again.
func applicationDidBecomeActive(_ application: UIApplication) {
fixesForCallBackInExt()
}
func fixesForCallBackInExt() {
guard let navController = window?.rootViewController as? UINavigationController else { return }
if let tabVC = navController.topViewController as? UITabBarController, let topVC = tabVC.selectedViewController, topVC.isKind(of: UIViewController.self) {
// Set Your Delegate = topVC
} else if let topVC = navController.topViewController, topVC.isKind(of: UIViewController.self) {
// Set Your Delegate = topVC
}
}

#IBDesignable with protocol

I have a UIview xib within a view controller, UIview class have two buttons with protocol function, but the protocol function never called when I press button, storyboard image like below
protocol method like below
import UIKit
#objc protocol TopViewDelegate: NSObjectProtocol {
#objc optional func pressRefreshButton()
#objc optional func pressMenuButton()
}
UIView class
#IBDesignable class OnJob_Top: UIView,TopViewDelegate {
weak var delegate : TopViewDelegate? = nil
#IBAction func refreshButtonTouchUpInside(_ sender: UIButton) {
delegate?.pressRefreshButton!()
}
#IBAction func menuButtonTouchUpInside(_ sender: UIButton) {
delegate?.pressMenuButton!()
print("come come")
}
view controller class
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let topView = OnJob_Top()
topView.delegate = self
}
}
extension HomeViewController:TopViewDelegate {
func pressMenuButton() {
print("come") // never come here
}
func pressRefreshButton() {
print("come") // never come here
}
}
Consider this code:
let topView = OnJob_Top()
topView.delegate = self
In the first line, you create a completely new OnJob_Top view.
In the second line, you make it the delegate.
In the third line... but there is no third line. The view vanishes in a silent puff of smoke. It is useless.
Meanwhile, the view in the storyboard never gets a delegate. So its delegate methods are never called.

How do you access a UIViewController function from within a UICollectionCell?

I have a function within a UICollectionViewCell that requires access to the
hosting UIViewController. Currently 'makeContribution()' can't be accessed:
What is the proper way of accessing the host UIViewController that has the desired function?
Thanks to the insightful responses, here's the solution via delegation:
...
...
...
{makeContribution}
This is a mildly controversial question - the answer depends a little on your philosophy about MVC. Three (of possibly many) options would be:
Move the #IBAction to the view controller. Problem solved, but it might not be possible in your case.
Create a delegate. This would allow the coupling to be loose - you could create a ContributionDelegate protocol with the makeContribution() method, make your view controller conform to it, and then assign the view controller as a weak var contributionDelegate: ContributionDelegate? in your cell class. Then you just call:
contributionDelegate?.makeContribution()
Run up the NSResponder chain. This answer has a Swift extension on UIView that finds the first parent view controller, so you could use that:
extension UIView {
func parentViewController() -> UIViewController? {
var parentResponder: UIResponder? = self
while true {
if parentResponder == nil {
return nil
}
parentResponder = parentResponder!.nextResponder()
if parentResponder is UIViewController {
return (parentResponder as UIViewController)
}
}
}
}
// in your code:
if let parentVC = parentViewController() as? MyViewController {
parentVC.makeContribution()
}
Well, CollectionView or TableView?
Anyway, Set your ViewController as a delegate of the cell. like this:
#objc protocol ContributeCellDelegate {
func contributeCellWantsMakeContribution(cell:ContributeCell)
}
class ContributeCell: UICollectionViewCell {
// ...
weak var delegate:ContributeCellDelegate?
#IBAction func contributeAction(sender:UISegmentedControl) {
let isContribute = (sender.selectedSegmentIndex == 1)
if isContribute {
self.delegate?.contributeCellWantsMakeContribution(self)
}
else {
}
}
}
class ViewController: UIViewController, UICollectionViewDataSource, ContributeCellDelegate {
// ...
func collectionView(_ collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
cell = ...
if cell = cell as? ContributeTableViewCell {
cell.delegate = self
}
return cell
}
// MARK: ContributeCellDelegate methods
func contributeCellWantsMakeContribution(cell:ContributeCell) {
// do your work.
}
}