Bagaimana cara menambahkan Tab di aplikasi berbasis non-dokumen di macOS?

Saya suka membuat aplikasi di Swift 3 dengan Xcode 8 dan itu harus mengaktifkan bilah tab Apple. Hal ini tidak berbasis dokumen. Saya mengetahui di sini, bahwa tab dapat diaktifkan jika saya ganti metode @IBAction func newWindowForTab(_ sender: Any?) misalnya di pengontrol jendela. Untuk mengujinya, saya membuat proyek baru di Xcode menggunakan storyboard, menambahkan subkelas NSWindowController dan menetapkannya di storyboard. Saya kemudian menerapkan

@IBAction override func newWindowForTab(_ sender: Any?) {}

dan bilah tab muncul saat aplikasi dibuat. Setelah dibangun kembali, saya perhatikan bahwa tombol "+" hanya muncul jika bilah tab tidak terlihat saat aplikasi ditutup sebelum pembuatan. Apakah itu bug? Bagaimana cara menambahkan tab baru?


person DaPhil    schedule 23.10.2016    source sumber
comment
dengan addTabbedWindow(jendela: dipesan :)   -  person Peter Ahlberg    schedule 05.11.2016
comment
@Dis3buted Terima kasih, tapi saya tidak bisa membuatnya berfungsi seperti yang saya inginkan. Saya pada dasarnya menginginkan perilaku seperti di Safari: ada semacam jendela default yang terbuka saat aplikasi diluncurkan dan dengan mengklik tombol +, jendela default lain ditambahkan di tab. Jadi dalam proyek baru (berbasis non-dokumen), saya membuat subkelas NSWindowController dan menugaskannya di Storyboard. Di Storyboard saya sudah memiliki View Controller Scene yang saya suka munculkan untuk setiap jendela tab baru. Tapi saya tidak tahu cara mengganti newWindowForTab dengan benar di NSWindowController saya sehingga tampilan dari Storyboard saya muncul.   -  person DaPhil    schedule 06.11.2016


Jawaban (3)


Oke ini file baru,

Delegasi Aplikasi

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Insert code here to initialize your application
}

func applicationWillTerminate(_ aNotification: Notification) {
    // Insert code here to tear down your application
}

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
    return true
}

@IBAction func newWindowForTab(_ sender: Any?){
} // without this the + button doesn't show from start

}

Pengontrol Tampilan

import Cocoa

class ViewController: NSViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
}
}

dan WindowController

import Cocoa

class WindowController: NSWindowController {

var subview: WindowController?

override func windowDidLoad() {
    super.windowDidLoad()
    // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}

@IBAction override func newWindowForTab(_ sender: Any?) {

    let story = self.storyboard
    let windowVC: WindowController = story?.instantiateInitialController() as! WindowController

    self.window?.addTabbedWindow(windowVC.window!, ordered: .above)
    self.subview = windowVC

    windowVC.window?.orderFront(self.window)
    windowVC.window?.makeKey()
}

}

Anda harus menambahkan item menu dan menghubungkannya ke FirstResponder dalam tampilan menu ke newWindowForTab: tindakan, tetapkan kunci, ucapkan cmd+t agar berfungsi, contoh ini hanya menambahkan tab dari tombol + dan opsi menu jendela berfungsi, "pindahkan tab ke yang baru jendela" dan "gabungkan semua jendela". Anda dapat menarik tab keluar dan melepas kembali, memindahkan tab secara horizontal. Sepertinya itu berhasil.

dilakukan dengan Xcode Versi 8.2 beta (8C30a)

person Peter Ahlberg    schedule 16.11.2016
comment
Mengapa Anda menyimpan windowVC yang baru dimuat ke dalam variabel subview? Hanya penasaran. - person Eimantas; 28.07.2017
comment
Mainkan dengannya, coba tanpa, buat dan hancurkan subview - person Peter Ahlberg; 28.07.2017
comment
Terima kasih untuk ini! Saya juga penasaran mengapa Anda menyimpan windowVC yang baru dimuat di variabel subview seperti yang ditanyakan @Eimantas juga. Berfungsi dengan baik untuk saya tanpanya (xcode 9.2). - person JohnV; 27.09.2018
comment
Menemukan jawabannya - tanpa variabel subview, Anda tidak dapat membuat lebih dari dua tab. Terima kasih lagi - person JohnV; 27.09.2018
comment
subview malah memastikan pengontrol jendela baru akan tetap hidup. Jika Anda tidak menyimpannya, benda tersebut akan dikumpulkan menjadi sampah. Karena windowVC bertipe sama dengan event handler, Anda cukup menyetel windowVC.window?.windowController = self dan membuat tombol + dan semua item menu tetap berfungsi. Lihat jawaban saya di bawah ini. - person ctietze; 23.01.2019
comment
Jangan lupa untuk mengatur kelas menjadi WindowController di Identity Inspector di Window. - person Philip Borges; 04.08.2021

Secara konseptual, inilah yang terjadi:

