Jak dodać pasek narzędzi do aplikacji macOS za pomocą SwiftUI?

11

Próbuję dodać pasek narzędzi wewnątrz paska tytułu do aplikacji macOS za pomocą SwiftUI, czegoś podobnego do tego, co pokazano poniżej.

Pasek narzędzi na pasku tytułowym w aplikacji macOS

Nie jestem w stanie znaleźć sposobu na osiągnięcie tego za pomocą SwiftUI. Obecnie mam pasek narzędzi (który ma tylko pole tekstowe) w moim widoku, ale chcę przenieść go do paska tytułu.

Mój obecny kod:

struct TestView: View {
    var body: some View {
        VStack {
            TextField("Placeholder", text: .constant("")).padding()
            Spacer()
        }
    }
}

Więc w moim przypadku muszę mieć pole tekstowe na pasku narzędzi.

Bijoy Thangaraj
źródło
Mówię o SwiftUI w docelowym systemie macOS.
Bijoy Thangaraj
Nie, modyfikator navigationBarTitle nie jest dostępny w systemie MacOS SwiftUI.
Bijoy Thangaraj
Pasek narzędzi nie jest na razie obsługiwany w SwiftUI. Jeśli naprawdę potrzebujesz (dlaczego?), Użyj jednego z AppKit, wciąż tam jest ... naprawdę.
Asperi
@Asperi Udało mi się to zrobić - zobacz odpowiedź poniżej. Paski narzędzi (lub akcesoria na pasku tytułu) są nadal szeroko stosowane w aplikacjach do systemu macOS, prawda?
Bijoy Thangaraj
@Asperi Aby użyć tego z AppKit, miałeś na myśli użycie NSViewRepresentable dla NSToolbar? Jeśli tak, wypróbowałem tę metodę, ale nie udało się. Jeśli masz takie rozwiązanie, chciałbym to sprawdzić.
Bijoy Thangaraj

Odpowiedzi:

6

Podejście 1:

Odbywa się to poprzez dodanie akcesorium paska tytułu. Udało mi się to zrobić, modyfikując plik AppDelegate.swift. Musiałem zastosować jakieś dziwne wypełnienie, aby wyglądało dobrze.

AppDelegate.swift

func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Create the titlebar accessory
        let titlebarAccessoryView = TitlebarAccessory().padding([.top, .leading, .trailing], 16.0).padding(.bottom,-8.0).edgesIgnoringSafeArea(.top)

        let accessoryHostingView = NSHostingView(rootView:titlebarAccessoryView)
        accessoryHostingView.frame.size = accessoryHostingView.fittingSize

        let titlebarAccessory = NSTitlebarAccessoryViewController()
        titlebarAccessory.view = accessoryHostingView       

        // Create the window and set the content view. 
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")

        // Add the titlebar accessory
        window.addTitlebarAccessoryViewController(titlebarAccessory)

        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)
    }

TitlebarAccessory.swift

import SwiftUI

struct TitlebarAccessory: View {
    var body: some View {

        TextField("Placeholder", text: .constant(""))

    }
}

Wynik:

Treść na pasku narzędzi macOS

Podejście 2 (metoda alternatywna):

Chodzi o to, aby zrobić część paska narzędzi za pomocą scenorysu, a resztę aplikacji za pomocą SwiftUI. Odbywa się to poprzez utworzenie nowej aplikacji z storyboardem jako interfejsem użytkownika. Następnie przejdź do scenorysu i usuń domyślny kontroler widoku i dodaj nowy NSHostingController. Połącz nowo dodany kontroler hostingu z głównym oknem, ustawiając jego relację. Dodaj pasek narzędzi do okna za pomocą konstruktora interfejsów.

Konfiguracja scenorysu

Dołącz niestandardową klasę do NSHostingControlleri załaduj do niej swój widok SwiftUI.

Przykładowy kod poniżej:

import Cocoa
import SwiftUI

class HostingController: NSHostingController<SwiftUIView> {

    @objc required dynamic init?(coder: NSCoder) {
        super.init(coder: coder, rootView: SwiftUIView())       

    }

}

Korzystanie z tego podejścia daje również możliwość dostosowania paska narzędzi.

