-[CollectionCell _setReuseIdentifier:]: unrecognized selector sent to instance - objective-c

CollectionCell is a UICollectionViewCell subclass. For learning purposes, the only difference is a single subview.
No interface builder. All in code.
I can do it like this for iOS 5…
[_collectionView registerClass:[CollectionCell class]
forCellWithReuseIdentifier:#"CollectionCell"];
…but it breaks in iOS 6, and I can't find any information.

Your subclassed CollectionCell doesn't have the "registerClass: forCellWithReuseIdentifier" method.
Your "UICollectionView" (which uses and displays CollectionCell objects) does.
Call "registerClass: forCellWithReuseIdentifier" on your collection view instead of the cell.

Make sure that your UICollectionViewCell classes extend PSUICollectionViewCell. I had the same issue happen to me and found out that my cells were extending PSTCollectionViewCell.

Related

Weird inheritance bug iOS

Okay so i decided to move my code for my UITableView delegates into another class. a subclass if you will, A subclass so that it would make it easier to access all the elements my Cellforrowwithindexpath function does within said subclass.
But now there is a slight issue...
It works fine, as far as the UItableView is concerned, But then when i tried to use the navigation controller to push a view on top, it did not work, i then discovered that self...Within my main class was actually an instance of my subclass...What? so self is not actually equal to self...
Can anyone give me any insight as to what i am doing so colossally wrong here?
EDIT: So i changed it to instead be a subclass to a delegate and it works fine, just in case anyone else runs into This issue, But i am still confused as to why it was happening in the first place...
Code:
#interface OpenGameList : MainMenuViewContoller <UITableViewDelegate,UITableViewDataSource>
{
}
#end
In my MainMenuViewController's viewDidLoad function
_openGameList = [[OpenGameList alloc] init];
_openGameList.delegate = self;
friendsTable.delegate = _openGameList;
friendsTable.dataSource = _openGameList;
And than after that it seems that any use of self in MainMenuViewController is equal to OpenGameList hence why using [[fromView navigationController] pushViewController:toView animated:NO]; does not work
self always points to the object that was actually instantiated – the most derived class.
When you have an instance of a subclass, and you send a message to self, the subclass's implementation will always be invoked if there is one. It doesn't matter whether or not you're in the superclass's implementation file or the subclass's implementation file.
This is an essential for polymorphism: it's what allows subclasses to override the behavior of a parent class. Take -[UIView drawRect:] for example. To invoke the drawing code for subclasses, when code in UIView invokes [self drawRect:] it's the subclass's drawing implementation which needs to be called.
It might help to remember that superclasses and subclasses aren't parent and child objects, but less and more specific types which apply to the same object. A UITableView is also a UIScrollView, UIView, and NSObject, but when you make one, there is one object which is all of those things, and self always refers to that one.

Setting .reuseIdentifier on a UICollectionViewCell

I have a particular UICollectionViewCell that I want to instantiate myself, and add to a UICollectionView. In order for this to work, the UICollectionViewCell instance needs its .reuseIdentifier property to be set.
Normally, the class or Nib that describes the cell is registered with the collection view, and the collection view instantiates the cell with its .reuseIdentifier already set, with these methods:
- registerClass:forCellWithReuseIdentifier:
- registerNib:forCellWithReuseIdentifier:
However, since I am constructing this cell outside of the collection view, these do not apply.
When I create the cell myself, there appears to be no way to set its .reuseIdentifier (because it is a readonly property, and there are no init... methods that initialize it).
When .reuseIdentifier is not set, the UICollectionView throws an exception when the cell is added. This behavior is different from UITableView, where reuse identifiers were optional.
An easy workaround to set the collection view cell's reuse identifier is to embed it in a .xib file and use the Identifier box, then create an instance of the cell with
[NSBundle.mainBundle loadNibNamed:#"MyCellName" owner:self options:nil][0];
I can then pass the above-instantiated UICollectionViewCell and everything works fine.
...but that seems like a pretty silly and arbitrary hoop to jump through. Is there some other way to get this property set on the cell instance without the .xib-wrapper detour?
Update: Apple's documentation says this:
To simplify the creation process for your code, the collection view requires that you always dequeue views, rather than create them explicitly in your code.
...which is actually not true, because it doesn't require this (i.e., externally-instanced cells work fine as long as their identifier is set somehow, e.g. if loaded from a .xib), and it also doesn't "simplify the creation process for my code" in my particular use case (rather requires an extra file; further it would be messy to require that the collection view create these few complex one-offs).
But the above does seem to imply that the answer to the question is no: it's intentionally "difficult" to create a usable cell except by having the collection view dequeue it.
I'm sorry to say it, but I think you're going to have to accept the fact that UICollectionView is the sole Apple-branded factory for producing UICollectionViewCells. So if you have two collection views in which you want to display the exact same object, you're out of luck!
The good news is that if you have two collection view in which you want to display a cell that looks exactly the same as in the other, then you can accomplish your task. It's easy:
#interface MyModelClass : NSObject
// a bunch of data
#end
#interface MyCollectionViewCell : UICollectionViewCell
-(void)refreshCellWithObject:(MyModelClass *)object;
#end
Meanwhile, in whichever classes own the UICollectionView cv1 and cv2
[[self cv1] registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MyCollectionViewCell"];
and
[[self cv2] registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MyCollectionViewCell"];
And their data sources can look the same
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyModelObject *obj = [self theObjectIWant];
MyCollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCollectionViewCell" forIndexPath:indexPath];
[cell refreshCellWithObject:obj];
return cell;
}
Use these two methods.
– registerNib:forCellWithReuseIdentifier:
– dequeueReusableCellWithReuseIdentifier:forIndexPath:
See apple's UICollectionView Class Reference

UIViewController throwing unrecognized selector exception on prepareForSegue

I'm trying to follow along the Stanford CS193p iOS programing lectures. One of the demo programs, called "Happiness" creates two UIViewControllers, a "PsychViewController" and a "HappinessViewController." It segues from the PsychViewController to the HappinessViewController using a target action method.
The following code keeps throwing this exception: "-[UIViewController setHappiness:]: unrecognized selector sent to instance"
Here's the offending code:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowDiagnosis"]) {
[segue.destinationViewController setHappiness:7];
}
}
I have searched this site and others, and the usually when this error comes up, it is because the generic UIViewController has not been correctly set to the specific view controller object used in the program, in this case the "HappinessViewController." But I have set the generic UIViewController to be a HappinessViewController using the identity inspector in IB, and I am still getting the exception. I am tearing my hair out, if anyone could help it would be much appreciated.
Let's look at the exception:
-[UIViewController setHappiness:]: unrecognized selector sent to instance
This tells you two things about the message that was unrecognized. It tells you the selector, setHappiness:. It also tells you the actual, runtime class of the receiver, UIViewController.
Of course, UIViewController has no method named setHappiness:. That's why you got the exception.
You wrote a subclass of UIViewController called HappinessViewController which does have a setHappiness: method (or a read/write property named happiness, which generates the method). And you intended for the receiver (the destination view controller) to be an instance of that subclass (HappinessViewController).
But the exception is telling you that the destination view controller is just a plain UIViewController. So even though you think you did, you probably did not set the view controller's custom class in the storyboard. Maybe you set the custom class of some other view controller, but you didn't set the class of this segue's destination.
You need to set the destination view controller's custom class in the Identity Inspector, like this:
I figured out the problem. Although I had correctly specified that the relevant UIViewController was a HappinessViewController, the linker, was for some reason, not linking to the correct files. The fix was to go to double click on the .xcodeproj file inside Xcode, then go to Build Phases and manually add the files under "Compile Sources."
For me changing the class of the view controller in the story board worked.
Try like this:
HappinessViewController *controller = segue.destinationViewController;
[controller setHappiness:7];
I had the same problem today and it was because my custom class was in a library. The library itself was being linked in build phases, but that in itself was not enough to pull in the class. So finally I solved it by add the following line to my AppDelegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[ CustomClass class ];
}
This forces the linker to pull in the class. Otherwise simply linking in the library may not be enough to pull in the class unless it is referenced somewhere in the application.
I tried adding a Cast and it worked for me, I had the same problem:
FirstViewController *detailViewController =
(FirstViewController *)[segue destinationViewController];
Check which prepareForSegue is triggering. My problem was that 2 segues were triggering, in the current viewController and in the incoming viewController. The solution, and always a good practice is to check segues identifiers.
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"segueIdentifier"]) {
}
}
Cast before assigning
HappinessViewController *controller = (HappinessViewController *)segue.destinationViewController;
[controller setHappiness:7];
Make sure in your storyboard, HappinessViewController is set as the class of you VC

