macos 如何为 Mac 创建 Cocoa App 首选项?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10148788/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 09:04:34  来源:igfitidea点击:

How to create Cocoa App Preferences for Mac?

objective-cmacoscocoa

提问by Cristian

I have created a simple app in Xcode for Mac, it builds and compiles fine.

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

How do I create a menu for preferences? Is there an easy way or do I have to create a new interface? How do I then get and put values into these preferences?

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

I did find one tutorial but it was for iOS, and from what I can see the 'Settings bundle' isn't available if you're developing for Mac.

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

EDIT: The following link is perfect for this: https://developer.apple.com/cocoa/cocoabindings.html

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

回答by Vervious

This is the documentation you want.More specifically How to provide a Preferences Interface.

这是您想要的文档。更具体地说,如何提供首选项接口

Anyhow, you'll have to make your own preferences menu/window as you would make any other window, and have some controls that allow modifying values that are then stored in the User Defaults dictionary, which is where user preferences are usually stored (a process which Cocoa Bindings makes extremely easy).

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

Otherwise, manually, you would probably want a reference to [NSUserDefaults standardUserDefaults]so you can -setObject:ForKey:and so forth. It would be wise to read the entire documentation linked to above (including the other sections).

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

For more information on how Cocoa Bindings can be used to interface with NSUserDefaultsto store/retrieve preferences, see the Apple docs here.

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

回答by Richard H.

For the window itself, i would recommend the RHPreferencesframework.

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

Available on GitHub. BSD Licensed.

GitHub可用。BSD 许可。

Its a simple and easy Preferences window controller with multiple tabs for your next Mac application.

它是一个简单易用的首选项窗口控制器,为您的下一个 Mac 应用程序提供多个选项卡。

It also provides:

它还提供:

  • Auto resizing between different sized tab views (With animation)
  • Custom NSToolbarItem support
  • Persistence of the last used tab
  • Support for placeholder NSToolbarItems (eg NSToolbarFlexibleSpaceItemIdentifier & NSToolbarShowFontsItemIdentifier)
  • 在不同大小的选项卡视图之间自动调整大小(带动画)
  • 自定义 NSToolbarItem 支持
  • 上次使用的选项卡的持久性
  • 支持占位符 NSToolbarItems(例如 NSToolbarFlexibleSpaceItemIdentifier 和 NSToolbarShowFontsItemIdentifier)

回答by Vlad

NSTabViewController.TabStyle.toolbar– A style that automatically adds any tabs to the window's toolbar. The tab view controller takes control of the window's toolbar and sets itself as the toolbar's delegate.

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

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

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

Keeping in mind above we can create NSWindowControllerwith NSWindow.contentViewControllerset to NSTabViewControllerto get standard macOS Preferences window.

记住上面我们可以NSWindowController使用NSWindow.contentViewControllerset to创建NSTabViewController标准的 macOS Preferences 窗口。

Here is a Preferences window made from code (Swift 4):

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

File: PreferencesPageID.swift– Keeps Preference page properties. Used in callbacks.

文件:PreferencesPageID.swift– 保留首选项页面属性。在回调中使用。

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"
      }
   }
}

File: PreferencesTabView.swift– Represents Preference page content view.

文件: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))
      }
   }
}

File: PreferenceItemViewController.swift- Controller which keeps Preference page content view. Needed mostly to fulfil macOS SDK requirements.

文件: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
   }    
}

File: PreferencesViewController.swift– Used as a NSWindow.contentViewController.

文件: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: 
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 
public class Application: NSApplication {

   private lazy var preferencesController = PreferencesWindowController()

   // Called from code or via IBAction
   private func showPreferences() {
      preferencesController.showWindow(nil)
   }

}
{ case .selected(let id): self?.window?.title = "Preferences — " + id.title } } } }
)) item.label =
//  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
.id.title item.image = ##代码##.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)) } } }

File: PreferencesWindowController.swift– Preferences window controller.

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

##代码##

Usage:

用法:

##代码##

Preferences screen

首选项屏幕

回答by Deepak Thakur

In MainMenu.xibof your project, you will see a drop down list after you click on your app name. Cntrl + Click + Drag Preferencesto your project's AppDelegate.hfile and create and IBAction method.

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

Create a class with NSWindowController (PreferenceWindowController) with xib, create a strong property of that PreferenceWindowController, alloc init it and add [self.preferenceWindowController showWindow:self];in AppDelegate.m. This will create a window of Preferencesfor your OS X app.

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

回答by Jeff Szuhay

Create a .nib and a controller (.h & .m) for each of your preference panes. Then hook them up dynamically in the AppDelegate.m of your app. I'm using it in my app that consists of a number dynamically loaded bundles such that each bundle has its own preferences.

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

You can see a really good concise example here: http://www.knowstack.com/nstoolbar-sample-code-objectivec/

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

In this example, it dynamically creates the NSToolbar and the NSToolbarItem. What you do in each window controller for each preference pane is up to you.

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

Here's the main parts of the AppDelegate.m:

这是 AppDelegate.m 的主要部分:

##代码##