s の独自のタブを描画したいNSTabViewItem
。マイ タブの外観は異なり、中央ではなく左上隅から開始する必要があります。
これどうやってするの?
s の独自のタブを描画したいNSTabViewItem
。マイ タブの外観は異なり、中央ではなく左上隅から開始する必要があります。
これどうやってするの?
タブを描画する方法の 1 つは、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()
}
}
これがどのように見えるかです:
NSTabViewのスタイルをTablessに設定してから、NSSegmentedCellをサブクラス化してスタイルと動作をオーバーライドするNSSegmentedControlで制御することができます。これを行う方法のアイデアについては、Xcode 4スタイルのタブをエミュレートするこのプロジェクトをチェックしてください:https ://github.com/aaroncrespo/WILLTabView/ 。
NSTabViewは、Cocoaで最もカスタマイズ可能なクラスではありませんが、サブクラス化して独自の描画を行うことは可能です。タブビューアイテムのコレクションを維持する以外にスーパークラスの多くの機能を使用することはなく、描画とイベント処理を正しく機能させるために、NSViewメソッドとNSResponderメソッドをいくつか実装することになります。
最初に無料またはオープンソースのタブバーコントロールの1つを確認するのが最善かもしれません。私は過去にPSMTabBarControlを使用しましたが、独自のタブビューサブクラスを実装するよりもはるかに簡単でした(これが置き換えられました)。
私は最近、私が取り組んでいた何かのためにこれを行いました。
テーブルのタブビューを使用して終了し、別のビューで自分でタブを描画しました。タブをウィンドウの下部にあるステータス バーの一部にしたかったのです。
かなり簡単なマウス クリックをサポートする必要があることは明らかですが、キーボードのサポートも機能することを確認する必要があります。これはもう少し注意が必要です。0.5 秒後にキーボード アクセスがない場合は、タイマーを実行してタブを切り替える必要があります ( OS Xのやり方を見てください)。アクセシビリティも考慮する必要がありますが、それが機能していることに気付くかもしれません。コードでまだチェックしていません。
私はこれに非常に行き詰まりました-そして背景色でNSTabViewを投稿しました-PSMTabBarControlが古くなったため、https://github.com/dirkx/CustomizableTabView/blob/master/CustomizableTabView/CustomizableTabView.m