UICollectionView: How to get item size for a specific item after setting them in delegate method

I am fiddling with the new UICollectionView and the UICollectionViewLayout classes. I have created a custom layout, subclassing UICollectionViewFlowLayout.
My cell sizes are changing dynamically and I set the item sizes using the delegate method below
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
return CGSizeMake(80, 80);
}
Now, under the prepareLayout method of my custom UICollectionViewFlowLayout class, I need to access these size variables so that I can make calculations how to place them and cache them for layoutAttributesForItemAtIndexPath.
However, I can't seem to find any property under UICollectionView or UICollectionViewFlowLayout to reach the custom item sizes I set in the delegate method.
Found it myself.
Implement the custom class like without omitting UICollectionViewDelegateFlowLayout
#interface SECollectionViewCustomLayout : UICollectionViewFlowLayout
<UICollectionViewDelegateFlowLayout>
and then you can call
CGSize size = [self collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:indexPath];
Looking at the various UICollectionView... header files, and watching the WWDC 2012 Session 219 - Advanced Collection Views and Building Custom Layouts video (from about 6:50 onwards), it seems the extensible delegate pattern takes advantage of dynamic typing to ensure the layout can properly access its extended delegate methods.
In short...
If you define a custom layout with its own delegate, define that delegate protocol in the layout's header file.
Your delegate object (typically the UI(Collection)ViewController that manages the collection view) should declare itself to support this custom protocol.
In the case that your layout is just a UICollectionViewFlowLayout or subclass thereof, this just means declaring conformance to UICollectionViewDelegateFlowLayout.
Feel free to do this in your class extension in the .m file if you'd rather not #import the layout header into the delegate's interface.
To access the delegate methods from the layout, call through to the collection view's delegate.
Use the layout's collectionView property, and cast the delegate to an object conforming to the required protocol to convince the compiler.
Don't forget to check that the delegate respondsToSelector: as usual prior to calling optional delegate methods. In fact, if you like, there's no harm in doing this for all methods, as the typecasting means there is no runtime guarantee the delegate will even implement the required methods.
In code...
So if you implement a custom layout that requires a delegate for some of its information, your header might look something like this:
#protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
#end
#interface CustomLayout : UICollectionViewLayout
// ...
#end
Your delegate declares conformance (I've done so in the implementation file here):
#import "CustomLayout.h"
#interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
#end
#implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath
{
return [self canDoSomethingMindblowing];
}
// ...
#end
And in your layout's implementation, you access the method like this:
BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:#selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) {
blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
layout:self
shouldDoSomethingMindblowingAtIndexPath:indexPath];
} else {
// Perhaps the layout also has a property for this, if the delegate
// doesn't support dynamic layout properties...?
// blowMind = self.blowMind;
}
Note that it's safe to typecast here, as we're checking the delegate responds to that method beforehand anyway.
The evidence...
It's only speculation, but I suspect it is how Apple manages the UICollectionViewDelegateFlowLayout protocol.
There is no delegate property on the flow layout, so calls must go via the collection view's delegate.
UICollectionViewController does not publicly conform to extended flow layout delegate (and I doubt it does so in another private header).
UICollectionView's delegate property only declares conformance to the 'base' UICollectionViewDelegate protocol. Again, I doubt there is a private subclass/category of UICollectionView in use by the flow layout to prevent the need for typecasting. To add further weight to this point, Apple discourages subclassing UICollectionView at all in the docs (Collection View Programming Guide for iOS: Creating Custom Layouts):
Avoid subclassing UICollectionView. The collection view has little or no appearance of its own. Instead, it pulls all of its views from your data source object and all of the layout-related information from the layout object.
So there we go. Not complicated, but worth knowing how to do it paradigm-friendly way.
There is a swift version:
self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)
Check out UICollectionView-FlowLayout on GitHub. Same idea, this just makes accessing the extended delegate methods of flowLayout a little cleaner.
For the later readers, IOS 7 has UICollectionViewFlowLayout which has defined it.
In my case everything about layout, cell layout etc. is being defined inside nib for UIViewController and separate nib for UICollectionViewCell. MyCollectionViewCell contains UIImageView with autolayout to cell with padding/margins but square-shaped.
I need round icons instead squared but don't want to take care which nib I use for iPhone or for iPad (I have separate nibs for devices and for orientation as well).
I don't want to implement #selector(collectionView:layout:sizeForItemAtIndexPath:) into my view controller.
So, inside collectionView:cellForItemAtIndexPath:
I can just use
CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;
Because collectionView:layout:sizeForItemAtIndexPath: call before collectionView:cellForItemAtIndexPath: and layout done.
You can check round avatars on the bottom

What method is called when a UITableViewCell is initialized by Interface Builder?

I have a custom UITableViewCell and I need to initialize some ivars before it is displayed in a UITableView. I overrode - (void)init but that did not seem to be the designated initializer. Where should I be putting my code?
EDIT: the cell was made using UITableViewCell prototypes in Interface Builder.
- (id)initWithCoder:(NSCoder *)decoder;
Also, for what it sounds like you are wanting to do, you may instead want to override:
-(void)awakeFromNib;
– initWithStyle:reuseIdentifier: is UITableVIewCell's designated initializer