【问题标题】:How to create Cocoa App Preferences for Mac?如何为 Mac 创建 Cocoa App 首选项?
【发布时间】:2012-04-13 21:52:13
【问题描述】:

我在 Xcode for Mac 中创建了一个简单的应用程序,它可以很好地构建和编译。

如何创建首选项菜单?有没有简单的方法或者我必须创建一个新界面?然后我如何获取这些偏好并将值放入这些偏好中?

我确实找到了一个教程,但它是针对 iOS 的,据我所知,如果您正在为 Mac 开发,则“设置包”不可用。

编辑:以下链接非常适合:https://developer.apple.com/cocoa/cocoabindings.html

【问题讨论】:

标签: objective-c macos cocoa


【解决方案1】:

This is the documentation you want. 更具体地说是How to provide a Preferences Interface

无论如何,您必须像制作任何其他窗口一样制作您自己的首选项菜单/窗口,并且有一些控件允许修改然后存储在用户默认值字典中的值,这是用户首选项通常所在的位置存储(Cocoa Bindings 使这个过程变得非常简单)。

否则,您可能需要手动引用[NSUserDefaults standardUserDefaults],以便可以-setObject:ForKey: 等等。阅读上面链接的整个文档(包括其他部分)是明智的。

有关如何使用 Cocoa Bindings 与 NSUserDefaults 交互以存储/检索首选项的更多信息,请参阅 Apple 文档 here

