【问题标题】:programmatically create initial window of cocoa app (OS X)以编程方式创建可可应用程序的初始窗口(OS X)
【发布时间】:2013-03-19 15:20:09
【问题描述】:

通常我正在制作 iOS 应用程序,但现在我正在尝试制作 OS X 应用程序,而我一开始就迷路了。假设我制作 iOS 应用程序的风格完全是程序化的,没有 xib 文件或任何东西,因为我通过打字比拖动有更多的控制权。然而在 OS X 编程中,它以一些带有菜单项和默认窗口的 xib 文件开始。菜单项中有很多项,所以这可能不是我想乱七八糟的东西,但我想自己以编程方式创建我的第一个窗口。

所以我这样做了:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{    
    NSUInteger windowStyleMask = NSTitledWindowMask|NSResizableWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask;
    NSWindow* appWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(200, 200, 1280, 720) styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
    appWindow.backgroundColor = [NSColor lightGrayColor];
    appWindow.minSize = NSMakeSize(1280, 720);
    appWindow.title = @"Sample Window";
    [appWindow makeKeyAndOrderFront:self];
    _appWindowController = [[AppWindowController alloc] initWithWindow:appWindow];
    [_appWindowController showWindow:self];
}

所以在这里,我先创建了一个窗口,并使用那个 windowController 来初始化这个窗口。窗口确实以这种方式显示,但我只能在此处指定内部元素,例如按钮和标签,而不能在 windowController 中指定。这让我感觉很糟糕,所以我尝试了另一种方法。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    _appWindowController = [[AppWindowController alloc] init];
    [_appWindowController showWindow:self];
}

在此之后,我想在 windowController 中设置 loadWindow: 函数中的其他元素,如下所示:

- (void)loadWindow
{
    [self.window setFrame:NSMakeRect(200, 200, 1280, 720) display:YES];
    self.window.title = @"Sample window";
    self.window.backgroundColor = [NSColor lightGrayColor];
    
    NSButton* sampleButton = [[NSButton alloc] initWithFrame:NSRectFromCGRect(CGRectMake(100, 100, 200, 23))];
    sampleButton.title = @"Sample Button!";
    [sampleButton setButtonType:NSMomentaryLightButton];
    [sampleButton setBezelStyle:NSRoundedBezelStyle];
    [self.window.contentView addSubview:sampleButton];
    NSLog(@"Loaded window!");
    [self.window makeKeyAndOrderFront:nil];
}

不幸的是,这永远不会奏效。 loadWindow: 永远不会被调用,windowDidLoad: 也不会被调用。他们去哪儿了?

请不要问我为什么不使用笔尖。我希望在内部制作一些高度定制的视图,可能是 OpenGL,所以我认为 nib 无法处理它。如果有人可以提供帮助,我将不胜感激。谢谢。

还有,谁知道如何以编程方式从头开始启动菜单项?

我正在使用最新的 Xcode。

【问题讨论】:

  • 您认为 XIB 无法处理 OpenGL 的想法是完全错误的。 Apple 提供了在 XIB 中使用的 OpenGL 视图。 XIB 可以处理任何以编程方式创建的视图,它们是封装这些项目的一种简单方式。
  • 这确实适用于某些情况,但想想 iOS 应用程序中的棋盘游戏。每个棋子都是带有自定义图像的视图,然后您需要移动它们。如果窗口中的大多数视图都是高度动态的,带有动画,那么很难想象如何在不造成大混乱的情况下实现它。
  • 如果您使用 OpenGL,您将拥有一个视图和所有部分,而没有在 OpenGL 中完成。如果您不在 OpenGL 中执行此操作,则同样的想法也适用,您可以在 XIB 中拥有一个超级视图,然后您可以以编程方式使用其他视图填充该视图。通过这种方式,您可以获得使用 XIB 的好处,同时仍然在代码中控制您的游戏板。
  • 那么你的意思是,在 xib 中创建一个大的自定义视图,然后以编程方式在代码中创建其他小视图?这听起来很有效,尽管关于根据窗口大小动态计算小视图的大小仍然存在更多问题。在 OS X 中它会改变,而在 iOS 中则不会。
  • 在 autoResizingMask 属性中查看视图。或者当它改变时自己计算窗口大小。或者使用滚动视图。无论哪种方式,这些问题都已经解决了,您只需要为您的应用找到正确的方法。

