Custom delegate methods in a NSTextView subclass - compiler warnings - cocoa

Long story short (please stop me if I'm doing this wrong): I want to have an NSTextView accept a custom drag type, and upon receipt of such a drag change the content to match.
To do this, I subclass NSTextView to implement the custom drag type, and (from the subclass) send a message to the NSTextView delegate when done. This works just fine, but I get a familiar compiler warning (though everything works fine):
Method '-dragReceivedWithTrack:' not found (return type defaults to 'id')
Some code:
#interface LyricTextView : NSTextView {
}
#end
#interface NSObject (CustomDragging)
-(BOOL)dragReceivedWithTrack:(NSDictionary *)track;
#end
#implementation LyricTextView
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSData *data = [[sender draggingPasteboard] dataForType:kMyType];
NSDictionary *track = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if ([[self delegate] respondsToSelector:#selector(dragReceivedWithTrack:)]) {
return ([[self delegate] dragReceivedWithTrack:track]); // gives a warning, but works
}
return NO;
}
Shouldn't the informal protocol take care of the warning?
What am I doing wrong?

You have not declared a protocol, you have declared a category on NSObject. You could probably remove its definition without affecting the compiler warning.
Since you have a subclass, you are stuck with the delegate as it is (which is an id ).
To quelch the compiler warning, you should be able to simply cast it to id, like so:
id delegate = (id)[self delegate]
if ((delegate respondsToSelector:...]) {...}

Related

How can I work around this MapKit bug that causes duplicate callouts?