【讨论】:

    【解决方案2】:

    对于窗口本身,我推荐RHPreferences 框架。

    GitHub 上可用。 BSD 许可。

    它是一个简单易用的 Preferences 窗口控制器,带有多个选项卡,适用于您的下一个 Mac 应用程序。

    它还提供:

    • 在不同大小的标签视图之间自动调整大小(带动画)
    • 自定义 NSToolbarItem 支持
    • 最后使用的标签的持久性
    • 支持占位符 NSToolbarItems(例如 NSToolbarFlexibleSpaceItemIdentifier 和 NSToolbarShowFontsItemIdentifier)

    【讨论】:

    • 如果您想要 ARC 支持,请尝试搜索 RHPreferences 的网络图。我发现这个 repo 将项目转换为 ARC,还包括用于调整窗口大小的完成块。 github.com/vilhalmer/RHPreferences
    【解决方案3】:

    NSTabViewController.TabStyle.toolbar – 一种自动将任何选项卡添加到窗口工具栏的样式。选项卡视图控制器控制窗口的工具栏并将自己设置为工具栏的委托。

    https://developer.apple.com/documentation/appkit/nstabviewcontroller/tabstyle

    请记住,我们可以创建 NSWindowController 并将 NSWindow.contentViewController 设置为 NSTabViewController 以获取标准的 macOS 首选项窗口。

    这是一个由代码 (Swift 4) 制成的首选项窗口:

    文件:PreferencesPageID.swift - 保留 Preference 页面属性。在回调中使用。

    enum PreferencesPageID: Int, CaseIterable {
    
       case generic, misc
    
       var image: NSImage? {
          switch self {
          case .generic:
             return NSImage(named: NSImage.folderSmartName)
          case .misc:
             return NSImage(named: NSImage.networkName)
          }
       }
    
       var title: String {
          switch self {
          case .generic:
             return "Some"
          case .misc:
             return "Other"
          }
       }
    }
    

    文件:PreferencesTabView.swift - 表示首选项页面内容视图。

    class PreferencesTabView: View {
    
       let id: PreferencesPageID
    
       init(id: PreferencesPageID) {
          self.id = id
          super.init()
       }
    
       required init?(coder decoder: NSCoder) {
          fatalError()
       }
    
       override func setupUI() {
          switch id {
          case .generic:
             backgroundColor = .red
             setIntrinsicContentSize(CGSize(width: 400, height: 200))
          case .misc:
             backgroundColor = .blue
             setIntrinsicContentSize(CGSize(width: 400, height: 300))
          }
       }
    }
    

    文件:PreferenceItemViewController.swift - 保持 Preference 页面内容视图的控制器。主要用于满足 macOS SDK 要求。

    class PreferenceItemViewController: ViewController {
    
       private let contentView: PreferencesTabView
    
       override func loadView() {
          view = contentView
       }
    
       init(view: PreferencesTabView) {
          contentView = view
          super.init(nibName: nil, bundle: nil)
       }
    
       required init?(coder: NSCoder) {
          fatalError()
       }    
    
       override func viewDidLayout() {
           super.viewDidLayout()
           preferredContentSize = view.intrinsicContentSize
       }    
    }
    

    文件:PreferencesViewController.swift - 用作NSWindow.contentViewController

    class PreferencesViewController: TabViewController {
    
       enum Event {
          case selected(PreferencesPageID)
       }
    
       var eventHandler: ((Event) -> Void)?
    
    
       override func setupUI() {
          tabStyle = .toolbar // This will "turn" View Controller to standard Preferences window.
          transitionOptions = .allowUserInteraction
          canPropagateSelectedChildViewControllerTitle = false
          let views = [PreferencesTabView(id: .generic), PreferencesTabView(id: .misc)]
          views.forEach {
             let item = NSTabViewItem(viewController: PreferenceItemViewController(view: $0))
             item.label = $0.id.title
             item.image = $0.id.image
             addTabViewItem(item)
          }
       }
    
       override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
          super.tabView(tabView, didSelect: tabViewItem)
          if let view = tabViewItem?.viewController?.view as? PreferencesTabView {
             eventHandler?(.selected(view.id))
          }
       }
    
    }
    

    文件:PreferencesWindowController.swift - 首选项窗口控制器。

    private class PreferencesWindowController: NSWindowController {
    
       private(set) lazy var viewController = PreferencesViewController()
    
       init() {
          let rect = CGRect(x: 400, y: 200, width: 400, height: 300)
          let window = NSWindow(contentRect: rect, styleMask: [.titled, .closable], backing: .buffered, defer: true)
          super.init(window: window)
    
          setupHandlers()
    
          let frameSize = window.contentRect(forFrameRect: window.frame).size
          viewController.view.setFrameSize(frameSize)
          window.contentViewController = viewController
       }
    
       required init?(coder: NSCoder) {
          fatalError()
       }
    
       // MARK: - Private
    
       private func setupHandlers() {
          viewController.eventHandler = { [weak self] in
             switch $0 {
             case .selected(let id):
                self?.window?.title = "Preferences — " + id.title
             }
          }
       }
    
    }
    

    用法:

    public class Application: NSApplication {
    
       private lazy var preferencesController = PreferencesWindowController()
    
       // Called from code or via IBAction
       private func showPreferences() {
          preferencesController.showWindow(nil)
       }
    
    }
    

    【讨论】:

      【解决方案4】:

      【讨论】:

      • 文档显示了如何将首选项与 icloud 同步,这不是我想要的,它显示了如何获取和设置首选项。但是它没有告诉我如何创建首选项窗口以及如何链接它?
      • 我只是链接了错误的部分,仍然是同一个文档。现在应该好多了。
      • 那些文档有些粗略。你需要去别处看看。
      【解决方案5】:

      在您项目的MainMenu.xib 中,单击您的应用名称后,您将看到一个下拉列表。 Cntrl + 单击 + 将 Preferences 拖动到项目的 AppDelegate.h 文件并创建和 IBAction 方法。

      使用xib创建一个带有NSWindowController(PreferenceWindowController)的类,创建该PreferenceWindowController的强属性,分配初始化它并在AppDelegate.m中添加[self.preferenceWindowController showWindow:self];。这将为您的 OS X 应用程序创建一个 Preferences 窗口。

      【讨论】:

        【解决方案6】:

        为您的每个首选项窗格创建一个 .nib 和一个控制器(.h 和 .m)。 然后将它们动态连接到您应用的 AppDelegate.m 中。 我在我的应用程序中使用它,它由许多动态加载的包组成,这样每个包都有自己的偏好。

        您可以在这里看到一个非常简洁的示例: http://www.knowstack.com/nstoolbar-sample-code-objectivec/

        在这个例子中,它动态地创建了 NSToolbar 和 NSToolbarItem。 您在每个窗口控制器中为每个首选项窗格执行的操作取决于您。

        这是 AppDelegate.m 的主要部分:

        //  AppDelegate.m
        //  CocoaToolBars
        //  Created by Debasis Das on 4/30/15.
        //  Copyright (c) 2015 Knowstack. All rights reserved.
        
        #import "AppDelegate.h"
        @interface AppDelegate ()
        @property (weak) IBOutlet NSWindow *window;
        @end
        
        @implementation AppDelegate
        
        - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
          // Insert code here to initialize your application
          _toolbarTabsArray = [self toolbarItems];
          _toolbarTabsIdentifierArray = [NSMutableArray new];
        
          for (NSDictionary *dict in _toolbarTabsArray){
            [_toolbarTabsIdentifierArray addObject:dict[@"identifier"]];
          }
          _toolbar = [[NSToolbar alloc]    initWithIdentifier:@"ScreenNameToolbarIdentifier"];
          _toolbar.allowsUserCustomization = YES;
          _toolbar.delegate = self;
          self.window.toolbar = _toolbar;
        }  
        
        -(NSArray *)toolbarItems {
          NSArray *toolbarItemsArray = [NSArray arrayWithObjects:
                                       [NSDictionary    dictionaryWithObjectsAndKeys:@"Find Departments",@"title",@"Department-50",@"icon",@"DepartmentViewController",@"class",@"DepartmentViewController",@"identifier", nil],
                                      [NSDictionary dictionaryWithObjectsAndKeys:@"Find Accounts",@"title",@"Business-50",@"icon",@"AccountViewController",@"class",@"AccountViewController",@"identifier", nil],
                                      [NSDictionary dictionaryWithObjectsAndKeys:@"Find Employees",@"title",@"Edit User-50",@"icon",@"EmployeeViewController",@"class",@"EmployeeViewController",@"identifier", nil],
                                      nil];
          return  toolbarItemsArray;
        }
        
        - (void)applicationWillTerminate:(NSNotification *)aNotification {
          // Insert code here to tear down your application
        }
        
        - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
           itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
          NSDictionary *itemInfo = nil;
        
          for (NSDictionary *dict in _toolbarTabsArray) {
            if([dict[@"identifier"] isEqualToString:itemIdentifier]) {
              itemInfo = dict;
              break;
            }
          }
        
          NSAssert(itemInfo, @"Could not find preferences item: %@", itemIdentifier);
        
          NSImage *icon = [NSImage imageNamed:itemInfo[@"icon"]];
          if(!icon) {
            icon = [NSImage imageNamed:NSImageNamePreferencesGeneral];
          }
          NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
          item.label = itemInfo[@"title"];
          item.image = icon;
          item.target = self;
          item.action = @selector(viewSelected:);
          return item;
        }
        
        -(void)viewSelected:(id)sender {
          NSToolbarItem *item = sender;
          [self loadViewWithIdentifier:item.itemIdentifier withAnimation:YES];
        }
        
        -(void)loadViewWithIdentifier:(NSString *)viewTabIdentifier
                              withAnimation:(BOOL)shouldAnimate {
          NSLog(@"viewTabIdentifier %@",viewTabIdentifier);
        
          if ([_currentView isEqualToString:viewTabIdentifier]) {
            return;
          } else {
            _currentView = viewTabIdentifier;
          }
          //Loop through the view array and find out the class to load
        
          NSDictionary *viewInfoDict = nil;
          for (NSDictionary *dict in _toolbarTabsArray) {
            if ([dict[@"identifier"] isEqualToString:viewTabIdentifier]) {
              viewInfoDict = dict;
              break;
            }
          }
          NSString *class = viewInfoDict[@"class"];
          if(NSClassFromString(class)) {
            _currentViewController = [[NSClassFromString(class) alloc] init];
            NSView *newView = _currentViewController.view;
        
            NSRect windowRect = self.window.frame;
            NSRect currentViewRect = newView.frame;
        
            windowRect.origin.y = windowRect.origin.y + (windowRect.size.height - currentViewRect.size.height);
            windowRect.size.height = currentViewRect.size.height;
            windowRect.size.width = currentViewRect.size.width;
        
            self.window.title = viewInfoDict[@"title"];
            [self.window setContentView:newView];
            [self.window setFrame:windowRect display:YES animate:shouldAnimate];      
          } else {
            NSAssert(false, @"Couldn't load %@", class);
          }
        }
        
        - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
          NSLog(@"%s %@",__func__,_toolbarTabsIdentifierArray);
          return _toolbarTabsIdentifierArray;
        }
        
        - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
          NSLog(@"%s",__func__);
          return [self toolbarDefaultItemIdentifiers:toolbar];
        }
        
        - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar {
          NSLog(@"%s",__func__);
        return [self toolbarDefaultItemIdentifiers:toolbar];
        }
        
        - (void)toolbarWillAddItem:(NSNotification *)notification {
          NSLog(@"%s",__func__);
        }
        
        - (void)toolbarDidRemoveItem:(NSNotification *)notification {
          NSLog(@"%s",__func__);
        }
        
        @end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-09-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-05
          • 1970-01-01
          相关资源
          最近更新 更多