MacOS-MacAPP carrega a interface principal da interface do usuário por meio de código puro sem depender de storyboard/xib

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
}

Acho que você gosta

Origin blog.csdn.net/MinggeQingchun/article/details/116307236
Recomendado
Clasificación