Subclassing a class, while needing to use the delegate - objective-c

Say for example you want to subclass UIScrollView to create UITableView, and you don't want to use any private interfaces.
In order to load cells, you must use the the parent's delegate (scrollViewDidScroll in UIScrollViewDelegate). Additionaly, you want to add some of your own methods to the delegate (e.g. tableView:willDisplayCell:forRowAtIndexPath:).
What I do now is:
Create UITableViewDelegate protocol which extends UIScrollViewDelegate protocol.
Create UITableViewDelegateProxy, which I set as super.delegate, in UITableView's init.
This class conforms to UIScrollViewDelegate and has a next property, which may reference an object conforming to UITableViewDelegate.
By default, it tries to respond using it's own implementation. If that isn't available, it tries with the next's implementations, otherwise it doesn't respond.
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([super respondsToSelector:aSelector])
return YES;
else if (self.next)
return [self.next respondsToSelector:aSelector];
else
return NO;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// self did not respond to selector
if (self.next)
[anInvocation invokeWithTarget:self.next];
else
[self doesNotRecognizeSelector:anInvocation.selector];
}
So up to now, this class is totally transparent and future-proof if UIScrollViewDelegate is extended.
Then I add implementations for some of the delegate methods to change the default behavior or add some more behavior (e.g. call next's tableView:willDisplayCell:forRowAtIndexPath: in scrollViewDidScroll).
Override delegate and setDelegate: in UITableView to return and set super.delegate.next instead of super.delegate. I also change the protocol from UIScrollViewDelegate to UITableViewDelegate.
This works OK if UIScrollView is accessing the delegate via it's ivar directly. If it uses the getter, it will not return our proxy, but instead the delegate set by the user of the class. Either way, we can't rely on this behavior. (fyi: In UIScrollView, it goes through the getter sometimes, but not always).
So if we stay with this example, the question is: how could we implement UITableView, exactly as it is today, ourselves?

You can't do it and preserve the contract, which is unfortunate. It's somewhat unusual that UITableView inherits from UIScrollView; on the desktop Cocoa side of things, NSTableView does not inherit from NSScrollView.

Related

Why not enforce strict singleton application delegate object to use in NIBs?

I just ran myself round in circles, all coming down to having instantiated an app delegate object in a secondary NIB that wasn't the NSMainNibFile. Amazing how having two app delegates kicking around means you have separate managedObjectContexts.
Here's a thought-- could I make my application delegate class a singleton? And safely instantiate it in more XIBs? What would that break?
Also, there are some mentions on stackoverflow that [[UIApplication sharedApplication] delegate] is a "singleton" but it doesn't appear that UIApplicationDelegate protocol guarantees that, nor is the superclass UIResponder a singleton, either. So could I shoot myself in the foot in this regard on iOS as well?
[edit] Looks like you could nil out the delegateClassName in UIApplicationMain for iOS and have the main NIB load the delegate object, so you could create the App Delegate object pattern seen on OSX, if using a main NIB.
[edit2] Screenshot of what MainMenu.xib looks like for a new non-document application. The project gets created with this object, app delegate class gets created with a window property. The issue is getting that nice handy object in other NIBs, and that object being the same as [NSApp delegate]
Just do this in your existing App Delegate (There will only be one!)
// In the header file
+ (AppDelegate*) sharedInstance;
// In the body
+ (AppDelegate*) sharedInstance {
return (AppDelegate*) [[UIApplication sharedApplication] delegate];
}
Then anywhere you want to refer to your App Delegate, you can simply use [AppDelegate sharedInstance] followed by the property or instance method you want to call.
You shouldn't be using the app delegate for stuff to do with core data anyway. So making it an enforced singleton is pointless.
Ideally nothing should need to reference back to it at all.
Okay, after the question having been voted up, and then voted down to zero because of who-knows-why, I've continued to investigate my own answer. I think it's useful to make your app delegate classes true singletons so you can't cause headaches with NIBs. I can't see why it would be harmful. And I think if your app has a single user interface, it's not unreasonable to have the app delegate own the core data stack for all NIBs. However, the recommended design pattern would be to then have each window or view controller be passed the ManagedObjectContext pointer, and to access the MOC through the File's Owner placeholder rather than using an App Delegate object.
Yet on the other hand, things are different with the "Shared User Defaults Controller" singleton, which gets a special object in every NIB. We don't have to pass every controller a pointer to it so that every view can access it. It's just omnipresent. The app delegate is different. There's no "Shared App Delegate" object in every NIB. Yes, there are reasons to never talk to the app delegate in NIBs, but that's not an answer to the question.
So, an answer.
Singleton design patterns:
Covered long ago by Apple in this deprecated reference document-- Creating a Singleton Instance.
Turns out what I want my application delegate class to implement is the "strict" implementation, rather than having a factory method which could create other objects of the app delegate class. The one different feature here is having [NSApp delegate] be the master pointer rather than an app delegate class function.
The strict implementation has to override allocWithZone for my application delegate class (as alloc calls allocWithZone).
+ (MYAppDelegate*)allocWithZone:(NSZone *)zone
{
if ([NSApp delegate] == nil) return [super allocWithZone:zone];
return [NSApp delegate];
}
- (MYAppDelegate*)copyWithZone:(NSZone *)zone
{
return self;
}
Init just returning [super init] is fine, so it needs no override.
Seems to work. I'll update this if not.
[update] I have also been investigating NIB loading using NSBundle's loadNibNamed:owner:topLevelObjects: -- but it appears that I'd get an array back with a new app delegate object, even from that method. The method allows getting pointers to the top-level objects in the NIB without having otherwise created outlets for them. Still seems the best method to get an app delegate object in a XIB other than MainMenu is to use something like the code above.
[another update] Why it could be harmful: According to the the section "Top-level Objects in OS X May Need Special Handling" in this document, there's good reason for me to believe that, even with ARC, this answer of mine increases the retain count on [NSApp delegate], but heck if I feel okay doing a bridge and a release on the app delegate in dealloc for the window/view controllers that have a top-level object for the app delegate. Plus that means code outside the app delegate class.

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.

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

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

Overriding keyDown: in an NSTableView category disables arrow key handling

I have an NSTableView and I have some issues with its default behavior.
If I've overridden the keyDown: method in a category as follows:
- (void) keyDown:(NSEvent *)event {
[super keyDown:event];
}
I can't change the row selection with the keyboard arrow keys anymore. Why is that?
in a category of NSTableView.
In a category of NSTableView, super refers to NSTableView's superclass (NSControl), not to NSTableView as it would in a subclass. You're passing the event on to the NSControl version of keyDown:, which knows nothing about table views and can't handle the arrow keys the way you want.
If you override a method in a category, there's no way to call the original method. It's almost never a good idea to do this on framework classes (whose source is unavailable to you). Use a subclass.
Cf. Using Super in an Objective C Category? and Is calling super in a category the same as calling it in a subclass?