admin管理员组

文章数量:1391929

I struggle to understand what I'm doing wrong here.

In my document-based app the document's model is modified in a notification. The model is correctly updated with the new content and I just want to tag the view as needing update.

To my surprise, nothing happened until I manually resized the window which led me to the following workaround-wart (a dummy change to the window size):

    NSWindow *window = document.window;
#if false
    window.contentView.needsDisplay = YES;
#else
    NSSize sz = document.model.canvasSize;
    [window setContentSize:NSMakeSize(0,0)];
    [window setContentSize:sz];
#endif
    [window makeKeyAndOrderFront:self];

Obviously, that's not how it's supposed to be done, but I'm at my wits end here.

The inital model renders perfectly, but any changes to the model are ignored.
I tried dispatching needsDisplay on the main thread using performSelectorOnMainThread but that didn't seem to matter.

Any clues to what goes on here would be greatly appreciated.


Update

I've created a minimal(?) example showing the same behaviour available on github:

Most of the functionality is in AppDelegate.m:

@interface AppDelegate ()
{
    Document *_documentRef;
}
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    [[NSDistributedNotificationCenter defaultCenter] addObserver:self
                                                        selector:@selector(notificationHandler:)
                                                            name:@"foo"
                                                          object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                                        selector:@selector(notificationHandler:)
                                                            name:@"foo"
                                                          object:nil];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (Document *)document
{
    Document *doc = _documentRef;
    NSLog(@"_documentRef: %@", _documentRef);
    if (doc == nil) {
        Document *doc = [[Document alloc] init];
        NSLog(@"Created document: %@", doc);
        _documentRef = doc;
        NSDocumentController *docController = NSDocumentController.sharedDocumentController;
        [docController addDocument:doc];
        [doc makeWindowControllers];
    }

    return _documentRef;
}

- (void)notificationHandler:(NSNotification *)notification {
    NSString *newModel = notification.userInfo[@"newModel"];
    NSData *newModelData = [newModel dataUsingEncoding:NSUTF8StringEncoding];
    Document *document = [self document];
    NSLog(@"document: %@", document);
    NSLog(@"Document before update: %@", document.model);
    Boolean success = [document readFromData:newModelData ofType:@"foo" error:nil];
    NSLog(@"Document after update: %@", document.model);
    
    NSWindow *window = document.window;
    window.contentView.needsDisplay = YES;
    [window makeKeyAndOrderFront:self];
}


- (IBAction)localNotification:(id)sender {
    NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date]
                                                          dateStyle:NSDateFormatterShortStyle
                                                          timeStyle:NSDateFormatterShortStyle];
    NSString *newModel = [NSString stringWithFormat:@"Local: %@", dateString];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"foo"
                                                        object:@"bar"
                                                      userInfo:@{@"newModel": newModel}];
}

- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
    return NO;
}

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender {
    return NO;
}

- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)hasVisibleWindows {
    return NO;
}


@end

Document.m is as small as they get:

@implementation Document

- (instancetype)init {
    self = [super init];
    if (self) {
        _model = @"Initial value";
    }
    return self;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
    self.model = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return (self.model != nil);
}

- (NSWindow *)window {
    return [self.windowControllers[0] window];
}

@end

and finally the view (MyView.m):

@implementation MyView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    NSString *model = [[[self.window windowController] document] model];
    [model drawAtPoint:NSMakePoint(50.0, self.bounds.size.height/2.0) withAttributes:nil];
}

@end

To send a local notification I hooked up an action from Help -> Notify to localNotification:, and to create a distributed notification there is a (non-sandboxed) "Sender" target in Xcode creating a simple tool:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date]
                                                              dateStyle:NSDateFormatterShortStyle
                                                              timeStyle:NSDateFormatterShortStyle];
        NSString *newModel = [NSString stringWithFormat:@"Remote: %@", dateString];
     
        [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"foo"
                                                                       object:@"bar"
                                                                     userInfo:@{@"newModel": newModel}
                                                           deliverImmediately:YES];

    }
    return 0;
}

Running the app and sending some notifications yields the following log output:

_documentRef: (null)
Created document: <Document: 0x600003436450>
document: <Document: 0x600003436450>
Document before update: Initial value
Document after update: Local: 2025-03-14, 11:22
_documentRef: <Document: 0x600003436450>
document: <Document: 0x600003436450>
Document before update: Local: 2025-03-14, 11:22
Document after update: Remote: 2025-03-14, 11:22
_documentRef: <Document: 0x600003436450>
document: <Document: 0x600003436450>
Document before update: Remote: 2025-03-14, 11:22
Document after update: Local: 2025-03-14, 11:22

but only the first notification cause the view to update.

FWIW, resizing the window doesn't invoke the view's drawRect: method (the y-position of the text should be in the middle of the view)

I'm pretty sure I'm missing something basic here...

本文标签: objective cNSView needsDisplay is ignoredStack Overflow