Reputation: 1550
I have an UICollectionView
which uses - (void)scrollViewDidScroll:(UIScrollView *)scrollView
to determine when the user has scrolled to the bottom.
The owner of my UICollectionView
has a method which sends a command to a server and asks for more data.
What I want to do is to perform a selector on the owner (my viewcontroller) from my UICollectionView
, and specifically from within scrollViewDidScroll
.
I try to do it using the following code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[[self superView] performSelector:@selector(onScrolledToBottom) withObject:nil];
}
}
Note: the selector onScrolledToBottom
is a SEL
property of my UICollectionView
.
The error I am getting says:
-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00'
EDIT
I've stripped my code down to fit this question.
I have the followin in my ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(@"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self.view: flowLayout: @selector(getMoreInfo)];
}
And for my myCollectionView
the .h
and .m
files look as following:
#import <UIKit/UIKit.h>
@interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIView *parentView;
SEL onScrolledToBottom;
}
@property UIView *parentView;
@property SEL onScrolledToBottom;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
@end
and
#import "myCollectionView.h"
@implementation myCollectionView
@synthesize parentView = parentView;
@synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
parentView = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentView performSelector:@selector(onScrolledToBottom) withObject:nil];
}
}
Upvotes: 0
Views: 327
Reputation: 778
You're getting the superview instead of the parent controller, that's one. Secondly, you shouldn't try to do it this way. Instead use a protocol and pass your controller as a delegate to your custom collectionview.
@protocol MyCollectionViewScrollProtocol <NSObject>
- (void)scrolledToBottom;
@end
Then in your ViewController implement this protocol and in your CollectionView
create a weak property delegate like this:
@property(weak, nonatomic) id<MyCollectionViewScrollProtocol> delegate;
Then you'll be able to call it
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[self.delegate scrolledToBottom];
}
}
and don't forget to set the delegate property from the controller.
Edit by OP:
The controller should contain the following code in the .h
file:
#import <UIKit/UIKit.h>
@protocol ViewControllerProtocol <NSObject>
- (void) Pong;
@end
@interface ViewController : UIViewController <ViewControllerProtocol>
@end
and the following code somewhere in the .m
file:
#import "ViewController.h"
@import UIKit;
#import "myUnit.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
myUnit mU = [[myUnit alloc] init];
mU.linkToController = self;
[mU Ping];
}
......
- (void) Pong{
NSLog(@"Pong");
}
......
The unit that wants to access the controller needs to have the following code in the .h
file:
#import <Foundation/Foundation.h>
#import "ViewController.h"
@interface myUnit : NSObject {
}
@property (weak, nonatomic) id <ViewControllerProtocol> linkToController;
- (void)Ping;
@end
And the unit that wants to access the controller needs to have the following code in the .m
file:
#import "myUnit.h"
#import "ViewController.h"
@implementation myUnit
- (void) Ping {
NSLog(@"Ping");
[self.linkToController Pong];
}
@end
Upvotes: 2
Reputation: 1550
First I want to say thank you to all of you.
I read through everything you guys wrote and I was inspired.
Especially Stanislav Ageev's answer where he said
You're getting the superview instead of the parent controller...
.
I tried to pass the controller itself as a UIViewController
instead of view of the controller.
And I also removed the @selector()
from within the scrollViewDidScroll
and passed the selector directly.
EDIT I missed zy.lius answer. Credit goes to him/her for posting an answer which basically explains what I've done here even though I figured this out on my own. Sorry.
My code now looks like this (.h/.m):
Controller
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(@"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self: flowLayout: @selector(getMoreInfo)];
}
myCollectionView.h file:
#import <UIKit/UIKit.h>
@interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UICollectionView *parentController;
SEL onScrolledToBottom;
}
@property UICollectionView *parentController;
@property SEL onScrolledToBottom;
- (id)init: (UICollectionView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
@end
and the .m file:
#import "myCollectionView.h"
@implementation myCollectionView
@synthesize parentController = parentController;
@synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.view.frame.size.width, parent.view.frame.size.height) collectionViewLayout: layout];
if (self) {
parentController = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentController performSelector:@onScrolledToBottom withObject:nil];
}
}
This works perfectly fine and this could be used to communicate with the main controller in various ways.
And it is very simple.
Thanks again for inspiring me to find the most efficient solution.
Upvotes: 0
Reputation: 732
Your "myCollectionView" should have a delegate (you will have to create a protocol for that) with some method along the line:
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView
By convention view will pass itself to this method (just like any tableView delegate methods). Then in the initCollectionView
after myCollectionView *mVC = [myCollectionView alloc] init...
you will have to set this delegate to the current view controller and implement that method.
mVC.delegate = self;
...
some other place in code
...
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView {
//do what's need to be done ;)
}
Now your "myCollectionView" does not care where it's just and by "who". Anyone who would like to use it can implement this method and be a delegate for that view with any custom logic. That is in this method you would call any required methods and any other things.
How this works:
:)
Upvotes: 1
Reputation: 346
The collectionView.parentView
will be ViewController.view
, which is totally a UIView
, doesn't have a method called onScrolledToBottom
.
Here is a approach if I right about what you want, but IT'S REALLY REALLY A BAD WAY to do this.
in ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(@"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] initWithParentViewController:self layout:flowLayout selector:@selector(getMoreInfo)];
}
in myCollectionView.h
#import <UIKit/UIKit.h>
@interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIViewController *_parentViewController;
SEL _onScrolledToBottom;
}
@property(weak) UIViewController *parentViewController;
@property SEL onScrolledToBottom;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL;
@end
in myCollectionView.m
#import "myCollectionView.h"
@implementation myCollectionView
@synthesize parentViewController = _parentViewController;
@synthesize onScrolledToBottom = _onScrollToBotton;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
_parentViewController = parentViewController;
self.delegate = self;
self.dataSource = self;
_onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[_parentViewController performSelector:@selector(onScrolledToBottom) withObject:nil];
}
}
WARNING:
MVC
there should be a Controller
to implement
the protocol, not a view.MyCollectionView
instead of myCollectionView
as a class name.protocol-delegate
is the best way.Upvotes: 1