How to draw your own NSTabView tabs?

前端 未结 6 1788
迷失自我
迷失自我 2021-02-09 17:05

I want to draw my own tabs for NSTabViewItems. My Tabs should look different and start in the top left corner and not centered.

How can I do this?

6条回答
  •  清歌不尽
    2021-02-09 17:23

    One of possible ways to draw tabs - is to use NSCollectionView. Here is Swift 4 example:

    Class TabViewStackController contains TabViewController preconfigured with style .unspecified and custom 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)
          }
       }
    }
    

    Class TabBarView contains CollectionView which represents tabs.

    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) {
          if let first = indexPaths.first {
             let item = tabs[first.item]
             eventHandler?(.select(item))
          }
       }
    }
    

    Class TabViewController preconfigured with style .unspecified

    class TabViewController: GenericTabViewController {
    
       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)
          }
       }
    }
    

    Rest of the classes.

    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()
       }
    }
    

    Here is how it looks like:

提交回复
热议问题