showWindow但等待窗口加载?

时间:2014-05-18 16:20:58

标签: objective-c cocoa

我有两个.xib文件,MainMenu和PreferenceMenu。我将文件的PreferenceMenu所有者设置为NSWindowController的子类,以便我可以修改窗口的打开,修改等方式。

通常我通过IB打开PreferenceMenu,将showWindow:动作发送到Preferences Controller对象。这一直很好,窗户一直打开完美。我还在PreferenceMenu中实施了NSTabViewNSToolBar,因为不同的标签包含不同的偏好。

无论如何,我现在必须提示用户是否要在首次加载应用程序后打开PreferenceMenu。我的过程检查是否设置了NSUserDefaults键,然后提示并设置密钥(如果不是)。如果返回的NSAlert按钮是特定按钮,则我打开PreferenceMenu并切换到正确的选项卡。

这个过程的要点是:

  1. CCPreferencesController.h
  2. 中导入CCAppDelegate.h
  3. 创建strong窗口(适用于ARC)的CCPreferencesController引用
  4. 使用NSAlert提示,如果返回按钮正确,请运行以下命令:

    self.windowController = [[CCPreferencesController alloc] initWithWindowNibName:@"PreferenceMenu"];
    [self.windowController showWindow:self];
    
  5. CCAppDelegate.h

  6. 中导入CCPreferencesController.m
  7. 处理NSAlert中的CCPreferencesController.m回复:

    - (void)showWindow:(id)sender{
        if ([CCAppDelegate class] == [sender class]){
            [self openLogin];
        }
        [super showWindow:sender];
    }
    
  8. TabView方法中切换openLogin
  9. 这更好的工作正常,除了showWindow之外,在窗口的内容实际加载之前调用。这意味着调用self.toolbarself.tabView等会返回NULL。我的解决方案是简单地使用一个计时器来等待元素加载,但这远远不够优雅。

    我的问题,是如何避免使用延迟,而是在openLogin上调用我的showWindow方法,但等待窗口&# 39;要加载的内容?

    我还99%确定我的代码非常糟糕,包括我如何导入.h文件和继承文件的所有者,所以非常感谢任何使这更好的提示。< / p>


    对于那些想要更详细地查看代码的人,这里是相关部分

    CCAppDelegate.h:

    #import <Cocoa/Cocoa.h>
    #import "CCPreferencesController.h"
    
    @class WebView;
    
    @interface CCAppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSSharingServiceDelegate>
    
    @property (strong) CCPreferencesController *windowController;
    ...
    
    @end 
    

    CCAppDelegate.m:

    - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame{
        bool loginPrompted = [[NSUserDefaults standardUserDefaults] boolForKey:@"loginPrompted"];
        if (!loginPrompted){
            NSAlert *alert = [NSAlert alertWithMessageText:@"Login"
                                             defaultButton:@"Login"
                                           alternateButton:@"Cancel"
                                               otherButton:nil
                                 informativeTextWithFormat:@"Would you like to login to enable voting?"];
            // Display alert
            [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
                // Return focus to window
                [[alert window] orderOut:self];
                NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
                [prefs setBool:YES forKey:@"loginPrompted"];
                [prefs synchronize];
                if (returnCode == 1){
                    self.windowController = [[CCPreferencesController alloc] initWithWindowNibName:@"PreferenceMenu"];
                    [self.windowController showWindow:self];
                }
            }];
    
        }
    
    }
    

    CCPreferencesController.h:

    #import <Cocoa/Cocoa.h>
    
    @interface CCPreferencesController : NSWindowController {}
    
    ...
    
    @end
    

    CCPreferencesController.m

    #import "CCPreferencesController.h"
    #import "CCAppDelegate.h"
    
    @implementation CCPreferencesController
    
    - (id)init{
        if(self = [super initWithWindowNibName:@"PreferenceMenu"]) {}
        return self;
    }
    
    - (void)awakeFromNib{
        ... 
        [self.toolbar setSelectedItemIdentifier:@"general"];
    }
    
    - (void)showWindow:(id)sender{
        if ([CCAppDelegate class] == [sender class]){
            [self openLogin];
        }
        [super showWindow:sender];
    }
    
    // This is gross
    - (void)openLogin{
        dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 0.1);
        dispatch_after(delay, dispatch_get_main_queue(), ^(void){
            [self.toolbar setSelectedItemIdentifier:@"tab"];
            [self.tabView selectTabViewItemAtIndex:1];
            NSRect frame = [self.window frame];
            frame.size.height += 55;
            frame.origin.y -= 55;
            [self.window setFrame:frame display:YES animate:YES];
        });
    
    }
    

