MacOS-MacAPP carga la interfaz principal de la interfaz de usuario a través de código puro sin depender del guión gráfico/xib

Descargué una gran cantidad de códigos y proyectos de código abierto de aplicaciones del lado de MacOS en Internet, y descubrí que la interfaz de usuario se carga básicamente a través de storyBoard o xib; usar storyBoard o xib también tiene dificultades. Para obtener más información, consulte MacOS-MacAPP usando Main. guión gráfico para iniciar el programa de vista pisando trampas

Pero quiero crear la UIWindow principal en AppDelegate como el iPhone y luego configurar un rootViewController personalizado, como se muestra a continuación:

Después de buscar en Internet durante mucho tiempo, descubrí que hay muy pocos materiales de referencia, pero el trabajo duro vale la pena, el blogger finalmente lo resolvió.

¿Cómo cargamos la interfaz principal de la interfaz de usuario a través de código puro sin depender del guión gráfico/xib?

1. Elimine el archivo Main.storyboard o xib en el proyecto

Para eliminar el nombre base del archivo del guión gráfico principal en el proyecto Info.plist: especifique el nombre del archivo del guión gráfico cargado cuando se inicia la aplicación; Nombre base del archivo nib principal: especifique el nombre del archivo xib cargado cuando se inicia la aplicación

Después de Xcode11, además de lo mismo que antes, elimine StoryboardName de SceneDelegate en el proyecto Info.plist

Para obtener más información, consulte el blog iOS-Xcode11: elimine el Main.storyBoard predeterminado, la ventana de interfaz de usuario personalizada no se puede procesar en AppDelegate, agregue el agente de SceneDelegate

2. El código completamente puro necesita modificar el archivo main.m. Para obtener más información, consulte el archivo del blog iOS-main.m

Consulte el código de archivo APPmain.m de macOS aquí:

#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 el proyecto para ejecutar el punto de interrupción de la siguiente manera:

Necesitamos crear la aplicación NSApplication y el proxy AppDelegate nosotros mismos, luego configurar el proxy e iniciar y ejecutar la aplicación.

#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);
}

Si no crea un proxy de configuración de la aplicación aquí, encontrará que cuando inicie y ejecute el programa, el punto de interrupción no ingresará el método en AppDelegate en absoluto - (void) applicationDidFinishLaunching:(NSNotification *)aNotification;

Sin embargo, la interfaz de usuario cargada a través del archivo Main.storyboard o xib ingresará, por lo que crean la aplicación, configuran el agente y ejecutan la aplicación de forma predeterminada; ejecutan el método principal y, cuando se ejecuta la aplicación, primero crea una instancia de NSApplication para cargar el archivo storyboard/xib. Cree un menú/ventana personalizado en el archivo storyboard/xib. NSApplication es el proxy AppDelegate. Por lo tanto, el método applicationDidFinishLaunching en AppDelegate se ejecutará para realizar alguna inicialización personalizada.

3. Configure una NSWindow personalizada en 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

Aquí, todavía hay una gran diferencia entre crear una clase NSWindow y crear una UIView en iOS. No solo necesita configurar el marco (método initWithFrame:), sino que también debe configurar el parámetro styleMask para confirmar el estilo de la ventana (- ( tipo de instancia)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)estilo backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag NS_DESIGNATED_INITIALIZER)

(1) contentRect:marco

(2) máscara de estilo: estilo de ventana

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) respaldo: modo caché para dibujo de ventana

/* 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) aplazar: indica creación retrasada o creación inmediata

4. Los resultados de ejecución son los siguientes:

Por supuesto, lo anterior es solo un ejemplo simple de cómo crear una NSWindow utilizando código puro como creación de código, por lo que no es muy perfecto y se crea directamente creando manualmente la vista de administración de NSWindow, y la escalabilidad y la capacidad de mantenimiento no son muy fuerte recomiendo

Podemos comparar el guión gráfico/archivo xib para cargar la vista de inicio. No hay una barra de menú en la interfaz, por lo que debemos agregar la barra de menú nosotros mismos.

guión gráfico

xib

Optimización y actualización

El efecto que necesitamos es el anterior: (1) Personalizar la barra de menú; (2) Crear una escena diferente Gestión jerárquica de escenas como WindowController y ViewController

La relación principal entre el controlador de ventana y el controlador de vista es la siguiente:

1. Cree MainWindowController, herede de NSWindowController y configure su ventana y vista raíz contentViewController en el 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

Relación entre NSWindowController y NSWindow; referencia mutua: NSWindowController hace referencia fuerte a NSWindow, la referencia no fuerte de NSWindow contiene un puntero a NSWindowController

(1) NSWindow.h

@property (nullable, weak) __kindof NSWindowController *windowController;

(2) en NSWindowController.h

@property (nullable, strong) NSWindow *window;

Por lo tanto, no se recomienda crear y administrar manualmente NSWindow en proyectos reales. La creación manual debe mantener la relación de referencia bidireccional entre NSWindowController y NSWindow. Se recomienda que NSWindow sea administrado por un NSWindowController independiente.

2. Crear MainViewController, heredado 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

La ventana debe tener una vista raíz, contentView

El controlador de ventana NSWindowController y NSWindow están mutuamente referenciados, y la vista de contenido contentView de NSWindow es NSView; cuando NSWindowController está configurado con contentViewController, la vista de NSViewController es finalmente la vista de contenido de la ventana de NSWindowController, y la ventana donde se encuentra la vista es la ventana de NSWindowController.

3. En 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. Cree un menú en main.m, cree una aplicación y configure un 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);
}

Efecto final de carrera

Por supuesto, para no personalizar la barra de menú, también podemos cargar la interfaz de usuario de inicio a través del archivo Main.storyboard o xib, pero debemos eliminar la ventana en MainMenu.xib, o eliminar todas las escenas NSWindowController y NSViewController creadas por predeterminado en Main.storyboard (seleccionado, presione directamente la tecla Atrás en el teclado (tecla ❎), de la siguiente manera, esto es solo para seguir usando el menú del sistema

También es necesario prestar especial atención a la necesidad de agregar el siguiente código en MainWindowController.m: el mismo efecto que el método initWithWindowNibName, para que se pueda encontrar el NSWindowController correspondiente cargando el archivo nib

//通过加载xib方式
- (NSString*)windowNibName {
    return @"MainWindowController";// this name tells AppKit which nib file to use
}

Supongo que te gusta

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