Bijoy Thangaraj
źródło
Czy istnieje sposób, aby można go było edytować, klikając prawym przyciskiem myszy i wybierając „Dostosuj pasek narzędzi”? (Podobne do Findera, stron itp.)
sqwk
2
Przy pierwszym rozwiązaniu nie. Ale spójrz na alternatywne podejście, które dodałem do mojej odpowiedzi powyżej. W ten sposób możesz dostosować pasek narzędzi.
Bijoy Thangaraj
@BijoyThangaraj jak zmienić Statezmienne SwiftUIViewza pomocą przycisku na pasku narzędzi? Próbowałem, ale stan się nie zmienia.
Alan
@Alan, jednym ze sposobów jest wysłanie powiadomienia z WindowController, które można obserwować w HostingController. W swoim HostingController możesz przechowywać ObservableObject i modyfikować jego właściwości na podstawie otrzymanego powiadomienia. Na koniec możesz przekazać ten obserwowalny obiekt do SwiftUIView w następujący sposób:super.init(coder: coder, rootView: AnyView(SwiftUIView().environmentObject(myObservableObject)))
Bijoy Thangaraj
0

UIKit + Catalyst

https://developer.apple.com/documentation/uikit/uititlebar

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)

        if let titlebar = windowScene.titlebar {

            //toolbar
            let identifier = NSToolbar.Identifier(toolbarIdentifier)
            let toolbar = NSToolbar(identifier: identifier)
            toolbar.allowsUserCustomization = true
            toolbar.centeredItemIdentifier = NSToolbarItem.Identifier(rawValue: centerToolbarIdentifier)
            titlebar.toolbar = toolbar
            titlebar.toolbar?.delegate = self

            titlebar.titleVisibility = .hidden
            titlebar.autoHidesToolbarInFullScreen = true
        }

        window.makeKeyAndVisible()

    }
#if targetEnvironment(macCatalyst)
let toolbarIdentifier = "com.example.apple-samplecode.toolbar"
let centerToolbarIdentifier = "com.example.apple-samplecode.centerToolbar"
let addToolbarIdentifier = "com.example.apple-samplecode.add"

extension SceneDelegate: NSToolbarDelegate {

    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        if itemIdentifier == NSToolbarItem.Identifier(rawValue: toolbarIdentifier) {
            let group = NSToolbarItemGroup(itemIdentifier: NSToolbarItem.Identifier(rawValue: toolbarIdentifier), titles: ["Solver", "Resistance", "Settings"], selectionMode: .selectOne, labels: ["section1", "section2", "section3"], target: self, action: #selector(toolbarGroupSelectionChanged))

            group.setSelected(true, at: 0)

            return group
        }

        if itemIdentifier == NSToolbarItem.Identifier(rawValue: centerToolbarIdentifier) {
            let group = NSToolbarItemGroup(itemIdentifier: NSToolbarItem.Identifier(rawValue: centerToolbarIdentifier), titles: ["Solver1", "Resistance1", "Settings1"], selectionMode: .selectOne, labels: ["section1", "section2", "section3"], target: self, action: #selector(toolbarGroupSelectionChanged))

            group.setSelected(true, at: 0)

            return group
        }

        if itemIdentifier == NSToolbarItem.Identifier(rawValue: addToolbarIdentifier) {
            let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: #selector(self.add(sender:)))
            let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
            return button
        }

        return nil
    }

    @objc func toolbarGroupSelectionChanged(sender: NSToolbarItemGroup) {
        print("selection changed to index: \(sender.selectedIndex)")
    }

    @objc func add(sender: UIBarButtonItem) {
        print("add clicked")
    }

    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        [NSToolbarItem.Identifier(rawValue: toolbarIdentifier), NSToolbarItem.Identifier(rawValue: centerToolbarIdentifier), NSToolbarItem.Identifier.flexibleSpace,
            NSToolbarItem.Identifier(rawValue: addToolbarIdentifier),
            NSToolbarItem.Identifier(rawValue: addToolbarIdentifier)]
    }

    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        self.toolbarDefaultItemIdentifiers(toolbar)
    }

}
#endif
hstdt
źródło