3 个答案:

答案 0 :(得分:1)

走这条路,但也许你的情况是正确的,因为你做了bloc kingly但是在你调用showWindow之前你可以强制加载窗口。在致电self.window

之前,请致电super showWindow

请注意,调用getter来实现副作用也远非干净!

最好将windowDidLoad等窗口中需要窗口的代码移动

MAYBE

@implementation MyWindowController {
    NSDictionary *_options;
}

- (void)showWindow:(id)sender{
    if ([CCAppDelegate class] == [sender class]){
        _options = @{@"Login": @YES};
    } else {
        _options = nil; 
    }

    if(self.isWindowLoaded) {
        [self applyOptions];
    }
    [super showWindow:sender];
}

- (void)windowDidLoad {
    [self applyOptions];
}

- (void)applyOptions {
    if([_options[@"Login"] boolValue]) {
        //if logged in ... blablabla
    }
}

答案 1 :(得分:0)

如果您在[super showWindow:sender]之前致电[self openLogin]怎么办?像

- (void)showWindow:(id)sender {
    [super showWindow:sender];
    if ([sender isKindOfClass:[CCAppDelegate class]]) {
        [self openLogin];
    }
}

添加了:

抱歉,我建议在[super showWindow:sender]之前移动[self openLogin]并不能解释您的问题。 self.toolbarself.tabViewnil,因为它们是IBOutlet,并且在加载相应的nib文件之前不会设置插座。延迟只是等待nib文件加载的一种方式,你承认自己这是一个可疑的方法。另一个解决方案是在调用[self openLogin]之前加载nib并且我已经提议调用-showWindow:因为-showWindow:如果尚未加载nib则间接加载nib。我强烈建议您在-[CCPreferencesController awakeFromNib]-[CCPreferencesController windowDidLoad]中添加断点。因此,您可以在调试器中查看何时加载nib文件以及何时可以使用IBOutlet

我对-[NSWindowController showWindow:]在runloop上安排某些事情一无所知。不幸的是,@ Daij-Djan还没有提供有关在runloop上安排的确切内容的任何细节。 @ Daij-Djan建议在-openLogin中执行-windowDidLoad次操作,但我看到序列before_showWindow:, windowDidLoad, after_showWindow:。我得出结论,nib立即加载,而不是在runloop上安排。这就是为什么在没有任何延迟的情况下致电-openLogin是安全的原因。

我再次鼓励您在调试器中检查自己是如何加载nib文件的。最好自己检查一下,而不是依靠别人的话。

如何避免使用延迟的问题的答案:在加载窗口后调用-openLogin,在这种情况下在-openLogin之后调用-showWindow:,因为它会导致nib加载。

答案 2 :(得分:0)

在我看来,看看其他选项,你应该这样做:

        if (returnCode == 1){
            self.windowController = [[CCPreferencesController alloc] initWithWindowNibName:@"PreferenceMenu"];
            [self.windowController showWindow:self];
            __weak CCAppDelegate* weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf.windowController openLogin];
           }

而且:

- (void)openLogin{
    [self.toolbar setSelectedItemIdentifier:@"tab"];
    [self.tabView selectTabViewItemAtIndex:1];
    NSRect frame = [self.window frame];
    frame.size.height += 55;
    frame.origin.y -= 55;
    [self.window setFrame:frame display:YES animate:YES];
}

你已经指示WC做某事了。如果WC提供了一种公共方法来执行此类操作,那么指示它显示特定选项卡并不是不合时宜的。

这基本上是我在用户点击通知时所做的事情。我显示窗口,让窗口控制器对下一个runloop迭代的通知中userInfo字典中的内容做出反应。

[edit] - 现在使用dispatch_async

在runloop上安排openLogin