Как нарисовать свои собственные вкладки NSTabView? - PullRequest
6 голосов
/ 06 июня 2009

Я хочу нарисовать свои собственные вкладки для NSTabViewItem с. Мои вкладки должны выглядеть иначе и начинаться в верхнем левом углу, а не по центру.

Как я могу это сделать?

Ответы [ 6 ]

4 голосов
/ 13 декабря 2011

можно установить стиль NSTabView на Tabless и затем управлять им с помощью NSSegmentedControl, который подклассов NSSegmentedCell переопределяет стиль и поведение. Для идеи, как сделать это, проверьте этот проект, который эмулирует вкладки стиля Xcode 4: https://github.com/aaroncrespo/WILLTabView/.

3 голосов
/ 07 июня 2018

Один из возможных способов рисования вкладок - это использование NSCollectionView. Вот пример Swift 4:

Класс TabViewStackController содержит TabViewController, предварительно настроенный со стилем .unspecified и пользовательский TabBarView.

class TabViewStackController: ViewController {

   private lazy var tabBarView = TabBarView().autolayoutView()
   private lazy var containerView = View().autolayoutView()
   private lazy var tabViewController = TabViewController()
   private let tabs: [String] = (0 ..< 14).map { "TabItem # \($0)" }

   override func setupUI() {
      view.addSubviews(tabBarView, containerView)
      embedChildViewController(tabViewController, container: containerView)
   }

   override func setupLayout() {
      LayoutConstraint.withFormat("|-[*]-|", forEveryViewIn: containerView, tabBarView).activate()
      LayoutConstraint.withFormat("V:|-[*]-[*]-|", tabBarView, containerView).activate()
   }

   override func setupHandlers() {
      tabBarView.eventHandler = { [weak self] in
         switch $0 {
         case .select(let item):
            self?.tabViewController.process(item: item)
         }
      }
   }

   override func setupDefaults() {
      tabBarView.tabs = tabs
      if let item = tabs.first {
         tabBarView.select(item: item)
         tabViewController.process(item: item)
      }
   }
}

Класс TabBarView содержит CollectionView, который представляет вкладки.

class TabBarView: View {

   public enum Event {
      case select(String)
   }

   public var eventHandler: ((Event) -> Void)?

   private let cellID = NSUserInterfaceItemIdentifier(rawValue: "cid.tabView")
   public var tabs: [String] = [] {
      didSet {
         collectionView.reloadData()
      }
   }

   private lazy var collectionView = TabBarCollectionView()
   private let tabBarHeight: CGFloat = 28
   private (set) lazy var scrollView = TabBarScrollView(collectionView: collectionView).autolayoutView()

   override var intrinsicContentSize: NSSize {
      let size = CGSize(width: NSView.noIntrinsicMetric, height: tabBarHeight)
      return size
   }

   override func setupHandlers() {
      collectionView.delegate = self
   }

   override func setupDataSource() {
      collectionView.dataSource = self
      collectionView.register(TabBarTabViewItem.self, forItemWithIdentifier: cellID)
   }

   override func setupUI() {
      addSubviews(scrollView)

      wantsLayer = true

      let gridLayout = NSCollectionViewGridLayout()
      gridLayout.maximumNumberOfRows = 1
      gridLayout.minimumItemSize = CGSize(width: 115, height: tabBarHeight)
      gridLayout.maximumItemSize = gridLayout.minimumItemSize
      collectionView.collectionViewLayout = gridLayout
   }

   override func setupLayout() {
      LayoutConstraint.withFormat("|[*]|", scrollView).activate()
      LayoutConstraint.withFormat("V:|[*]|", scrollView).activate()
   }
}

extension TabBarView {

   func select(item: String) {
      if let index = tabs.index(of: item) {
         let ip = IndexPath(item: index, section: 0)
         if collectionView.item(at: ip) != nil {
            collectionView.selectItems(at: [ip], scrollPosition: [])
         }
      }
   }
}

extension TabBarView: NSCollectionViewDataSource {

   func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
      return tabs.count
   }

   func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
      let tabItem = tabs[indexPath.item]
      let cell = collectionView.makeItem(withIdentifier: cellID, for: indexPath)
      if let cell = cell as? TabBarTabViewItem {
         cell.configure(title: tabItem)
      }
      return cell
   }
}

extension TabBarView: NSCollectionViewDelegate {

   func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
      if let first = indexPaths.first {
         let item = tabs[first.item]
         eventHandler?(.select(item))
      }
   }
}

Класс TabViewController, предварительно настроенный со стилем .unspecified

class TabViewController: GenericTabViewController<String> {

   override func viewDidLoad() {
      super.viewDidLoad()
      transitionOptions = []
      tabStyle = .unspecified
   }

   func process(item: String) {
      if index(of: item) != nil {
         select(itemIdentifier: item)
      } else {
         let vc = TabContentController(content: item)
         let tabItem = GenericTabViewItem(identifier: item, viewController: vc)
         addTabViewItem(tabItem)
         select(itemIdentifier: item)
      }
   }
}

Остальные занятия.

class TabBarCollectionView: CollectionView {

   override func setupUI() {
      isSelectable = true
      allowsMultipleSelection = false
      allowsEmptySelection = false
      backgroundView = View(backgroundColor: .magenta)
      backgroundColors = [.clear]
   }
}