There's a bug in MapKit that can cause duplicate callout views on an annotation. If the timing is just right, an annotation view can get re-used while it is being selected and apparently just before the callout view is actually added to it. As a result, the old callout view gets stuck there, and the new callout will appear on top of or next to it. Here's what this can look like in an OS X app:
There's only one annotation on this map. If you click elsewhere on the map to deselect the annotation, only one of the callouts disappears. In some cases you might have two callouts with completely different information, which is where things get really confusing for someone using your app.
Here's the majority of a sample OS X project I put together that illustrates this bug:
#import MapKit;
#import "AppDelegate.h"
#import "JUNMapAnnotation.h"
#interface AppDelegate () <MKMapViewDelegate>
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet MKMapView *mapView;
#property BOOL firstPin;
- (void)placeAndSelectPin;
- (JUNMapAnnotation *)placePin;
- (void)clearPins;
#end
#implementation AppDelegate
- (IBAction)dropSomePins:(id)sender {
self.firstPin = YES;
[self placeAndSelectPin];
[self performSelector:#selector(placeAndSelectPin) withObject:nil afterDelay:0.0001];
}
#pragma mark - Private methods
- (void)placeAndSelectPin {
[self clearPins];
JUNMapAnnotation *annotation = [self placePin];
[self.mapView deselectAnnotation:annotation animated:NO];
[self.mapView selectAnnotation:annotation animated:YES];
}
- (JUNMapAnnotation *)placePin {
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(50.0,50.0);
JUNMapAnnotation *annotation = [[JUNMapAnnotation alloc] initWithCoordinate:coord];
annotation.title = #"Annotation";
annotation.subtitle = (self.firstPin) ? #"This is an annotation with a longer subtitle" : #"This is an annotation";
[self.mapView addAnnotation:annotation];
self.firstPin = NO;
return annotation;
}
- (void)clearPins {
[self.mapView removeAnnotations:self.mapView.annotations];
}
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[JUNMapAnnotation class]]) {
static NSString *identifier = #"annotationView";
MKPinAnnotationView *view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (view == nil) {
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
view.canShowCallout = YES;
NSLog(#"new annotation view");
} else {
view.annotation = annotation;
}
return view;
}
return nil;
}
#end
The same bug seems to exist in iOS, though I've had a tougher time recreating it there.
While I'm waiting on Apple to fix this, I'd like to work around it as much as possible. So far I've come up with a few possibilities:
Don't re-use annotation views. From what I can tell this seems like the only way to completely avoid the bug, but it seems pretty inefficient.
When an annotation view is re-used in mapView:viewForAnnotation:, remove all of its subviews. Currently it seems like the callout is the only subview, though it doesn't seem like a particularly safe hack. It also only sort of works—it doesn't prevent duplicate callouts from appearing, it just keeps them from sticking around forever. (When this bug first happens, there actually aren't any subviews yet.)
Combine both of those: if dequeueReusableAnnotationViewWithIdentifier: returns a view that has any subviews, ignore it and create a new one. This seems a lot safer than 2 and isn't nearly as inefficient as 1. But as with 2 it's not a complete workaround.
I've also tried adding deselectAnnotation:animated: in every place I can think of, but I can't find anything that works. I assume that once the annotation view is re-used, the MapView loses track of the first callout, so none of its normal methods will get rid of it.
this is a bit out of left field, but..
try registering the same cell class with 2 different reuse identifiers. in viewForAnnotation:, alternate between using each identifier when dequeueing a cell. this should prevent grabbing from the same queue twice in succession.

Intercept Keydown Actions in an NSTextFieldCell

I have a cell-based NSOutlineView which displays NSTextFieldCell objects.
I'd like to respond to keydown or keyup events so as to make the text contained in the NSTextFieldCell bold when the text contains certain preset keywords. What is the most elegant way to achieve this - should I:
Subclass NSOutlineView and override the keydown method
Subclass NSTextFieldCell
Utilize a delegate of some kind
Utilize some other approach
Thanks very much in advance to all for any info!
Found it.
In awakeFromNib:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(actionToTakeOnKeyPress:) name:NSControlTextDidChangeNotification object:theNSOutlineViewThatContainsTheNSTextFieldCell];
Then add a method like this:
- (void) actionToTakeOnKeyPress: (id) sender
{
//will be called whenever contents of NSTextFieldCell change
}
To intercept key presses in a way that they can still be filtered out, various NSResponder messages may be overwritten, such as keyDown: or interpretKeyEvents:.
To be able to do that, a subclass of a NSTextView needs to be used as the field editor. For that, one subclasses NSTextFieldCell and overrides fieldEditorForView:, returning the subclass (see Custom field editor for NSTextFieldCell in an NSTableView).
Here's the relevant code excerpts:
In a subclassed NSTextFieldCell (which then has to be assigned in Interface Builder for the editable column, or returned by the NSTableViewDelegate's dataCellForTableColumn message):
- (NSTextView *)fieldEditorForView:(NSView *)aControlView
{
if (!self.myFieldEditor) {
self.myFieldEditor = [[MyTextView alloc] init];
self.myFieldEditor.fieldEditor = YES;
}
return self.myFieldEditor;
}
It also requires the declaration of a property in the #interface section:
#property (strong) MyTextView *myFieldEditor;
And then in MyTextView, which is a subclass of NSTextView:
-(void)keyDown:(NSEvent *)theEvent
{
NSLog(#"MyTextView keyDown: %#", theEvent.characters);
static bool b = true;
if (b) { // this silly example only lets every other keypress through.
[super keyDown:theEvent];
}
b = !b;
}

Drag and drop in an NSView

I'm testing drag and drop in an NSView, but draggingEntered: is never called.
Code:
#import <Cocoa/Cocoa.h>
#interface testViewDrag : NSView <NSDraggingDestination>
#end
#implementation testViewDrag
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self registerForDraggedTypes:[NSImage imagePasteboardTypes]];
NSLog(#"initWithFrame");
}
return self;
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
NSLog(#"draggingEntered");
return NSDragOperationEvery;
}
-(NSDragOperation) draggingUpdated:(id<NSDraggingInfo>)sender
{
NSLog(#"draggingUpdated");
return NSDragOperationEvery;
}
#end
In interface builder I add a subView (class is set to testViewDrag) to the main window. In log I can see the initWithFrame log but when I drag nothing is shown in the log.
What am I missing ?
"To receive drag operations, you must register the pasteboard types that your window or view will accept by sending the object a registerForDraggedTypes: message, defined in both NSWindow and NSView, and implement several methods from the NSDraggingDestination protocol. During a dragging session, a candidate destination receives NSDraggingDestination messages only if the destination is registered for a pasteboard type that matches the type of the pasteboard data being dragged. The destination receives these messages as an image enters, moves around inside, and then exits or is released within the destination’s boundaries." You can read more about the drag and drop programming topic here. As I see it, your problem lies in the argument you define in your registerForDraggedTypes: method.
Try replacing it with this:
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSColorPboardType, NSFilenamesPboardType, nil]];
Hope this helps!

