29

通常我正在制作 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];
}

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

- (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。

4

4 回答 4

46

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

事实证明这很简单,为了“社区”的利益,我想我会在这里发布一个简洁的最新答案。那里有一些较旧的博客文章,我发现最有用的是来自Lap Cat Software的文章。'提示 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.xib

于 2014-02-24T12:36:38.873 回答
2

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()
于 2017-03-27T22:00:21.420 回答
1

斯威夫特 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)
   }
}
于 2017-11-30T15:04:04.953 回答
0

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

但我普遍同意没有理由避免使用 NIB 的评论。

于 2013-03-29T01:02:29.373 回答