class TabBarScrollView: ScrollView {

   override func setupUI() {
      borderType = .noBorder
      backgroundColor = .clear
      drawsBackground = false

      horizontalScrollElasticity = .none
      verticalScrollElasticity = .none

      automaticallyAdjustsContentInsets = false
      horizontalScroller = InvisibleScroller()
   }
}

// Disabling scroll view indicators.
// See: https://stackoverflow.com/questions/9364953/hide-scrollers-while-leaving-scrolling-itself-enabled-in-nsscrollview
private class InvisibleScroller: Scroller {

   override class var isCompatibleWithOverlayScrollers: Bool {
      return true
   }

   override class func scrollerWidth(for controlSize: NSControl.ControlSize, scrollerStyle: NSScroller.Style) -> CGFloat {
      return CGFloat.leastNormalMagnitude // Dimension of scroller is equal to `FLT_MIN`
   }

   override func setupUI() {
      // Below assignments not really needed, but why not.
      scrollerStyle = .overlay
      alphaValue = 0
   }
}

class TabBarTabViewItem: CollectionViewItem {

   private lazy var titleLabel = Label().autolayoutView()

   override var isSelected: Bool {
      didSet {
         if isSelected {
            titleLabel.font = Font.semibold(size: 10)
            contentView.backgroundColor = .red
         } else {
            titleLabel.font = Font.regular(size: 10.2)
            contentView.backgroundColor = .blue
         }
      }
   }

   override func setupUI() {
      view.addSubviews(titleLabel)
      view.wantsLayer = true
      titleLabel.maximumNumberOfLines = 1
   }

   override func setupDefaults() {
      isSelected = false
   }

   func configure(title: String) {
      titleLabel.text = title
      titleLabel.textColor = .white
      titleLabel.alignment = .center
   }

   override func setupLayout() {
      LayoutConstraint.withFormat("|-[*]-|", titleLabel).activate()
      LayoutConstraint.withFormat("V:|-(>=4)-[*]", titleLabel).activate()
      LayoutConstraint.centerY(titleLabel).activate()
   }
}

class TabContentController: ViewController {

   let content: String
   private lazy var titleLabel = Label().autolayoutView()

   init(content: String) {
      self.content = content
      super.init()
   }

   required init?(coder: NSCoder) {
      fatalError()
   }

   override func setupUI() {
      contentView.addSubview(titleLabel)
      titleLabel.text = content
      contentView.backgroundColor = .green
   }

   override func setupLayout() {
      LayoutConstraint.centerXY(titleLabel).activate()
   }
}

Вот как это выглядит:

Result

3 голосов
/ 06 июня 2009

Я недавно сделал это для чего-то, над чем я работал.

Я прекратил использовать представление вкладок таблиц, а затем сам рисовал вкладки в другом представлении. Я хотел, чтобы мои вкладки были частью строки состояния в нижней части окна.

Очевидно, что вам нужно поддерживать щелчки мышью, что довольно легко, но вы должны убедиться, что ваша клавиатура тоже работает, и это немного сложнее: вам нужно будет запустить таймеры для переключения вкладки после отсутствия доступа к клавиатуре после половины второй (посмотрите, как это делает OS X). Доступность - это еще одна вещь, о которой вы должны подумать, но вы можете найти, что она просто работает - я еще не проверял это в своем коде.

3 голосов
/ 06 июня 2009

NSTabView - не самый настраиваемый класс в Какао, но его можно разделить на подклассы и сделать свой собственный рисунок. Вы не будете использовать много функциональных возможностей суперкласса, кроме поддержки набора элементов представления вкладок, и в итоге вы будете реализовывать несколько методов NSView и NSResponder для правильной работы чертежа и обработки событий.

Лучше всего сначала взглянуть на один из элементов управления панели вкладок со свободным или открытым исходным кодом, в прошлом я использовал PSMTabBarControl , и это было намного проще, чем реализовать мой собственный подкласс вида вкладки это то, что он заменял).

1 голос
/ 11 ноября 2011

Я очень застрял на этом - и опубликовал NSTabView с цветом фона - так как PSMTabBarControl теперь устарел и также опубликован https://github.com/dirkx/CustomizableTabView/blob/master/CustomizableTabView/CustomizableTabView.m

0 голосов
/ 10 января 2015

Очень просто использовать отдельный NSSegmentedCell для управления выбором вкладки в NSTabView. Все, что вам нужно, - это переменная экземпляра, с которой они оба могут связываться, либо в владельце файла, либо в любом другом классе контроллера, который появляется в вашем файле пера. Просто поместите что-то вроде этого в объявлении интерфейса класса:

@property NSInteger selectedTabIndex;

Затем в инспекторе привязок IB свяжите выбранный индекс как NSTabView, так и NSSegmentedCell с тем же свойством selectedTabIndex.

Это все, что вам нужно сделать! Вам не нужно инициализировать свойство, если вы не хотите, чтобы выбранный по умолчанию индекс вкладки отличался от нуля. Вы можете оставить вкладки или создать таблицу NSTabView, она будет работать в любом случае. Элементы управления будут синхронизированы независимо от того, какой элемент управления изменит выбор.

...