Adding yourself as an observer to an array than an NSArrayController is bound to? - objective-c

I have class called AppController which contains a mutable array of Person objects called people. Each person simply has an NSString and a float.
I also have an NSArrayController whose contentArray is bound to the people array in AppController. Then I have the usual setup of a table view bound to the array controller to show a list of all the person objects.
In my AppController class, I tried to register as an observer of the people array to add undo support. Here's how I do it:
#implementation AppController
#synthesize people;
- (id)init
{
self = [super init];
if (self != nil) {
people = [[NSMutableArray alloc] init];
undoManager = [[NSUndoManager alloc] init];
[self addObserver:self
forKeyPath:#"people"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
}
return self;
}
- (void)undo:(id)sender
{
[undoManager undo];
}
- (void)redo:(id)sender
{
[undoManager redo];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
int kindOfChange = [[change objectForKey:NSKeyValueChangeKindKey] intValue];
int index = [[change objectForKey:NSKeyValueChangeIndexesKey] firstIndex];
Person *p;
if (kindOfChange == NSKeyValueChangeInsertion) {
p = [change objectForKey:NSKeyValueChangeNewKey];
[[undoManager prepareWithInvocationTarget:self] removePersonAtIndex:index];
} else {
p = [change objectForKey:NSKeyValueChangeOldKey];
[[undoManager prepareWithInvocationTarget:self] addPerson:p atIndex:index];
}
}
- (void)addPerson:(Person *)person atIndex:(int)index
{
[[self mutableArrayValueForKey:#"people"] addObject:person];
}
- (void)removePersonAtIndex:(int)index
{
[[self mutableArrayValueForKey:#"people"] removeObjectAtIndex:index];
}
#end
With this code, I can successfully add a person object and then undo it. I cannot, however, undo deleting a person object. I always get this exception when I undo a delete operation:
[<NSCFArray 0x3009a0> addObserver:forKeyPath:options:context:] is not supported. Key path: personName
It seems like I'm not the only one, though: http://www.cocoabuilder.com/archive/cocoa/84489-modifying-the-array-of-an-nsarraycontroller.html#84496
Any ideas?
UPDATE:
Here's the backtrace:
#0 0x7fff8161b41e in -[NSArray(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:]
#1 0x7fff8822fc47 in -[_NSModelObservingTracker _registerOrUnregister:observerNotificationsForModelObject:]
#2 0x7fff883b2bb9 in -[_NSModelObservingTracker startObservingModelObjectAtReferenceIndex:]
#3 0x7fff883acc41 in -[_NSModelObservingTracker setObservingToModelObjectsRange:]
#4 0x7fff883aca5a in -[NSTableBinder tableView:updateVisibleRowInformation:]
#5 0x7fff883ac98e in -[_NSBindingAdaptor tableView:updateVisibleRowInformation:]
#6 0x7fff882b4c7b in -[NSTableView drawRect:]
#7 0x7fff882ab081 in -[NSView _drawRect:clip:]
#8 0x7fff882a9cf4 in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:]
#9 0x7fff882aa05e in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:]
#10 0x7fff882aa05e in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:]
#11 0x7fff882a83c6 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:]
#12 0x7fff882a9292 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:]
#13 0x7fff882a9292 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:]
#14 0x7fff882a7ee8 in -[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:]
#15 0x7fff882a479a in -[NSView _displayRectIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:]
#16 0x7fff8821dff6 in -[NSView displayIfNeeded]
#17 0x7fff88218ea2 in _handleWindowNeedsDisplay
#18 0x7fff81da9077 in __CFRunLoopDoObservers
#19 0x7fff81d84ef4 in __CFRunLoopRun
#20 0x7fff81d8484f in CFRunLoopRunSpecific
#21 0x7fff8836ab31 in _NSUnhighlightCarbonMenu
#22 0x7fff8834d0c1 in -[NSMenu performKeyEquivalent:]
#23 0x7fff8834be69 in -[NSApplication _handleKeyEquivalent:]
#24 0x7fff8821caa1 in -[NSApplication sendEvent:]
#25 0x7fff881b3922 in -[NSApplication run]
#26 0x7fff881ac5f8 in NSApplicationMain
#27 0x100001469 in main at main.m:13

What's happening is that [change objectForKey: NSKeyValueChangeNewKey] returns NSArray *, not Person *. You should call -lastObject on the array returned to get the actual Person object (assuming that the array contains only one object).

Related

UIWebDocumentView _updateSubviewCaches crash in iOS10

I am getting the following crash in HockeyApp more seriously in iOS10. Please find the crash log as given below.
Thread 4 Crashed:
0 libobjc.A.dylib 0x0000000187242f30 objc_msgSend + 16
1 UIKit 0x000000018e86e914 -[UIWebDocumentView _updateSubviewCaches] + 36
2 UIKit 0x000000018e69093c -[UIWebDocumentView subviews] + 88
3 UIKit 0x000000018e941bd4 -[UIView(CALayerDelegate) _wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:] + 68
4 UIKit 0x000000018e63d770 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1248
5 QuartzCore 0x000000018bb0640c -[CALayer layoutSublayers] + 144
6 QuartzCore 0x000000018bafb0e8 CA::Layer::layout_if_needed(CA::Transaction*) + 288
7 QuartzCore 0x000000018bafafa8 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 28
8 QuartzCore 0x000000018ba77c64 CA::Context::commit_transaction(CA::Transaction*) + 248
9 QuartzCore 0x000000018ba9f0d0 CA::Transaction::commit() + 508
10 QuartzCore 0x000000018ba9faf0 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 116
11 CoreFoundation 0x00000001887a57dc __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 28
12 CoreFoundation 0x00000001887a340c __CFRunLoopDoObservers + 368
13 CoreFoundation 0x00000001886d2068 CFRunLoopRunSpecific + 472
14 WebCore 0x000000018d273a2c RunWebThread(void*) + 452
15 libsystem_pthread.dylib 0x000000018788b860 _pthread_body + 236
16 libsystem_pthread.dylib 0x000000018788b770 _pthread_start + 280
17 libsystem_pthread.dylib 0x0000000187888dbc thread_start + 0
Any idea what is going on here ?
Seems like the following crash groups are also related.
Crash Group 1
[UIWebBrowserView _collectAdditionalSubviews]
objc_msgSend() selector name: autorelease
Crash Group 2
[UIWebDocumentView subviews]
objc_msgSend() selector name: arrayByAddingObjectsFromArray:
I am answering to my own question, since I am able to replicate this issue and found the root cause.
Please note the following.
1. I am using a UIWebView. First of all, this is not recommended.
2. The following way of implementation causes this issue. I am writing pseudo-code here to demonstrate the problem.
#implementation MyViewController
- (void)loadView {
//creating UIWebView Here
self.webView = [[UIWebView alloc] initWithFrame:_some_frame];
//setting the web view properties here.
//Adding to the view
[self.view addSubView: self.webView];
[self populateWebView];
}
- (void) populateWebView {
[self.webView loadHTMLString:_some_html
baseURL:[NSURL fileURLWithPath:_some_path]];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.webView) {
//The following two lines causing this issue.
self.webView.scrollView.contentInset = _some_insets;
self.webView.scrollView.scrollIndicatorInsets = _some_insets;
}
}
#end
3. The solution is given below.
#implementation MyViewController
- (void)loadView {
//creating UIWebView Here
self.webView = [[UIWebView alloc] initWithFrame:_some_frame];
//setting the web view properties here.
//Adding to the view
[self.view addSubView: self.webView];
[self populateWebView];
}
- (void) populateWebView {
[self.webView loadHTMLString:_some_html
baseURL:[NSURL fileURLWithPath:_some_path]];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
webView.scrollView.contentInset = _some_insets;
webView.scrollView.scrollIndicatorInsets = _some_insets;
}
#end
In my actual code, I was using a custom WebView class here by subclassing the UIWebView. Not think that will create some issue. The root cause is setting the "contentInset" and "scrollIndicatorInsets" in viewDidLayoutSubviews which is not a good idea. When I put the break points, "viewDidLayoutSubviews" called several times and eventually App crashes.
As a solution I moved the following part of the code into "webViewDidFinishLoad" which will call only when the WebView is ready after finished loading. So it makes sense to add this code under this delegate method.
webView.scrollView.contentInset = _some_insets;
webView.scrollView.scrollIndicatorInsets = _some_insets;

UIAlertView crash private method on iOS7

The stack is as below, how to fix this crash ?
It is only in iOS7 and why there is uitableview in the stack?
0 libobjc.A.dylib objc_msgSend + 5
1 UIKit -[UIAlertView(Private) modalItem:shouldDismissForButtonAtIndex:] + 62
2 UIKit -[_UIModalItemsCoordinator _notifyDelegateModalItem:tappedButtonAtIndex:] + 94
3 UIKit -[_UIModalItemAlertContentView tableView:didSelectRowAtIndexPath:] + 894
4 UIKit -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1078
5 UIKit -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 214
6 UIKit _applyBlockToCFArrayCopiedToStack + 316
7 UIKit _afterCACommitHandler + 430
8 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 20
- (id)initWithIdentifier:(LTAlertMsgIdentifier)alertIdentifier
delegate:(id /*<UIAlertViewDelegate>*/)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... {
LTAlertMsgManager *sharedAlertMsgMgr = [LTAlertMsgManager shareAlertManageInstance];
NSString *strMsg = [sharedAlertMsgMgr getLTAlertMsgByAlertID:alertIdentifier];
if ([NSString isBlankString:strMsg]){
// alert is invalid, if alert message is empty
return nil;
}
NSString *strTitle = [sharedAlertMsgMgr getLTAlertTitleByAlertID:alertIdentifier];
if (self = [super initWithTitle:([NSString isBlankString:strTitle] ? nil : strTitle)
message:strMsg
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil]){
va_list args;
va_start(args, otherButtonTitles);
for (NSString *arg = otherButtonTitles; arg != nil; arg = va_arg(args, NSString*))
{
[self addButtonWithTitle:arg];
}
va_end(args);
}
NSLog(#"cancel button index - %d", self.cancelButtonIndex);
return self;
}
The buttons in the UIAlertView are implemented using a UITableView. That's why tapping a button triggers a tableView:didSelectRowAtIndexPath.
Typical problems causing such errors are:
Alert not displayed from the main thread
Delegate already deallocated (make sure the alert delegate is retained somewhere for the entire life of the alert).
See iOS 7 bug or my bug in UIAlertView
UIAlertView delegate is
#property(nonatomic, assign) id delegate, so ensure alertView.delegate = nil when delegate is dealloced.

-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (myCustomClass)

I'm new to objective-c and cocoa, and trying to create a document based application that saves and loads a custom class to and from the filesystem. My custom class that conforms to nscoding protocol is as follow:
NSString *const description = #"description";
#implementation PhotoItem
#synthesize description = _description;
-(id)init {
if(self = [super init]){
self.description = #"";
}
return self;
}
/* serializing into file */
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.description forKey:description];
}
/* deserializing */
- (id)initWithCoder:(NSCoder *)aDecoder {
self.description = [aDecoder decodeObjectForKey:description];
return self;
}
and my document:
#implementation AMRDocument
#synthesize imgMainImage = _imgMainImage, lblCreationDate = _lblCreationDate, photo=_photo,
txtDescription = _txtDescription;
- (id)init
{
self = [super init];
if (self) {
self.photo = [[PhotoItem alloc] init];
}
return self;
}
- (NSString *)windowNibName
{
return #"AMRDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
NSLog(#"windowControllerDidLoadNib");
[super windowControllerDidLoadNib:aController];
// self.imgMainImage.image = self.photo.image;
[self.txtDescription setString: self.photo.description];
}
+ (BOOL)autosavesInPlace
{
return YES;
}
#pragma mark -saving and loading
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
// self.photo.image = self.imgMainImage.image;
// return [NSKeyedArchiver archivedDataWithRootObject:self.photo];
self.photo.description = self.txtDescription.string;
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self.photo];
return archivedData;
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
self.photo = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return YES;
}
- (void)windowWillClose:(NSNotification *)notification {
}
- (IBAction)archiveClicked:(id)sender {
self.photo = [NSKeyedUnarchiver unarchiveObjectWithFile:[#"~/desktop/myphoto1.photo"
stringByExpandingTildeInPath]];
[self.txtDescription setString:self.photo.description];
}
- (IBAction)archiveClicked:(id)sender {
self.photo.description = self.txtDescription.string;
[NSKeyedArchiver archiveRootObject:self.photo toFile:[#"~/desktop/myphoto1.photo"
stringByExpandingTildeInPath]];
}
#end
As you can see I've created two actions called archiveClicked, and unarchiveClicked, one for writing and the other for reading from the file.
It works perfectly fine.
When I'm saving the document using command+s, it can also successfully writes to the file, or at least no exception is thrown then. But when I'm trying to open up the saved file, it'll give me following exception in a popup instead of running the application and loads from the disk.
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (PhotoItem)'
terminate called throwing an exception
abort() called
Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff8f715b06 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff912f03f0 objc_exception_throw + 43
2 CoreFoundation 0x00007fff8f7158dc +[NSException raise:format:] + 204
3 Foundation 0x00007fff98a1e8c3 _decodeObjectBinary + 2559
4 Foundation 0x00007fff98a1dd24 _decodeObject + 226
5 Foundation 0x00007fff98a90df3 +[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
6 DocumentBasedApp 0x0000000108e49a11 -[AMRDocument readFromData:ofType:error:] + 113
7 AppKit 0x00007fff8d764527 -[NSDocument readFromURL:ofType:error:] + 546
8 AppKit 0x00007fff8d365348 -[NSDocument _initWithContentsOfURL:ofType:error:] + 135
9 AppKit 0x00007fff8d364ff4 -[NSDocument initWithContentsOfURL:ofType:error:] + 262
10 AppKit 0x00007fff8d78d765 -[NSDocumentController makeDocumentWithContentsOfURL:ofType:error:] + 332
11 AppKit 0x00007fff8d78ce7f __block_global_12 + 230
12 AppKit 0x00007fff8d78ca1b __block_global_8 + 955
13 AppKit 0x00007fff8d78bdba -[NSDocumentController _openDocumentWithContentsOfURL:usingProcedure:] + 593
14 AppKit 0x00007fff8d78c655 __block_global_7 + 273
15 libdispatch.dylib 0x00007fff926b4f01 _dispatch_call_block_and_release + 15
16 libdispatch.dylib 0x00007fff926b10b6 _dispatch_client_callout + 8
17 libdispatch.dylib 0x00007fff926b60c8 _dispatch_main_queue_callback_4CF + 275
18 CoreFoundation 0x00007fff8f6b7b4c __CFRunLoopRun + 1644
19 CoreFoundation 0x00007fff8f6b70e2 CFRunLoopRunSpecific + 290
20 HIToolbox 0x00007fff8df91eb4 RunCurrentEventLoopInMode + 209
21 HIToolbox 0x00007fff8df91b94 ReceiveNextEventCommon + 166
22 HIToolbox 0x00007fff8df91ae3 BlockUntilNextEventMatchingListInMode + 62
23 AppKit 0x00007fff8d44c533 _DPSNextEvent + 685
24 AppKit 0x00007fff8d44bdf2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
25 AppKit 0x00007fff8d4431a3 -[NSApplication run] + 517
26 AppKit 0x00007fff8d3e7bd6 NSApplicationMain + 869
27 DocumentBasedApp 0x0000000108e49652 main + 34
28 libdyld.dylib 0x00007fff8d2647e1 start + 0
I've actually created the actions to check the correctness of my nscoding functions and it seems to be working fine, but have no clue, and cannot find anything related to my issue.
Appreciate any help. Needless to say this is a part of my homework.
Edit:
name of this application is "PhotoManager". Earlier I had another document based app called "DocumentBasedApp", and the exception starts with :
Process: DocumentBasedApp [4852]
Path: /Users/USER/Library/Caches/*/DocumentBasedApp.app/Contents/MacOS/DocumentBasedApp
Identifier: com.cjcoax.DocumentBasedApp
Version: 1.0 (1)
Code Type: X86-64 (Native)
Parent Process: launchd [132]
User ID: 501
Your initWithCoder: needs to call [super init];.
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.description = [aDecoder decodeObjectForKey:description];
}
return self;
}
The serialized version of MyCustomClass differs from the current version in your code, probably due to changes during development.
/Users/USER/Library/Caches/*/DocumentBasedApp.app/Contents/MacOS/DocumentBasedApp
Deleting the Cache directory got rid of the old serialized data.
Finally solved, I delete everything from the cache as I could not find following path:
/Users/USER/Library/Caches/*/DocumentBasedApp.app/Contents/MacOS/DocumentBasedApp
and it started working! Bizarre!
I had another problem with NSCoder's decodeObjectForKey method while using FXForm - it crashed without any explanation.
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
self.someObject = [decoder decodeObjectForKey:#"someObject"];
}
return self;
}
The reason was in memory issues. Commonly, looks like when there is no explanation of a bug in logs, it means, it's a memory issue. I forgot to use correct property attribute - copy. I used assign instead. This is correct code:
#interface MyClass : : NSObject <FXForm, NSCoding>
#property (nonatomic, copy) SomeClass *someObject;
#end
Just in case somebody meets this problem too.

EXC_BAD_ACCESS error in Objective-C

Every now and then I get one of the EXC_BAD_ACCESS errors that seem to plague new Objective-C programmers.
In my program I'm trying to get a get the time in seconds, convert that to a string and then convert that to a NSData object for writing to a file. Here is the code I'm using, but it crashes with an EXC_BAD_ACCESS every time I run it. What am I doing wrong?
-(void) startTheClock{
NSTimeInterval cloqInTime = [NSDate timeIntervalSinceReferenceDate];
NSString * dateStr = [self stringFromTimeInterval:cloqInTime];
NSData * data = [[dateStr stringByAppendingString:#", "] dataUsingEncoding:NSUTF8StringEncoding];
NSLog([#"Data:" stringByAppendingString:[data description]]);
// [data retain]; // <-- Uncommenting this and the [data release] line doesn't prevent the error
[self writeData:data]; // <-- EXC_BAD_ACCESS happens here!
// [data release];
}
When I run this method I get the following output:
timeString: |2011-11-04 16:17:12|
Data:<32303131 2d31312d 30342031 363a3137 3a31322c 20>
As requested here is my stringFromTimeInterval method:
-(NSString *) stringFromTimeInterval:(NSTimeInterval)t{
NSDate * date = [NSDate dateWithTimeIntervalSinceReferenceDate:t];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *timeString = [dateFormat stringFromDate:date];
NSLog(#"timeString: %#", timeString);
return timeString;
}
Here is the stacktrace:
#0 0x00007fff84407e90 in objc_msgSend ()
#1 0x00000001054543f0 in 0x00000001054543f0 ()
#2 0x00000001000012ee in -[WorqAppDelegate startTheClock] at /Users/slayton/Documents/Xcode/Worq/Worq/WorqAppDelegate.m:61
#3 0x0000000100001164 in -[WorqAppDelegate cloqInAction:] at /Users/slayton/Documents/Xcode/Worq/Worq/WorqAppDelegate.m:37
#4 0x00007fff8cbb9a1d in -[NSObject performSelector:withObject:] ()
#5 0x00007fff86f69710 in -[NSApplication sendAction:to:from:] ()
#6 0x00007fff86f69642 in -[NSControl sendAction:to:] ()
#7 0x00007fff86f6956d in -[NSCell _sendActionFrom:] ()
#8 0x00007fff86f68a30 in -[NSCell trackMouse:inRect:ofView:untilMouseUp:] ()
#9 0x00007fff86fe88e0 in -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] ()
#10 0x00007fff86f6763a in -[NSControl mouseDown:] ()
#11 0x00007fff86f320e0 in -[NSWindow sendEvent:] ()
#12 0x00007fff86eca68f in -[NSApplication sendEvent:] ()
#13 0x00007fff86e60682 in -[NSApplication run] ()
#14 0x00007fff870df80c in NSApplicationMain ()
#15 0x0000000100001092 in main ()
Here is the writeData method, recordFile is an instance of NSFileHandle:
-(BOOL) writeData:(NSData *)data{
if (recordFile != NULL)
[recordFile writeData:data];
else
NSLog(#"Record file is null! No data written");
}
Keep an eye on your recordFile object. I'm wondering if this object has been over-released (and thus deallocated) but is not nil. This would cause the [recordFile writeData:data] method to throw an EXC_BAD_ACCESS. What is the lifespan of recordFile?
Going out on a limb, but I'd say that data is probably nil. Have you checked the value of data?
the debugger and in particular the stack trace is your friend, but yeah, most likely data is nil.
in the debugger window at the prompt, type: po data
or better, just look at the local variables in the left pane and see what data's value is.

How to extend NSTextStorage?

According to documentation, I try to subclass NSTextStorage and use it in text view:
/*
NSTextStorage is a semi-abstract subclass of NSMutableAttributedString. It
implements change management (beginEditing/endEditing), verification of
attributes, delegate handling, and layout management notification. The one
aspect it does not implement is the actual attributed string storage --- this is
left up to the subclassers, which need to override the two
NSMutableAttributedString primitives:
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;
*/
So I try to use delegate, to handle all needed events with same functionality:
#implementation MyTextStorage
- (id) init {
self = [super init];
if (self != nil) {
storage = [[NSMutableAttributedString alloc] init];
}
return self;
}
- (void) dealloc {
[storage release];
[super dealloc];
}
- (NSString *) string {
return [storage string];
}
- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
[storage replaceCharactersInRange:range withString:str];
}
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range {
[storage setAttributes:attrs range:range];
}
#end
It is no matter what I'm use as delegate: NSTextStorage or NSMutableAttributedString, The result is the same:
An uncaught exception was raised
* NSRunStorage, _NSBlockNumberForIndex(): index (18446744073709551615) beyond array
bounds (0)
* Terminating app due to uncaught exception 'NSRangeException', reason:
'*** NSRunStorage,
_NSBlockNumberForIndex(): index (18446744073709551615) beyond array
bounds (0)'
Stack trace:
0 CoreFoundation 0x00007fff840cd7b4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff885390f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff840cd5d7 +[NSException raise:format:arguments:] + 103
3 CoreFoundation 0x00007fff840cd564 +[NSException raise:format:] + 148
4 AppKit 0x00007fff86cc6288 _NSBlockNumberForIndex + 86
5 AppKit 0x00007fff86cc71d5 -[NSLayoutManager textContainerForGlyphAtIndex:effectiveRange:] + 364
6 AppKit 0x00007fff86d1f121 -[NSTextView(NSSharing) didChangeText] + 340
7 AppKit 0x00007fff86d44b68 -[NSTextView insertText:replacementRange:] + 2763
8 CocoaCalculator 0x0000000100002312 -[CalcTextView insertText:] + 65
9 CocoaCalculator 0x00000001000027ac -[CalcTextContainer initWithFrame:] + 1176
10 AppKit 0x00007fff86c23d44 -[NSCustomView nibInstantiate] + 646
11 AppKit 0x00007fff86b7be17 -[NSIBObjectData instantiateObject:] + 259
12 AppKit 0x00007fff86b7b202 -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] + 336
13 AppKit 0x00007fff86b7988d loadNib + 226
14 AppKit 0x00007fff86b78d9a +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:]
Termination start when I try to call
[textView insertText:#"123"];
But in standard NSTextStorage version all works properly.
What I need to do to extend this class ?
In addition to the NSAttributedString primitive methods that need implemented, I believe you also need to make a call to edited:range:changeInLength: inside your overrides of replaceCharactersInRange:withString: and setAttributes:range: respectively.
Something like:
- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
[storage replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedCharacters range:range changeInLength:[str length] - range.length];
}
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range {
[storage setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
}
The NSTextStorage documentation is a little bit vague in this area. The comment you quote in your question, about only needing to override 2 of the NSMutableAttributedString primitives only means that you don't have to override the other NSMutableAttributedString primitives.
You still have to override the primitives for the NSAttributedString superclass. Which means you need to implement -attributesAtIndex:effectiveRange:, -length and -string. I notice you did include an implementation of -string, so the two remaining ought to do it.
If you add:
- (NSUInteger)length
{
return [storage length];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
{
return [storage attributesAtIndex:location effectiveRange:range];
}
... then hopefully it will fix this problem.