How do I release this allocation?

I got this code off another thread on here and it works perfectly, but it leaks, and I don't know how to release it. I have tried adding "autorelease" statements to the GoToNext alloc line. It didnt help. Anyone know how to properly handle this?
webView.delegate = [[GoToNext alloc] initWithTarget:self andNext:#selector(loadUpdateGraph)]; //leak
This is the GoToNext code:
.h
#interface GoToNext : NSObject <UIWebViewDelegate> {
id __weak target;
SEL next;
}
-(id)initWithTarget:(id)target andNext:(SEL)next;
-(void)webViewDidFinishLoad:(UIWebView *)webView;
#end
.m
#import "GoToNext.h"
#implementation GoToNext
-(id)initWithTarget:(id)_target andNext:(SEL)_next {
self = [super init];
if (self) {
target = _target;
next = _next;
}
return self;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[target performSelector:next];
}
#end
When you create an instance of GoToNext using alloc, that instance has a retain count of 1. Somewhere in your app you must release this instance before you lose your only reference to it (which in this case is the delegate property of webView). The delegate property of a UIWebView uses assign semantics, so assigning your instance of GoToNext to that property does not retain it. This means that you cannot release or autorelease it while it is still the delegate of webView or webView.delegate will point to deallocated memory.
If you're sure you're only setting webView.delegate once in the lifecycle of the class containing this code, you can get by with just put [webView.delegate release] in the dealloc method of that class. If you're setting it more than once, you might try creating a method like:
-(void)setWebViewDelegate:(id)delegate {
if (webView.delegate) {
[webView.delegate release];
}
webView.delegate = delegate;
}
And using that method to set webView's delegate. There are other ways to handle this situation, but I think this method will probably require the fewest changes to your code.
Of course, in my opinion the best solution of all is to just convert the application to ARC and never have to worry about this kind of thing again.

Tableview with 2 columns y=mx example

