How to call super class delegate method from subclass delegate method in Cocoa? - objective-c

There is example class:
#interface OutlineViewController : NSOutlineView <NSOutlineViewDataSource, NSOutlineViewDelegate>
#end
#implementation OutlineViewController
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *result = nil;
if (myCondition)
{
// ...
return result;
} else {
// how return defult?
}
}
#end
Is there possibility call default realization from delegate method?

Just use the super keyword to refer to the parent class as you've done in your comment:
if (myCondition) {
//...your implementation here
}
else {
return [super outlineView:outlineview heightOfRowByItem:item];
}
For extra points, you might use -respondsToSelector: to check that super responds to the method in question.
Update: I just noticed that the superclass in this case is NSOutlineView itself. This is quite confusing -- views and view controllers are different things, so calling something descended from a view a "view controller" is not a good plan. Also, note that the docs advise that "Subclassing NSOutlineView is not recommended."
Nevertheless, I think I understand your question better now -- I think that by "default realization" you mean not the inherited version of the delegate method, but the behavior that the outline view would use if the delegate method weren't implemented at all. In this case, the answer is pretty simple: you can simply do what NSOutlineView itself would do. The documentation for -outlineView:heightOfRowByItem: says:
Implement this method to support an outline view with varying row heights.
For fixed row heights, on the other hand, NSOutlineView almost certainly uses the rowHeight property that it inherits from NSTableView. So, you can simply return rowHeight in those cases when you don't want to change the row height:
if (myCondition) {
//...your implementation here
}
else {
return outlineView.rowHeight;
}

Related

Objective-C: Am I Properly Using this Delegate?