标签: objective-c macos cocoa


【解决方案1】:

覆盖AppWindowController 类中的-init 方法以创建窗口,然后使用该窗口调用super-initWithWindow: 方法(这是NSWindowController 的指定初始化程序)。

但我普遍同意 cmets 的观点,即没有理由避免使用 NIB。

【讨论】:

  • 谢谢。不过,看起来每个人都很抗拒不使用笔尖。对于iOS,我曾经做过,但是不完整,比如storyboard和segues只能做一堆有限的东西,所以我完全放弃了,转而使用纯代码。 OS X 不也是这样吗?
  • 我没有做过 iOS 开发,但我知道故事板和转场比 NIB 强得多。 NIB 只是您可以随意重构的“冻干”对象图。并不是说您最终在 NIB 中做所有事情并且几乎没有或没有代码。只是使用 NIB 不会干扰你必须做的代码工作,并且减轻了一些繁琐、繁重的编码,为什么不呢?
  • 故事板与 XIB 不同,是的,它们的局限性更大。就像我在其他评论中所说的那样,XIB 只是一种封装视图/对象的简单方法。他们并没有真正做任何其他事情。它们不是视图的替代品,也不是控制器的替代品。他们只是为了让生活更轻松。你可以不使用它们,但你最终会写更多的代码来做同样的事情。
  • 所以我再举一个例子,当你在做全屏游戏时,我可以看到一切都是定制的。在这种情况下,它只是放弃 xibs,还是利用原生全屏模式并在其中放置一个 OpenGL 视图来处理所有内容?
  • 我认为您对 XIB 是什么感到困惑。 XIB 所做的任何事情都不会阻止您在代码中做任何您想做的事情。把它想象成一个容器。它不是一个视图,它甚至不是一个对象。它只是一个序列化的对象容器,有助于代码组织和基本布局。
【解决方案2】:

我自己花了整个星期天的时间来研究这个问题。就像问这个问题的人一样,我更喜欢在没有 nib 文件(大多数情况下)或 Interface Builder 的情况下编写 iOS 和 OSX,并使用裸机。我确实使用 NSConstraints。如果您正在做更简单的 UI,那么避免 IB 可能不值得,但是当您进入更复杂的 UI 时,它会变得更加困难。

事实证明这很简单,为了“社区”的利益,我想我会在这里发布一个简洁的最新答案。那里有一些较旧的博客文章,我发现最有用的是来自Lap Cat Software 的文章。 'Tip O The Hat' 先生!

这假定为 ARC。修改你的 main() 看起来像这样:

#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"

int main(int argc, const char *argv[])
{
    NSArray *tl;
    NSApplication *application = [NSApplication sharedApplication];
    [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:application topLevelObjects:&tl];

    AppDelegate *applicationDelegate = [[AppDelegate alloc] init];      // Instantiate App  delegate
    [application setDelegate:applicationDelegate];                      // Assign delegate to the NSApplication
    [application run];                                                  // Call the Apps Run method

    return 0;       // App Never gets here.
}

您会注意到那里仍然有一个 Nib (xib)。这仅适用于主菜单。事实证明,即使在今天(2014 年),显然也无法轻松设置位置 0 菜单项。那就是标题=您的应用程序名称的那个。您可以使用[NSApplication setMainMenu] 将所有内容设置在它的右侧,但不是那个。所以我选择在新项目中保留 Xcode 创建的 MainMenu Nib,并将其剥离到位置 0 项。我认为这是一个公平的妥协,我可以接受。 UI Sanity 的一个简短插件...当您创建菜单时,请遵循与其他 Mac OSX 应用程序相同的基本模式