How would I make a tableview with 2 columns (x and y) for a function of y=mx
Ive tried lots of things, all of which end in complete failure.
Can someone please make me a sample code and explain it.
Ive asked and asked and people have directed me to all sorts of tutorials of bool, and how to copy and paste contents, how to save to a file, how to make a list of open applications, none of which help me because they are overly complicated
i have this
//array.m
#import "array.h"
#implementation array
- (IBAction)makeArrays:(id)sender
{
int x,y;
NSNumber *multiplier=[NSNumber numberWithFloat:[mField floatValue]];
for (x=0;x++;x<181)
{
y=[multiplier floatValue]*x;
NSNumber *xValue = [NSNumber numberWithInt:x];
NSNumber *yValue = [NSNumber numberWithInt:x];
NSArray *xArray = [NSArray arrayWithObject:xValue];
NSArray *yArray = [NSArray arrayWithObject:yValue];
}
}
#end
and class file
//array.h
#import <Cocoa/Cocoa.h>
#interface array : NSObject {
IBOutlet id mField;
}
- (IBAction)makeArrays:(id)sender;
#end
where do i go from here?
The first thing you should do in OOP is consider the classes of objects. Cocoa uses an MVC (Model, View, Controller) architecture, so classes should fit in one of these three categories. Cocoa already provides the NSTableView class which works quite well, so that leaves the model and controller.
There are a number of different approaches to the model class you could take:
You could write a function table class that holds x and y values in separate arrays
You could write a function table class that has a single array of (x,y) pairs.
In either this or the previous implementation, you could provide a public interface that supports both arrangements (i.e. they'd have methods that return a y given an x, and properties that are x, y, and (x,y) collections). Some implementation details would depend on how you're connecting the table view to the data (bindings, or the older NSTableViewDataSource protocol).
You could also use an array of x values, and create a value transformer. With this approach, the y-values exist in the table view and not the model.
And so on
The application requirements will determine which approach to take. I'll show you the value transformer approach, as it requires the least amount of code.
For the controller, you could rely on NSArrayController (which works quite well with NSTableView), or create your own. For example, you could use an NSMutableArray as the model, and create a controller that maps the values from the array to other values. This controller could perform the mapping using blocks or some function classes that you define.
As you see, there are quite a few options. I'm going to go with the option that requires the least coding: a value transformer, an NSArrayController for the controller and an NSMutableArray (stored in an object that also stores a value transformer) for the model. In the following, code should be stored in files following the standard convention: each interface and implementation is in a separate file with name equal to the class, and an extension of ".h" for interfaces and ".m" for implementation. I also won't bother with the common import statements, such as for Cocoa/Cocoa.h and each class implementation's own interface.
First, the value transformer. Actually, there are two, an abstract superclass and a concrete subclass. This separation is so that you can easily add other function types later. The superclass, FunctionTransformer, is very simple. All that needs to be overridden from its base, NSValueTransformer, is the method that returns the class of transformed values, transformedValueClass:
#interface FunctionTransformer : NSValueTransformer
+ (Class)transformedValueClass;
#end
#implementation Function
+ (Class)transformedValueClass {
return [NSNumber class];
}
#end
The concrete subclass, LinearTransformer, needs to override the primary method of value transformers: transformedValue:. Since linear transforms are invertible, we'll also provide a reverseTransformedValue:. It will also need properties for the slope and intercept values.
#import "FunctionTransformer.h"
#interface LinearTransformer : FunctionTransformer {
NSNumber *m_;
NSNumber *b_;
}
#property (nonatomic,retain) NSNumber *slope;
#property (nonatomic,retain) NSNumber *intercept;
+ (BOOL)allowsReverseTransformation;
-(id)init;
-(id)initWithSlope:(float)slope;
-(id)initWithIntercept:(float)intercept;
-(id)initWithSlope:(float)slope intercept:(float)intercept;
-(void)dealloc;
-(NSNumber*)transformedValue:(id)value;
-(NSNumber*)reverseTransformedValue:(id)value;
#end
#implementation LinearTransformer
#synthesize slope=m_, intercept=b_;
+(BOOL)allowsReverseTransformation {
return YES;
}
-(id)initWithSlope:(float)m intercept:(float)b {
if ((self = [super init])) {
m_ = [[NSNumber alloc] initWithFloat:m];
b_ = [[NSNumber alloc] initWithFloat:b];
}
return self;
}
-(id)init {
return [self initWithSlope:1.0 intercept:0.0];
}
-(id)initWithSlope:(float)slope {
return [self initWithSlope:slope intercept:0.0];
}
-(id)initWithIntercept:(float)intercept {
return [self initWithSlope:1.0 intercept:intercept];
}
-(void)dealloc {
[b release];
[m release];
[super dealloc];
}
-(NSNumber*)transformedValue:(id)value {
return [NSNumber numberWithFloat:([value floatValue] * [m floatValue] + [b floatValue])];
}
-(NSNumber*)reverseTransformedValue:(id)value {
return [NSNumber numberWithFloat:(([value floatValue] - [b floatValue]) / [m floatValue])];
}
#end
A specific LinearTransformer needs to be registered to be used so that you can set the slope and intercept. The application delegate could own this transformer (along with the x value collection), or you could write a custom controller. We're going to write a model class that bundles together the x values and the value transformer, named FunctionTable. Setting the function transformer requires a sub tasks: registering the transformer as a value transformer (using +setValueTransformer:forName:). This means we'll need to provide our own setter (setF:) for the function transformer property (f).
#import "FunctionTransformer.h"
extern NSString* const kFunctionTransformer;
#interface FunctionTable : NSObject {
NSMutableArray *xs;
FunctionTransformer *f;
}
#property (nonatomic,retain) IBOutlet NSMutableArray *xs;
#property (nonatomic,retain) IBOutlet FunctionTransformer *f;
#end
// FunctionTable.m:
#import "LinearTransformer.h"
NSString* const kFunctionTransformer = #"Function Transformer";
#implementation FunctionTable
#synthesize xs, f;
-(id) init {
if ((self = [super init])) {
xs = [[NSMutableArray alloc] init];
self.f = [[LinearTransformer alloc] init];
[f release];
}
return self;
}
-(void)dealloc {
[f release];
[xs release];
[super dealloc];
}
-(void)setF:(FunctionTransformer *)func {
if (func != f) {
[f release];
f = [func retain];
[NSValueTransformer setValueTransformer:f forName:kFunctionTransformer];
}
}
#end
By default, FunctionTable uses a LinearTransformer. If you want to use a different one, simply set the FunctionTables's f property. You could do this in Interface Builder (IB) by using bindings. Note that in this simplistic implementation, the value transformer is always registered under the name "Function Transformer", effectively limiting you to one FunctionTable. A more complex scheme would be to give every FunctionTable their own function transformer name which would be used when registering their own FunctionTransformer.
To set everything up:
Open the app's main window nib in IB.
Instantiate an NSArrayController and a FunctionTable (and your custom app delegate, if any).
To the main window, add:
Buttons to add and remove elements,
labels and NSTextFields for the slope and intercept,
an NSTableView.
Set the table headers to "x" and "y" (not necessary for app to work)
Set up the connections:
Have the add & remove buttons send to the NSArrayController's add: and remove: actions.
Bind the NSTextFields values to the FunctionTables's f.slope and f.intercept key paths.
Bind the values of both columns of the NSTableView to FunctionTables's xs.
Set the value transformer for the second column to "Function Transformer"
Bind the NSArrayController's content array to the FunctionTable's xs key.
If you've got an app delegate, connect it to the File's Owner's delegate outlet.
Now build and run. You can use the add and remove buttons to add and remove rows to/from the table. You can edit the "x" and "y" column in a row (the latter is thanks to reverseTransformedValue:). You can sort by either the "x" or "y" columns. You can change the slope and intercept, though you won't notice the updates in the table unless you select the rows individually.
Advanced Topics
To fix the table view update problem, we need to propagate changes on one object's (a FunctionTransformer) properties to changes on another's (a FunctionTable) properties. We'll have the FunctionTable observe changes on its function transformer's properties and, when it FunctionTable receives a notice that any such property has changed, send a notice that the xs property has changed (which is a bit of an abuse, since xs hasn't actually changed). This is going to get a little magical, so bear with me.
An object subscribes to changes on another object using the KVO method addObserver:forKeyPath:options:context: of the other object, and unsubscribes using removeObserver:forKeyPath:. These methods just need to be called, not written. Notifications are handled by a observeValueForKeyPath:ofObject:change:context: method of the observing object, so this method needs to be written. Finally, an object can send its own notifications by calling willChangeValueForKey: and didChangeValueForKey:. Other methods exist to send notifications that only part of a collection has changed, but we won't use them here.
Our FunctionTable could handle the change subscription and unsubscription, but then it has to know which properties of the function transformer to observe, which means you couldn't change the type of the transformer. You could add methods to each concrete function transformer to subscribe and unsubscribe an observer:
#implementation LinearTransformer
...
-(void)addObserver:(NSObject *)observer
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
[self addObserver:observer
forKeyPath:#"slope"
options:options
context:context];
[self addObserver:observer
forKeyPath:#"intercept"
options:options
context:context];
}
-(void)removeObserver:(id)observer {
[self removeObserver:observer forKeyPath:#"slope"];
[self removeObserver:observer forKeyPath:#"intercept"];
}
#end
However, this will require a fair bit of code repetition in each method and across each concrete function transformer. Using some magic (reflection and closures, or as they're called in Objective-C, blocks ([2])), we can add the methods (named addObserver:options:context: and removeObserver:, as they are functionally similar to the KVO methods for subscribing & unsubscribing) to FunctionTransformer, or even to NSObject. Since observing all properties on an object isn't just limited to FunctionTransformers, we'll add the methods to NSObject. For this to work, you'll need either OS X 10.6 or PLBlocks and OS X 10.5.
Let's start from the top down, with the changes to FunctionTable. There's now new subtasks when setting the function transformer: unsubscribing from changes to the old transformer and subscribing to changes to the new one. The setF: method thus needs to be updated to make use of NSObject's new methods, which will be defined in a header named "NSObject_Properties.h". Note we don't need to worry about the implementation of these methods yet. We can use them here, having faith that we will write suitable implementations later. FunctionTable also needs a new method to handle change notifications (the observeValueForKeyPath:ofObject:change:context: referred to earlier).
#import "NSObject_Properties.h"
#interface FunctionTable
...
-(void)setF:(FunctionTransformer *)func {
if (func != f) {
[f removeObserver:self];
[f release];
f = [func retain];
[f addObserver:self
options:NSKeyValueObservingOptionPrior
context:NULL];
[NSValueTransformer setValueTransformer:f forName:kFunctionTransformer];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (object == f) {
if ([[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
[self willChangeValueForKey:#"xs"];
} else {
[self didChangeValueForKey:#"xs"];
}
}
}
Next, we write the new methods on NSObject. The methods to subscribe or unsubscribe from changes will loop over the object's properties, so we'll want a helper method, forEachProperty, to perform the loop. This helper method will take a block that it calls on each property. The subscription and unsubscription methods will simply call forEachProperty, passing a block that calls the standard KVO methods (addObserver:forKeyPath:options:context: and removeObserver:forKeyPath:) on each property to add or remove subscriptions.
//NSObject_Properties.h
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#interface NSObject (Properties)
typedef void (^PropertyBlock)(objc_property_t prop, NSString *name);
-(void)forEachProperty:(PropertyBlock)block;
-(void)addObserver:(id)observer options:(NSKeyValueObservingOptions)options context:(void *)context;
-(void)removeObserver:(id)observer;
#end
// NSObject_Properties.m:
...
#implementation NSObject (Properties)
-(void)forEachProperty:(PropertyBlock)block {
unsigned int propCount, i;
objc_property_t * props = class_copyPropertyList([self class], &propCount);
NSString *name;
for (i=0; i < propCount; ++i) {
name = [[NSString alloc]
initWithCString:property_getName(props[i])
encoding:NSUTF8StringEncoding];
block(props[i], name);
[name release];
}
free(props);
}
-(void)addObserver:(NSObject *)observer
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
[self forEachProperty:^(objc_property_t prop, NSString *name) {
[self addObserver:observer
forKeyPath:name
options:options
context:context];
}];
}
-(void)removeObserver:(id)observer {
[self forEachProperty:^(objc_property_t prop, NSString *name) {
[self removeObserver:observer forKeyPath:name];
}];
}
#end

Resources