I have a mainWindowController that contains a tabView (which I'm using to switch between views on the main window).
I have view controllers (each with a nib file) for each view. One of the views, view A, contains a tableView. I need to use a delegate method to accomplish something.
After an hour or two or web research and reading up on delegates (new concept to me), I finally got my program to achieve the result I wanted it to for view A.
Here's the interface declaration for view A:
#interface ViewAController : NSViewController <NSTableViewDelegate>
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
As you can see, I'm using NSTableViewDelegate and I need to disable editing of table columns. The implementation looks like this for the method:
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSLog(#"shouldEditTableColumn called");
return NO;
}
I used NSLog to make sure the function is being called.
Now in the SAME view controller (view A), I disable editing by clicking a button:
- (IBAction)turnOffEditing:(id)sender
{
[self.tableView setDelegate:self];
[self tableView:self.tableView shouldEditTableColumn:self.columnTableName row:0];
[self tableView:self.tableView shouldEditTableColumn:self.columnTableName row:1];
NSLog(#"turnOffEditing");
}
As you can see, I get the tableView from the view controller and assign the delegate to self.
I then call the shouldEditTableColumn method on self.
Now, everything works. However, is this the correct way to use a delegate? If I need to use more delegate methods for NSTableView for view A (the only view which will have a table), I'm assuming I can define them in View A's controllers as I did previously?
Usually, the delegate is the delegate from the start. That is, it's a bit strange to set the delegate in the -turnOffEditing: action method. Of course, for my suggestion to work, you'd want to return some dynamic value, like the value of a boolean flag instance variable, from the delegate method.
Also, you shouldn't be calling the delegate method yourself in the action method. That does nothing. The delegate is a thing which the frameworks call when they need to make a decision about how to behave.
So, I'd change your code to something like:
#property BOOL editingDisabled;
- (BOOL) tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSLog(#"shouldEditTableColumn called");
return self.editingDisabled;
}
- (IBAction)turnOffEditing:(id)sender
{
self.editingDisabled = TRUE;
NSLog(#"turnOffEditing");
}
You'd want to set the delegate during setup. A good approach is to simply connect the table view's delegate outlet to your controller in the NIB.

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

set placeholder for uitextview methods are not being called

I saw this answer of how to create a placeholder for UITextView.
I took the following steps:
Add to the .h class the declaration:
#interface AdjustPhotoViewController : UIViewController<UITextViewDelegate>
Added the method:
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
NSLog(#"%d",[textView tag]);
if ([textView tag]==1){
campaignTitle.text = #"";
}else{
campaignDescription.text = #"";
}
return YES;
}
But I don't see that the method is being invoked!
What am I missing?
textView is already delegated via the storyboard to the view
SOLVED:
The problem was that it wasn't delegated. Although I was using storyboard - it was only an outlet, not a delegate.
Remember that if you are using storyboard, you need to delegate also from the text view to the orange button of the view! not only the other way
What am I missing?
Actually setting the delegate.
textView.delegate = self;
Merely conforming to a protocol won't magically make your object into the delegate of an arbitrary object; that's just a formal thing, and anyways, how on Earth would the UITextField know which particular instance of the class it has to assign its delegate?

NSTextField not calling delegate when inside an NSTableCellView

I have a fairly vanilla Source List (dragged out from the Object Library) in my app, with an NSTreeController as its data source. I set the NSTextField inside the DataCell to be editable, but I want to be able to turn that off for some cells. The way I figured you would do this, is with a delegate for the NSTextField, but none of the delegate methods I've tried get called. Is there something I'm missing? I have the delegate set with an outlet in my XIB, and it happens to be the delegate to the owner NSOutlineView, as well, implementing both the NSOutlineViewDelegate and NSTextFieldDelegate protocols.
Also, I can't use the old –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate method either, since that only works with cell-based Outline Views (I'm assuming this is the case - the Outline View documentation doesn't appear to have been updated for Lion, though the analogous NSTableView documentation has, and those methods don't get called either).
Update
I reproduced this in a brand new test project, so it's definitely not related to any of my custom classes. Follow the steps below to create my sample project, and reproduce this problem.
In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
In MainMenu.xib, drag a Source List onto the Window
Drag two Objects onto the dock (left side of the window), specifying the SourceListDataSource class for one, and the SourceListDelegate for the other
Connect the Outline View's dataSource and delegate outlets to those two objects
Select the Static Text NSTextField for the DataCell view inside the outline view's column
Turn on its Value binding, keeping the default settings
Connect its delegate outlet to the Source List Delegate object
Set its Behavior property to Editable
Build and Run, then click twice on either cell in the outline view.
Expected: The field is not editable, and there is a "well, should I?" message in the log
Actual: The field is editable, and no messages are logged
Is this a bug in the framework, or am I supposed to achieve this a different way?
SourceListDataSource.m
#import <Cocoa/Cocoa.h>
#interface SourceListDataSource : NSObject <NSOutlineViewDataSource>
#property (retain) NSArray *items;
#end
#implementation SourceListDataSource
#synthesize items;
- (id)init
{
self = [super init];
if (self) {
items = [[NSArray arrayWithObjects:#"Alo", #"Homora", nil] retain];
}
return self;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (!item) {
return [self.items objectAtIndex:index];
}
return nil;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
return !item ? self.items.count : 0;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return NO;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return item;
}
#end
SourceListDelegate.m
#import <Foundation/Foundation.h>
#interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> #end
#implementation SourceListDelegate
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
return [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
}
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
NSLog(#"well, should I?");
return NO;
}
#end
Subclass NSTableCellView, with an outlet for the text field, and set the text field delegate in awakeFromNib. After doing that, control:textShouldBeginEditing: gets called. I'm not sure why, but (edit:) if you set the delegate in the xib, the delegate methods aren't called – I had the same experience as you.
Alternatively, you can forego the delegate and conditionally set Editable using a binding, either to a boolean property of the model, or using a value transformer which acts on a model instance and returns a boolean. Use the Editable binding of the text field.
I've encountered this problem, too. Because I didn't want to lose the bindings, I did the following:
Binding editable of the TextField to the objectValue and set up a custom NSValueTransformer subclass.
The other proposed solutions above are not performant and will not work on modern versions of macOS. NSTableView calls acceptsFirstResponder on EVERY textField in the entire table when one is about to be edited. And first responder methods get called while you just scroll around the table. If you put some logging in those calls, you'll see them in action.
Additionally, assigning the textField's delegate anywhere other than IB is not needed and won't actually work because NSTableView (and therefore NSOutlineView) basically "take over" for the views they contain.
The Correct, Modern Approach:
Subclass NSTableView (or NSOutlineView) and do this:
final class MyTableView: NSTableView
{
override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool
{
// NSTableView calls -validateProposedResponder on cellViews' textFields A METRIC TON, even while just scrolling around, therefore
// do not interfere unless we're evaluating a CLICK on a textField.
if let textField: NSTextField = responder as? NSTextField,
(event?.type == .leftMouseDown || event?.type == .rightMouseDown)
{
// Don't just automatically clobber what the TableView returns; it'll return false here when delays are needed for double-actions, etc.
let result: Bool = super.validateProposedFirstResponder(responder, for: event)
// IF the tableView thinks this textField should edit, now we can ask the textField's delegate to confirm that.
if result == true
{
print("Validate first responder called: \(responder).")
return textField.delegate?.control?(textField, textShouldBeginEditing: textField.window?.fieldEditor(true, for: nil) ?? NSText()) ?? result
}
return result
}
else
{
return super.validateProposedFirstResponder(responder, for: event)
}
}
}
Notes:
This was written against macOS 11.3.1 and Xcode 12.5 for an application targeting macOS 11.
The isEditable property of the NSTextFields in your NSTableCellViews must be set to true. NSTableView's implementation of -validateFirstResponder will check that property first, so you need not do so in your delegate method.

Call super in overridden class method [duplicate]

This question already has answers here:
How to call original implementation when overwriting a method with a category?
(3 answers)
Closed 8 years ago.
I want to add a new custom UIButtonType to the UIButton class via a category like so:
enum {
UIButtonTypeMatteWhiteBordered = 0x100
};
#interface UIButton (Custom)
+ (id)buttonWithType:(UIButtonType)buttonType;
#end
Is it possible to get the super implementation of that overridden method somehow?
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType];
}
The code above is not valid since super refers to UIControl in this context.
You can replace the method at runtime with your own custom method like so:
#import <objc/runtime.h>
#implementation UIButton(Custom)
// At runtime this method will be called as buttonWithType:
+ (id)customButtonWithType:(UIButtonType)buttonType
{
// ---Add in custom code here---
// This line at runtime does not go into an infinite loop
// because it will call the real method instead of ours.
return [self customButtonWithType:buttonType];
}
// Swaps our custom implementation with the default one
// +load is called when a class is loaded into the system
+ (void) load
{
SEL origSel = #selector(buttonWithType:);
SEL newSel = #selector(customButtonWithType:);
Class buttonClass = [UIButton class];
Method origMethod = class_getInstanceMethod(buttonClass, origSel);
Method newMethod = class_getInstanceMethod(buttonClass, newSel);
method_exchangeImplementations(origMethod, newMethod);
}
Be careful how you use this, remember that it replaces the default implementation for every single UIButton your app uses. Also, it does override +load, so it may not work for classes that already have a +load method and rely on it.
In your case, you may well be better off just subclassing UIButton.
Edit: As Tyler notes below, because you have to use a class level method to make a button this may be the only way to override creation.
Jacob has a good point that category methods act differently than subclass methods. Apple strongly suggests that you only provide category methods that are entirely new, because there are multiple things that can go wrong otherwise - one of those being that defining a category method basically erases all other existing implementations of the same-named method.
Unfortunately for what you're trying to do, UIButton seems to be specifically designed to avoid subclassing. The only sanctioned way to get an instance of a UIButton is through the constructor [UIButton buttonWithType:]. The problem with a subclass like Jacob suggests (like this):
#implementation MyCustomButton
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType]; //super here refers to UIButton
}
#end
is that the type returned by [MyCustomButton buttonWithType:] will still be a UIButton, not MyCustomButton. Because Apple hasn't provided any UIButton init methods, there's not really a way for a subclass to instantiate itself and be properly initialized as a UIButton.
If you want some customized behavior, you can create a custom UIView subclass that always contains a button as a subview, so that you can take advantage of some of UIButton's functionality.
Something like this:
#interface MyButton : UIView {}
- (void)buttonTapped;
#end
#implementation MyButton
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.bounds;
[button addTarget:self action:#selector(buttonTapped)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void)buttonTapped {
// Respond to a button tap.
}
#end
If you want the button to do different things depending on more complex user interactions, you can make more calls to [UIButton addTarget:action:forControlEvents:] for different control events.
Reference: Apple's UIButton class reference
No, this is not possible when you use a category to augment a class' functionality, you are not extending the class, you are actually wholly overriding the existing method, you lose the original method completely. Gone like the wind.
If you create a subclass of UIButton, then this is totally possible:
enum {
UIButtonTypeMatteWhiteBordered = 0x100
};
#interface MyCustomButton : UIButton {}
#end
#implementation MyCustomButton
+ (id)buttonWithType:(UIButtonType)buttonType {
return [super buttonWithType:buttonType]; //super here refers to UIButton
}
#end