如何为Mac创建Cocoa应用程序偏好设置?

16

我在Xcode中为Mac创建了一个简单的应用程序,它能够成功地构建和编译。

我该如何创建偏好设置菜单?有没有简单的方法,还是必须创建新的界面?然后我如何获取和放置这些偏好设置的值?

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

编辑:下面的链接非常适合此目的:https://developer.apple.com/cocoa/cocoabindings.html


3
这是苹果官方文档关于如何在iCloud中存储用户偏好设置数据的内容。文档详细介绍了如何通过使用NSUserDefaults和NSUbiquitousKeyValueStore API来实现数据同步。其中包括创建并配置一个iCloud容器、监听iCloud状态变化以及处理同步冲突等相关操作。 - N_A
非常感谢,我会认真阅读的。 - Cristian
“EDIT”链接现在只是指向通用的Cocoa文档。我认为这个链接就是你所指的:https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaBindings/CocoaBindings.html - Stan James
如果你喜欢使用“界面构建器”创建首选项:https://www.youtube.com/watch?v=raLK95FyQso - Vlad
6个回答

16

这是你想要的文档。 更具体地说,如何提供一个首选项界面

无论如何,你都需要像制作任何其他窗口一样制作自己的首选项菜单/窗口,并拥有一些控件,允许修改值,然后将其存储在用户默认字典中,这通常是用户首选项存储的地方(Cocoa Bindings使这个过程非常容易)。

否则,手动操作,你可能需要引用[NSUserDefaults standardUserDefaults],以便你可以使用-setObject:ForKey:等方法。最好阅读以上链接文档的所有部分(包括其他部分)。

有关如何使用Cocoa Bindings与NSUserDefaults接口来存储/检索首选项的更多信息,请参见苹果文档此处


6

对于窗口本身,我建议使用RHPreferences框架。

该框架在GitHub上可用,采用BSD许可证。

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

此外,它还提供:

  • 自动调整大小以适应不同大小的选项卡视图(带有动画效果)
  • 自定义NSToolbarItem支持
  • 记忆最后使用的选项卡
  • 支持占位符NSToolbarItems(例如NSToolbarFlexibleSpaceItemIdentifier和NSToolbarShowFontsItemIdentifier)

1
如果您想要ARC支持,请尝试在RHPreferences的网络图中搜索。我找到了这个存储库,它将项目转换为ARC,并包括调整窗口大小的完成块。https://github.com/vilhalmer/RHPreferences - Samuel Clay

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

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

考虑到上述内容,我们可以创建一个NSWindowController,并将NSWindow.contentViewController设置为NSTabViewController,以获取标准的macOS首选项窗口。
下面是使用代码创建的首选项窗口(Swift 4):
文件: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"
      }
   }
}

文件: 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 - 控制器,用于维护首选项页面的内容视图。主要用于满足 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)
   }

}

Preferences screen


2

文档显示如何使用iCloud同步偏好设置,但这并不是我想要的,它还展示了如何获取和设置偏好设置。然而,它没有告诉我如何创建偏好设置窗口以及如何链接它。 - Cristian
2
我刚才链接了错误的部分,但是仍然是同一个文档。现在应该没问题了。 - N_A
那些文件有点不清楚。你需要另寻他处。 - Jeff Szuhay

1
在您的项目中的MainMenu.xib中,点击应用程序名称后,您将看到一个下拉列表。将Preferences使用Cntrl + 点击 + 拖动到您的项目的AppDelegate.h文件中,并创建一个IBAction方法。
使用NSWindowController(PreferenceWindowController)和xib创建一个类,为该PreferenceWindowController创建一个强属性,alloc init它并在AppDelegate.m中添加[self.preferenceWindowController showWindow:self];。这将为您的OS X应用程序创建一个Preferences窗口。

0
为每个偏好设置面板创建一个.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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接