接下来将 AppDelegate 修改为如下所示:

-(id)init
{
    if(self = [super init]) {
        NSRect contentSize = NSMakeRect(500.0, 500.0, 1000.0, 1000.0);
        NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
        window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
        window.backgroundColor = [NSColor whiteColor];
        window.title = @"MyBareMetalApp";

        // Setup Preference Menu Action/Target on MainMenu
        NSMenu *mm = [NSApp mainMenu];
        NSMenuItem *myBareMetalAppItem = [mm itemAtIndex:0];
        NSMenu *subMenu = [myBareMetalAppItem submenu];
        NSMenuItem *prefMenu = [subMenu itemWithTag:100];
        prefMenu.target = self;
        prefMenu.action = @selector(showPreferencesMenu:);

        // Create a view
        view = [[NSTabView alloc] initWithFrame:CGRectMake(0, 0, 700, 700)];
    }
    return self;
 }

 -(IBAction)showPreferencesMenu:(id)sender
 {
    [NSApp runModalForWindow:[[PreferencesWindow alloc] initWithAppFrame:window.frame]];
 }

-(void)applicationWillFinishLaunching:(NSNotification *)notification
{
    [window setContentView:view];           // Hook the view up to the window
}

-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
    [window makeKeyAndOrderFront:self];     // Show the window
} 

还有宾果游戏......你很高兴!您可以像您熟悉的那样在 AppDelegate 中开始工作。希望对您有所帮助!

更新:我不再使用代码创建菜单,如上所示。我发现您可以在 Xcode 6.1 中编辑 MainMenu.xib 源代码。效果很好,非常灵活,只需进行一些实验即可了解其工作原理。比乱写代码更快,并且易于本地化!查看图片以了解我在说什么:

【讨论】:

  • 非常感谢您这么长时间后的详细解释。我认为几个月后没有人会照顾这个,但我改变了这个的正确答案并赞扬你的工作。再次非常感谢。
  • 谢谢!这很棒。
  • 如何找到我的“MainMenu”笔尖……我很困惑?什么是笔尖?!!!哈
  • 我有点困惑。您在解决方案中引用了 viewPreferencesWindow,这两者在 AppDelegate 中都不存在。我在哪里可以得到这些?当然,我可以为view 创建一个属性,但我对PreferencesWindow 迷路了。谢谢-。
【解决方案3】:

https://github.com/sindresorhus/touch-bar-simulator/blob/master/Touch%20Bar%20Simulator/main.swift

在 main.swift 中

let app = NSApplication.shared()
let delegate = AppDelegate()
app.delegate = delegate
app.run()

【讨论】:

    【解决方案4】:

    斯威夫特 4:

    // File main.swift
    autoreleasepool {
       // Even if we loading application manually we need to setup `Info.plist` key:
       // <key>NSPrincipalClass</key>
       // <string>NSApplication</string>
       // Otherwise Application will be loaded in `low resolution` mode.
       let app = Application.shared
       app.setActivationPolicy(.regular)
       app.run()
    }
    
    // File Application.swift
    public class Application: NSApplication {
    
       private lazy var mainWindowController = MainWindowController()
       private lazy var mainAppMenu = MainMenu()
    
       override init() {
          super.init()
          delegate = self
          mainMenu = mainAppMenu
       }
    
       public required init?(coder: NSCoder) {
          super.init(coder: coder) // This will newer called.
       }
    
    }
    
    extension Application: NSApplicationDelegate {
    
       public func applicationDidFinishLaunching(_ aNotification: Notification) {
          mainWindowController.showWindow(nil)
       }
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-26
      • 1970-01-01
      • 2015-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      • 2012-02-13
      • 1970-01-01
      相关资源
      最近更新 更多