Eu baixei muitos projetos e códigos de código aberto do lado do MacOS na Internet e descobri que a interface do usuário é basicamente carregada por storyBoard ou xib; usar storyBoard ou xib também apresenta armadilhas. Para obter detalhes, consulte MacOS-MacAPP usando Main. storyboard para iniciar o programa de exibição pisando em armadilhas
Mas eu quero criar a UIWindow principal no AppDelegate como o iPhone e, em seguida, definir um rootViewController personalizado, conforme mostrado abaixo:
Depois de muito pesquisar na Internet, descobri que existem poucos materiais de referência, mas o trabalho duro compensa, o blogueiro finalmente resolveu
Como carregamos a interface principal da interface do usuário por meio de código puro sem depender de storyboard/xib?
1. Exclua o arquivo Main.storyboard ou xib no projeto
Para excluir o nome base do arquivo storyboard principal no projeto Info.plist: Especifique o nome do arquivo storyboard carregado quando o aplicativo é iniciado; Nome base do arquivo nib principal: especifique o nome do arquivo xib carregado quando o aplicativo é iniciado
Após o Xcode11, além do mesmo de antes, exclua o StoryboardName de SceneDelegate no projeto Info.plist
Para obter detalhes, consulte o blog iOS-Xcode11: Exclua o Main.storyBoard padrão, a UIWindow personalizada não pode ser processada no AppDelegate, adicione o agente SceneDelegate
2. O código completamente puro precisa modificar o arquivo main.m. Para obter detalhes, consulte o arquivo iOS-main.m do blog
Confira o código do arquivo APPmain.m do macOS aqui:
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
Inicie o projeto para executar o breakpoint da seguinte forma:
Precisamos criar o aplicativo NSApplication e o proxy AppDelegate por nós mesmos, definir o proxy e iniciar e executar o aplicativo
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建应用
NSApplication *application = [NSApplication sharedApplication];
//创建代理
AppDelegate *appDelegate = [[AppDelegate alloc]init];
//配置应用代理
[application setDelegate:appDelegate];
//运行应用
[application run];
}
return NSApplicationMain(argc, argv);
}
Se você não criar um proxy de configuração de aplicativo aqui, descobrirá que, ao iniciar e executar o programa, o ponto de interrupção não inserirá o método em AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
No entanto, a interface do usuário carregada por meio do arquivo Main.storyboard ou xib entrará, então eles criam o aplicativo, definem o agente e executam o aplicativo por padrão; eles executam o método principal e, quando o APP é executado, ele primeiro cria uma instância NSAplication para carregar o arquivo storyboard/xib Crie um menu/janela personalizado no arquivo storyboard/xib. NSApplication é o proxy AppDelegate. Portanto, o método applicationDidFinishLaunching em AppDelegate será executado para executar alguma inicialização personalizada
3. Defina um NSWindow personalizado em AppDelegate
#import "AppDelegate.h"
@interface AppDelegate ()
@property (strong, nonatomic) NSWindow *window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRect frame = CGRectMake(0, 0, 300, 400);
NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSWindowStyleMaskResizable;
/*
contentRect: frame
styleMask: 窗口风格
backing: 窗口绘制缓存模式
defer: 延迟创建还是立马创建
*/
self.window = [[NSWindow alloc] initWithContentRect:frame styleMask:style backing:NSBackingStoreBuffered defer:YES];
self.window.title = @"My Window";
self.window.backgroundColor = [NSColor redColor];
//窗口居中
[self.window center];
//窗口显示
[self.window makeKeyAndOrderFront:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
@end
Aqui, ainda existe uma grande diferença entre criar uma classe NSWindow e criar um UIView no iOS. Ele não precisa apenas definir o frame (método initWithFrame:), mas também precisa definir o parâmetro styleMask para confirmar o estilo da janela (- ( instancetype)initWithContentRect:(NSRect )contentRect styleMask:(NSWindowStyleMask)backing de estilo:(NSBackingStoreType)backingStoreType defer:(BOOL)flag NS_DESIGNATED_INITIALIZER)
(1)contentRect:quadro
(2) styleMask: estilo da janela
typedef NS_OPTIONS(NSUInteger, NSWindowStyleMask) {
NSWindowStyleMaskBorderless = 0, //没有顶部titilebar边框
NSWindowStyleMaskTitled = 1 << 0, //有顶部titilebar边框
NSWindowStyleMaskClosable = 1 << 1, //带有关闭按钮
NSWindowStyleMaskMiniaturizable = 1 << 2, //带有最小化按钮
NSWindowStyleMaskResizable = 1 << 3, //恢复按钮
/* Specifies a window with textured background. Textured windows generally don't draw a top border line under the titlebar/toolbar. To get that line, use the NSUnifiedTitleAndToolbarWindowMask mask.
*/
NSWindowStyleMaskTexturedBackground API_DEPRECATED("Textured window style should no longer be used", macos(10.2, 11.0)) = 1 << 8, //带纹理背景的window,文字,标题栏没有边框线。如果需要线,要使用 NSUnifiedTitleAndToolbarWindowMask
/* Specifies a window whose titlebar and toolbar have a unified look - that is, a continuous background. Under the titlebar and toolbar a horizontal separator line will appear.
*/
NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, //标题栏和toolBar 下有统一的分割线
/* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called.
*/
NSWindowStyleMaskFullScreen API_AVAILABLE(macos(10.7)) = 1 << 14, //全屏显示
/* If set, the contentView will consume the full size of the window; it can be combined with other window style masks, but is only respected for windows with a titlebar.
Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area.
*/
NSWindowStyleMaskFullSizeContentView API_AVAILABLE(macos(10.10)) = 1 << 15, //contentView会充满整个窗口
/* 下面样式只适用于NSPanel及其子类 */
/* The following are only applicable for NSPanel (or a subclass thereof)
*/
NSWindowStyleMaskUtilityWindow = 1 << 4,
NSWindowStyleMaskDocModalWindow = 1 << 6,
NSWindowStyleMaskNonactivatingPanel = 1 << 7, // Specifies that a panel that does not activate the owning application
NSWindowStyleMaskHUDWindow API_AVAILABLE(macos(10.6)) = 1 << 13 // Specifies a heads up display panel //用于头部显示的panel
};
(3) backing: modo de cache para desenho de janela
/* Types of window backing stores.
*/
typedef NS_ENUM(NSUInteger, NSBackingStoreType) {
/* NSBackingStoreRetained and NSBackingStoreNonretained have effectively been synonyms of NSBackingStoreBuffered since OS X Mountain Lion. Please switch to the equivalent NSBackingStoreBuffered.
*/
NSBackingStoreRetained API_DEPRECATED_WITH_REPLACEMENT("NSBackingStoreBuffered", macos(10.0,10.13)) = 0, // 兼容老系统参数,基本很少用到
NSBackingStoreNonretained API_DEPRECATED_WITH_REPLACEMENT("NSBackingStoreBuffered", macos(10.0,10.13)) = 1, //不缓存直接绘制
NSBackingStoreBuffered = 2, //缓存绘制
};
(4) adiar: indica criação atrasada ou criação imediata
4. Os resultados da execução são os seguintes:
Obviamente, o exemplo acima é apenas um exemplo simples de criação de um NSWindow usando código puro como criação de código, portanto, não é muito perfeito e é criado diretamente criando manualmente a exibição de gerenciamento do NSWindow, e a escalabilidade e a capacidade de manutenção não são muito forte.recomendo
Podemos comparar o arquivo storyboard/xib para carregar a visualização de inicialização. Não há barra de menu na interface, então precisamos adicionar a barra de menu nós mesmos
storyboard
xib
Otimização e atualização
O efeito que precisamos é o seguinte: (1) Personalize a barra de menu; (2) Crie uma cena diferente Gerenciamento hierárquico de cena, como WindowController e ViewController
A relação principal entre o controlador de janela e o controlador de exibição é a seguinte:
1. Crie MainWindowController, herde de NSWindowController e configure sua janela e visualização raiz contentViewController no método init
#import "MainWindowController.h"
#import "MainViewController.h"
@interface MainWindowController ()
@property (nonatomic, strong)MainViewController *viewController;
@end
@implementation MainWindowController
- (MainViewController *)viewController {
if (!_viewController) {
_viewController = [[MainViewController alloc]init];
}
return _viewController;
}
- (instancetype)init{
if (self == [super init]) {
/*窗口控制器NSWindowController
1、实际项目中不推荐手动创建管理NSWindow,手动创建需要维护NSWindowController和NSWindow之间的双向引用关系,带来管理复杂性
2、xib加载NSWindow
【1】显示window过程:(1)NSApplication运行后加载storyboard/xib文件(2)创建window对象(3)APP启动完成,使当前window成为keyWindow
【2】关闭window过程:(1)执行NSWindow的close方法(2)最后执行orderOut方法
3、storyboard加载NSWindow
【1】执行完NSWindow的init方法,没有依次执行orderFront,makeKey方法,直接执行makeKeyAndOrderFront方法(等价同时执行orderFront和makeKey方法)
【2】window显示由NSWindowController执行showWindow方法显示
4、NSWindowController和NSWindow关系;互相引用:NSWindowController强引用NSWindow,NSWindow非强引用持有NSWindowController的指针
【1】NSWindow.h中
@property (nullable, weak) __kindof NSWindowController *windowController;
【2】NSWindowController.h中
@property (nullable, strong) NSWindow *window;
*/
NSRect frame = CGRectMake(0, 0, 600, 400);
NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSWindowStyleMaskResizable;
self.window = [[NSWindow alloc]initWithContentRect:frame styleMask:style backing:NSBackingStoreBuffered defer:YES];
self.window.title = @"My Window";
//设置window
self.window.windowController = self;
[self.window setRestorable:NO];
//设置contentViewController
self.contentViewController = self.viewController;
// [self.window.contentView addSubview:self.viewController.view];
[self.window center];
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
@end
Relacionamento NSWindowController e NSWindow; referência mútua: NSWindowController referencia fortemente NSWindow, referência não forte de NSWindow contém um ponteiro para NSWindowController
(1) NSWindow.h
@property (nullable, weak) __kindof NSWindowController *windowController;
(2) em NSWindowController.h
@property (nullable, strong) NSWindow *window;
Portanto, não é recomendável criar e gerenciar NSWindow manualmente em projetos reais. A criação manual precisa manter o relacionamento de referência bidirecional entre NSWindowController e NSWindow. É recomendável que NSWindow seja gerenciado por um NSWindowController independente.
2. Crie MainViewController, herdado de NSViewController
#import "MainViewController.h"
@interface MainViewController ()
@end
@implementation MainViewController
- (instancetype)initWithNibName:(NSNibName)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
NSRect frame = CGRectMake(0, 0, 600, 400);
NSView *view = [[NSView alloc]initWithFrame:frame];
self.view = view;
[self setSUbViews];
return self;
}
- (void)setSUbViews {
NSButton *button = [NSButton buttonWithTitle:@"Show " target:self action:@selector(showView:)];
button.frame = CGRectMake(200, 50, 100, 60);
[button setButtonType:NSPushOnPushOffButton];
button.bezelStyle = NSRoundedBezelStyle;
[self.view addSubview:button];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)showView:(NSButton *)button{
NSLog(@"点击我");
}
@end
A janela deve ter uma exibição raiz, contentView
O controlador de janela NSWindowController e NSWindow são referenciados mutuamente, e a visualização de conteúdo contentView de NSWindow é NSView; quando NSWindowController é configurado com contentViewController, a visualização de NSViewController é finalmente o contentView da janela de NSWindowController, e a janela onde a visualização está localizada é a janela do NSWindowController.
3. Em AppDelegate
#import "AppDelegate.h"
#import "MainWindowController.h"
@interface AppDelegate ()
@property (nonatomic, strong)MainWindowController *windowController;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self.windowController showWindow:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (MainWindowController *)windowController {
if (!_windowController) {
_windowController = [[MainWindowController alloc]init];
}
return _windowController;
}
@end
4. Crie um menu em main.m, crie um aplicativo e defina um proxy
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
NSMenu *mainMenu() {
NSMenu *mainMenu = [NSMenu new];
//应用和File菜单
NSMenuItem *mainAppMainItem = [[NSMenuItem alloc]initWithTitle:@"Application" action:nil keyEquivalent:@""];
NSMenuItem *mainFileMenuItem = [[NSMenuItem alloc]initWithTitle:@"File" action:nil keyEquivalent:@""];
[mainMenu addItem:mainAppMainItem];
[mainMenu addItem:mainFileMenuItem];
//应用的子菜单
NSMenu *appMenu = [NSMenu new];
mainAppMainItem.submenu = appMenu;
NSMenu *appServiceMenu = [NSMenu new];
NSApp.servicesMenu = appServiceMenu;
[appMenu addItemWithTitle:@"About" action:nil keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:@"Preferences..." action:nil keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
NSMenuItem *hideOthersItem = [[NSMenuItem alloc]initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
hideOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand + NSEventModifierFlagOption;
[appMenu addItem:hideOthersItem];
[appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@"h"];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""].submenu = appServiceMenu;
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
//File的子菜单
NSMenu *fileMenu = [[NSMenu alloc]initWithTitle:@"File"];
mainFileMenuItem.submenu = fileMenu;
[fileMenu addItemWithTitle:@"New..." action:@selector(newDocument:) keyEquivalent:@"n"];
return mainMenu;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
//创建应用
NSApplication *application = [NSApplication sharedApplication];
//创建代理
AppDelegate *appDelegate = [[AppDelegate alloc]init];
//配置应用代理
[application setDelegate:appDelegate];
//配置菜单
application.mainMenu = mainMenu();
//运行应用
[application run];
}
return NSApplicationMain(argc, argv);
}
Efeito de corrida final
Obviamente, para não personalizar a barra de menus, também podemos carregar a IU de inicialização por meio do arquivo Main.storyboard ou xib, mas precisamos excluir a janela em MainMenu.xib ou excluir todas as cenas NSWindowController e NSViewController criadas por padrão em Main.storyboard (selecionado, pressione diretamente a tecla Voltar no teclado (tecla ❎), da seguinte maneira, isso é apenas para continuar usando o menu do sistema
Também é necessário atentar para a necessidade de adicionar o seguinte código em MainWindowController.m: o mesmo efeito do método initWithWindowNibName, para que o NSWindowController correspondente possa ser encontrado carregando o arquivo nib
//通过加载xib方式
- (NSString*)windowNibName {
return @"MainWindowController";// this name tells AppKit which nib file to use
}