  • Cukup memanggil NSWindow.addTabbedWindow(_:ordered:) untuk menambahkan jendela ke bilah tab asli.
  • Setelah Anda memasukkan NSResponder.newWindowForTab(_:) ke dalam rantai responden di jendela utama, tombol "+" akan terlihat.
  • Saat Anda menyetel window.tabbingMode = .preferred, bilah tab akan selalu terlihat.

Namun, ada beberapa kendala saat menerapkan metode ini.

Terapkan newWindowForTab

Jadi di mana menambahkan @IBAction override func newWindowForTab(_ sender: Any?) agar Anda dapat menelepon NSWindow.addTabbedWindow(_:ordered:)?

  • Jika Anda menggunakan Storyboard, masukkan ini ke dalam subkelas NSWindowController yang Anda miliki. Itu cara paling sederhana untuk menghubungi NSWindow untuk menelepon addTabbedWindow.
  • Jika Anda menggunakan Xibs, AppDelegate akan memiliki referensi ke jendela utama. Anda bisa meletakkan caranya di sini.
  • Jika Anda menggunakan pengaturan terprogram, letakkan di mana pun Anda mengetahui instance NSWindow utama.

Jadikan Tombol "+" Berfungsi

TL;DR: Saat Anda menginisialisasi jendela baru, simpan windowController jendela di suatu tempat. Anda perlu mempertahankan referensi yang kuat agar peristiwa jendela tidak dapat ditangani (di pengontrol).

Saya menulis aplikasi contoh dengan TabManager yang menangani hal ini: https://github.com/DivineDominion/NSWindow-Tabbing

Dan postingan blog dengan detail: https://christiantietze.de/posts/2019/07/nswindow-tabbing-multiple-nswindowcontroller/

Pertimbangkan bagaimana acara dikirim. Pesan Menu Utama dikirim ke rantai responden, dan begitu pula newWindowForTab. NSApp.sendAction akan gagal untuk kejadian standar jika sumber panggilan tidak terhubung sepenuhnya -- artinya, setidaknya hingga NSWindowController Anda, bahkan mungkin hingga AppDelegate Anda.

Anda harus memastikan setiap jendela tambahan yang Anda tambahkan, pada kenyataannya, merupakan bagian dari rantai responden yang sama dengan jendela asli, atau item menu akan berhenti berfungsi (dan berwarna abu-abu/dinonaktifkan). Demikian pula, tombol "+" berhenti berfungsi saat Anda mengkliknya.

Inilah yang disebut @JohnV di komentar jawaban lain: "tanpa variabel subview, Anda tidak dapat membuat lebih dari dua tab". Itu efeknya, tapi itu bukan penjelasan sebenarnya. Anda selalu dapat membuat lebih banyak tab, tetapi hanya dari jendela/tab asli, bukan yang baru; itu karena tab lainnya tidak merespons newWindowForTab.

"Tab lainnya" sendiri hanyalah NSWindow. Namun implementasi newWindowForTab Anda berada di pengontrol. Itu naik satu tingkat.

Mengadaptasi kode oleh @Peter Ahlberg, ini akan berhasil:

class WindowController: NSWindowController {

    @IBAction override func newWindowForTab(_ sender: Any?) {

        let windowController: WindowController = self.storyboard?.instantiateInitialController() as! WindowController
        let newWindow = windowController.window

        self.window?.addTabbedWindow(newWindow, ordered: .above)

        newWindow.orderFront(self.window)
        newWindow.makeKey()

        // Store the windowController in a collection of sorts
        // to keep a strong reference and make it handle events:
        // (NSApp.delegate as? AppDelegate).addManagedWindowController(windowController)
    }
}

Saya tidak perlu menambahkan newWindowForTab ke AppDelegate agar semuanya berfungsi menggunakan Storyboard -- karena dengan cara ini pengontrol jendela tetap melakukan tugasnya dan tidak memerlukan fallback!

person ctietze    schedule 23.01.2019
comment
Jawaban yang bagus, tetapi berbagi pengontrol menjadikannya mimpi buruk untuk menangani outlet. Saya berjuang dengan pendekatan Anda, sampai saya menemukan postingan bagus yang menjelaskan cara menggunakan pengontrol terpisah untuk setiap tab. Ternyata postingan blog itu milik Anda juga! Harap perbarui jawabannya, dengan setidaknya menyertakan tautan ke postingan Anda: christiantietze.de/posts/2019/07/ - person kontiki; 20.09.2019
comment
Wah, kamu benar. Terima kasih! Telah ulangi jawabannya. - person ctietze; 20.09.2019

Dalam kasus di atas "+" akan menambahkan tab baru selalu setelah jendela pertama, jika Anda menutup jendela pertama maka itu akan dibuat ulang di bawah jendela saat ini.

ada cara untuk membuatnya berfungsi

override func newWindowForTab(_ sender: Any?) {
    let wc: NSWindowController = self.storyboard?.instantiateInitialController() as! NSWindowController
    wc.window?.windowController = self
    NSApplication.shared.mainWindow!.addTabbedWindow(wc.window!, ordered: .above)
    wc.window?.orderFront(self)
}

trik di NSApplication.shared.mainWindow! Anda selalu menambahkan tab ke jendela aktif saat ini.

dan jika kita perlu membuat jendela di akhir kita harus menggunakan trik ini

let tabbedWindows = NSApplication.shared.mainWindow!.tabbedWindows!
let lastTabIdx = tabbedWindows.count - 1
tabbedWindows[lastTabIdx].addTabbedWindow(wc.window!, ordered: .above)
person user840250    schedule 09.09.2019