Alternatives UI System

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Alternatives UI System

    Ich möchte hier ein Projekt vorstellen, welches sich aktuell noch in der Technology Preview Phase befindet. Ich bin also noch dabei auszuprobieren was möglich ist und wie es funktionieren könnte.

    Es handelt sich um ein UI System ähnlich SwiftUI, welches intern AppKit verwendet, also aktuell nur mit macOS als Zielplattform.

    Was waren/sind die Hintergründe für dieses Projekt?

    Ich wollte/möchte mich mehr und intensiver mit Swift beschäftigen und mal wieder etwas mit macOS machen, da ich zuletzt eher mit iOS unterwegs war. Weiterhin ist die direkte Verwendung von AppKit mit Auto Layout Constraints über den Interface Builder oder per Code sehr aufwändig. Wer schon mal SwiftUI zur Erstellung eines UIs ausprobiert hat, hat sicherlich schnell Gefallen daran gefunden, wie einfach sich ein UI auch per reinem Code realisieren lassen kann.

    Wie sieht das UI System aktuell als UI und im Code aus?

    Da AppKit verwendet wird, sieht das resultierende UI natürlich wie ein natives macOS UI aus. Die Unterschiede zeigen sich also im Code und der Programmierung. D.h. das UI wird nicht mit dem Interface Builder, sondern ähnlich wie bei SwiftUI komplett im Code erstellt. Es gibt z.B. vertikale und horizontale Stacks, um Elemente in vertikalen oder horizontalen Layouts zu gruppieren und auszurichten. Weiterhin gibt es z.B. einen Grid, welcher es erlaubt Elemente in einem Layout aus Spalten und Zeilen zu positionieren. Die Positionierung und ggf. Größenänderung einzelner Elemente wird dabei komplett vom UI System vorgenommen. Somit entfällt das Hantieren mit Auto Layout Constraints.

    Die Verknüpfung von UI Elementen und Model geschieht, ähnlich wie bei SwiftUI, über Bindings. D.h. nach der Verwendung eines Property Wrappers im Model lässt sich ein Property per Binding mit einem UI Element verbinden. Somit werden Änderungen am Property direkt im verknüpftem UI Element aktualisiert bzw. Änderungen im UI Element direkt im Property geändert. Aktuell lässt sich der "Inhalt/Wert" eines UI Elements, sowie der Zustand "enabled" per Binding mit Properties verbinden.

    Ein einfaches TextField mit einem Placeholder für den Vornamen in einem Model sieht dann z.B. so aus:

    TextField(placeholderString: "Vorname", binding: model.$firstname)

    Benötigt man z.B. ein TextField zur Eingabe einer PLZ mit der Begrenzung auf max. 5 Stellen für PLZ in Deutschland und einer fixen Breite von 50 Pixeln, dann könnte dies z.B. so aussehen:

    TextField(placeholderString: "PLZ", binding: model.$zip).width(50).formatter(IntegerFormatter(maximumLength: 5))

    In dieses TextField lassen sich dann nur max. 5 Ziffern eingeben. Die Eingabe von anderen Zeichen ausser Ziffern bzw. mehr als 5 Zeichen/Ziffern wird durch den verwendeten Formatter blockiert.

    Ein einfaches Grid Layout mit mehreren Elementen (Label, TextFields, PopUpButton) könnte z.B. so aussehen:

    Quellcode

    1. Grid(columns: [GridColumn(alignment: .trailing), GridColumn(alignment: .leading, size: .flexible)], verticalSpacing: 5, horizontalSpacing: 2,
    2. content: Label("Anrede:"), PopUpButton(items: model.salutations, binding: model.$salutation).width(80),
    3. Label("Vorname:"), TextField(placeholderString: "Vorname", binding: model.$firstname),
    4. Label("Nachname:"), TextField(placeholderString: "Nachname", binding: model.$lastname),
    5. Label("PLZ/Ort:"), HStack(spacing: 5,
    6. content: TextField(placeholderString: "PLZ", binding: model.$zip).width(50).formatter(IntegerFormatter(maximumLength: 5)),
    7. TextField(placeholderString: "Ort", binding: model.$city)
    8. )
    9. )

    Das resultierende Fenster mit dem o.a. Grid Layout, sowie 2 weiteren Buttons, würde dann wie eine "normale" AppKit App aussehen:
    Screenshot.png

    Das Model ist, bis auf den verwendeten Property Wrapper für die Ermöglichung des Bindings mit den UI Elementen, dann wie eine ganz normale Klasse aufgebaut:

    Quellcode

    1. class TestModel {
    2. @Binding var salutation: String? = nil
    3. @Binding var firstname: String? = nil
    4. @Binding var lastname: String? = nil
    5. @Binding var zip: String? = nil
    6. @Binding var city: String? = nil
    7. let salutations: [(String?,String)] = [(nil, ""),("mr", "Herr"), ("mrs", "Frau"), ("company", "Firma")]
    8. init(saluation: String? = nil, firstname: String? = nil, lastname: String? = nil, zip: String? = nil, city: String? = nil) {
    9. self.salutation = salutation
    10. self.firstname = firstname
    11. self.lastname = lastname
    12. self.zip = zip
    13. self.city = city
    14. }
    15. }
    Alles anzeigen

    Der komplette Code für das o.a. Fenster würde so aussehen

    Quellcode

    1. class TestViewController: ViewController {
    2. let model = TestModel(firstname: "Hans", lastname: "Testmann", zip: "04711", city: "Teststadt")
    3. override func buildContent() -> Element? {
    4. self.title = "Formular Test"
    5. self.autoresizingMask = [.flexibleWidth]
    6. let grid = Grid(columns: [GridColumn(alignment: .trailing), GridColumn(alignment: .leading, size: .flexible)], verticalSpacing: 5, horizontalSpacing: 2,
    7. content: Label("Anrede:"), PopUpButton(items: model.salutations, binding: model.$salutation).width(80),
    8. Label("Vorname:"), TextField(placeholderString: "Vorname", binding: model.$firstname),
    9. Label("Nachname:"), TextField(placeholderString: "Nachname", binding: model.$lastname),
    10. Label("PLZ/Ort:"), HStack(spacing: 5,
    11. content: TextField(placeholderString: "PLZ", binding: model.$zip).width(50).formatter(IntegerFormatter(maximumLength: 5)),
    12. TextField(placeholderString: "Ort", binding: model.$city)
    13. )
    14. )
    15. let buttonsStack = HStack(layout: .equal, spacing: 20,
    16. content: Button("Test").action( { print("Button test!") } ),
    17. Button("OK").action({ [weak self] in
    18. guard let model = self?.model else { return }
    19. print("model.salutation:\(String(describing: model.salutation)), model.firstname:\(String(describing: model.firstname)), lastname:\(String(describing: model.lastname)), zip:\(String(describing: model.zip)), test:\(String(describing: model.city))")
    20. })
    21. )
    22. let vStack = VStack(spacing: 10,
    23. content: grid,
    24. HStack(content: Spacer(), buttonsStack, Spacer())
    25. ).padding(Insets(10))
    26. return vStack
    27. }
    28. }
    Alles anzeigen

    Warum stelle ich das Projekt vor?

    Ich hätte gerne Feedback, ob jemand ein "alternatives" UI System unter macOS überhaupt verwenden würde. Oder würdet ihr lieber bei AppKit mit Auto Layout Constraints bleiben bzw. gleich SwiftUI für eure macOS Projekte verwenden?

    Ich hatte auch darüber nachgedacht, dass man das UI System auf weitere Plattformen wie Linux/Gtk oder Windows portieren könnte. Somit könnten sich Cross Plattform App mit Swift sicherlich leichter erstellen lassen.
  • Hej, lange nichts gehört :)

    Interessantes Projekt, magst Du kurz erläutern, warum SwiftUI für Dich keine Alternative ist? Ich muss gestehen, es bisher nur (rudimentär) unter iOS eingesetzt zu haben, aber es wäre für mich der präferierte Kandidat, um das umständliche Constraint-Gebastle zu vermeiden...

    Allerdings muss ich gestehen, dass ich externe Abhängigkeiten scheue wie der Teufel das Weihwasser: Zu schnell findet man sich in Sackgassen wieder. Für mich (!) müsste es also nahezu zwingende Gründe geben, um ein 3rd party framework zu nutzen.

    Nicht jeder ist so restriktiv, auf jeden Fall viel Erfolg für Dein Projekt!
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hi Mattes,

    nachdem ich etwas in SwiftUI unter iOS reinschauen konnte, wollte ich mich mal an einer macOS App mit SwiftUI probieren. Leider wurden einige SwiftUI Elemente wie OutlineView oder dynamische PopUpButtons für macOS, zu dem Zeitpunkt, wohl noch nicht so richtig unterstützt, so dass man dann auf AppKit zurückgreifen musste. Die Verwendung von AppKit unter SwiftUI ist zwar kein großes Problem, aber man musste sich dann doch erst mal um die benötigten UI Elemente kümmern anstatt diese einfach zu verwenden.

    Weiterhin gefällt es mir nicht so, dass man Alerts vorab komplett im SwiftUI Code angeben muss und diese dann über das Setzen eines Property angezeigt werden. Dies mag zwar ggf. auch seine Vorteile haben, aber entsprechender Code in einem ViewController, um z.B. nach einem Button Klick einen Alert anzuzeigen finde ich deutlich einfach zu lesen und zu verstehen.

    Die exakte Anzeige von einigen Fehlern, also die Stelle eines Syntax Fehlers etc. beim SwiftUI Code, in Xcode war teilweise ein Graus. Wenn es dem Compiler "zu schwierig" wurde den Fehler im Code zu Lokalisieren, gab es dann ggf. nur noch die Fehlermeldung "Syntax Fehler". Dann musste man so lange mit Undo zurück, bis der Compiler den Code wieder übersetzten konnte und dann Schritt für Schritt die Änderungen wieder vornehmen und schauen, bei welchem Schritt der Compiler dann nicht mehr wollte. Also nicht gerade Entwickler freundlich. Evtl. sind diese Probleme aber mittlerweile ausgemerzt und der Compiler kann die exakte Stelle eines evtl. Fehlers im Code anzeigen.

    Was mir an SwiftUI jedoch sehr gut gefällt ist, dass sich das Layout für einen View sehr schnell und einfach per Code aufbauen lässt und dass man die Properties in einem Model sehr einfach mit den UI Elementen verbinden kann. Das Hantieren mit Layout Constraints im InterfaceBuilder entfällt damit und dies konnte schon manchmal sehr nervig und zeitaufwändig werden. Einiges an Code, um Daten zwischen Model und UI Auszutauschen entfällt damit auch.

    Die Idee war daher eine einfache Gestaltung von Layouts per Code, wie bei SwiftUI, unter Verwendung von AppKit zu ermöglichen, sowie Model und UI zu Verbinden. Durch das Projekt konnte ich dann auch mal tiefer in die Programmierung mit Swift einsteigen und mich z.B. mit Property Wrappern und Generic beschäftigen. Einige Dinge bei Swift finde ich allerdings immer noch eher kompliziert oder zu kryptisch. :S

    Da man zum Programmieren und Testen Praxisbeispiele mit diversen UI Elementen benötigt, hatte ich die Einstellung von meiner App Tables mit dem neuen UI System umgesetzt. Dies war zum Programmieren und Testen des UI Systems gut geeignet und hat in der Umsetzung dann auch sehr gut funktioniert. Was seinerzeit im Original mit Objective-C, Nib Files und Bindings im InterfaceBuilder sicherlich mehrere Stunden/Tage Zeit und fast 600 Zeilen Code benötigte, könnte man damit in gerade mal 200 Zeilen Code und deutlich kürzerer Zeit realisieren.

    Ok, hierbei sind auch ca. 200 Zeilen Code für eine PrefsViewController Basis Klasse entstanden, welche die Toolbar erzeugt und die Umschaltung der View inkl. Größenänderung des Fensters übernimmt. Ein Entwickler müsste zur Erstellung der Einstellungen bei einer App dann nur noch die einzelnen Seiten inkl. Icon und Titel definieren.

    Ich hatte dann auch noch einen Property Wrapper erstellt, welcher nicht nur das Binding mit den UI Elementen ermöglicht, sondern die Werte auch gleich mit den User Defaults synchronisiert. So etwas lässt sich natürlich auch zur Verwendung mit SwiftUI realisieren.

    Bevor ich mich jetzt an die Unterstützung weiterer UI Elemente begebe, wollte ich erst mal das Interesse an so einem UI System abchecken. Ziel wäre dann letztlich ein Open Source Swift Package, so dass es kein 3rd party framework mit Sackgasse wird, welches irgendwann ggf. nicht mehr funktioniert oder nicht mehr weiterentwickelt wird.
  • Hallo MCDan,
    ebenfalls lange nichts mehr voneinander gehört...

    Irgendwie empfinde ich den ganzen Ansatz ein bisschen als Rückschritt. Das Besondere an NeXTSTEP bzw. Cocoa/Xcode ist ja der Interface-Builder. Um genau diese Art Code loszuwerden um Fenster etc. zu erzeugen. Davor gab es ja diverse C++ Toolkits, Foundation-Classes, RealBASIC und wie das alles hieß, wo man eben keinen Interface-Builder hatte. Ich hatte in den 90ern vieles mit Xt und den Motif-Libs auf HP-UX-Maschinen gemacht.

    Die neue Idee von NeXTSTEP war da dann mit macOS 1 eine saubere Trennung zwischen Layout und Inhalt. Layout ist grafisch und sollte daher auch grafisch bearbeitet werden. Inhalt kommt aus dem Code (MVC-Muster).

    Als Zwischenebene gab es die NIB-Files und dann die XIB. Letztere sind ja quasi auch eine Programmiersprache zur Schachtelung von visuellen Elementen - nur dass man die nicht von Hand tippt sondern mit NIB/XIB-Editor bearbeitet. Und der "Compiler" ist der XIB>NIB-Translator in Xcode und der "Prozessor" der NIB-Loader von AppKit.

    Aber um SWIFT und SwiftUI UI habe ich bisher einen großen Bogen gemacht. Ich habe irgendwie bis heute nicht verstanden wo da die praktischen Vorteile in der täglichen Arbeit sind... Und nur weil es von Apple kommt mache ist schon lange nicht mehr jede angebliche Verbesserung mit. Daher kann es sein, dass ich das eigentliche Problem nicht (er)kenne und auf einem viel zu theoretischen Level versuche mitzudiskutieren.

    Zur Frage ob ich das statt AutoLayout Constraints verwenden würde: ich verwende die bisher in keinem einzigen Projekt... Wobei ich Apps a) nur für den Eigenbedarf mache und b) man auch ohne Constraints gute Layouts hinbekommt. Und Layouts die sich nach Fenster/Bildschirmgröße anpassen sind nicht unbedingt benutzerfreundlich(er). Ich schreibe aber auch keine Apps für iPhone oder iPad sonder nur macOS wo das vermutlich eine größere Bedeutung hat.

    Soweit mal ein paar Gedanken zu dem Thema.

    -- hns
  • Hi hns,

    ich fand den Interface Builder auch klasse und dieser war damals auch ein Grund für mich vom Amiga zum Mac und nicht zu Windows oder Linux zu wechseln. Leider hat Apple mit Einführung von Auto Layout die Gestaltung von UIs im Interface Builder eher erschwert also diese zu vereinfachen. Für die Arbeit im Team ist der Interface Builder auch eher hinderlich, da sich Änderungen in NIBs/XIBx oder gar in einem Storyboard nur schwer mergen lassen.

    Wenn man sich dann mal etwas Mühe macht und sich die Erstellung von UIs per Code anzuschauen, stellt man schnell fest, dass die Erstellung per Interface Builder, zumindest mit Auto Layout, eher umständlich ist.

    Bei der Erstellung von UIs per Code spielt natürlich das verwendete Framework und dessen Möglichkeiten eine wichtige Rolle. AppKit UIs lassen sich ja schon immer auch per Code, also ohne Interface Builder, erstellen. Allerdings ist dies ohne weitere Unterstützung eines Frameworks doch eher aufwändig, weshalb bei diesem Vergleich der Interface Builder noch punkten kann.

    Schaut man sich mal die Erstellung von UIs z.B. mit SwiftUI an und vergleicht dies mit AppKit per Code, stellt man sich schnell die Frage, warum man nicht schon viel früher darauf gekommen ist UIs so einfach per Code erstellen zu können.

    Ich bin die Tage zufällig auf das Swift Package AppKitUI von Darren Ford gestoßen, welches eine Erweiterung von AppKit ist und die einfache Erstellung von AppKit UIs, inkl. Auto Layout, per Code ermöglicht.
  • Ja genau. IB könnte so gut sein, wenn nicht...

    Das stimmt, für git diff/merge sind weder NIB noch XIB gut geeignet. Übrigens gab es bei GNUstep kürzlich auch eine Diskussion in diesem Sinne: lists.gnu.org/archive/html/dis…tep/2026-07/msg00006.html

    Im Endeffekt prallen da zwei Welten aufeinander:

    a) Grafische Hochsprache (IB) mit eigentlich nur maschinenlesbarer interner Darstellung (XIB/NIB) und daher zwar sehr komfortabel (wenn es nicht durch immer neue Features verbastelt wurde) aber nicht gut für Kollaboration (diff/merge) geeignet.

    b) eine auch als "Programmiersprache" verwendbare menschen- und maschinenlesbare Darstellung die diff/merge kann - das kann eine Textdatei sein oder Programmcode. Hier gibts dann einfache oder komplizierte Ansätze...

    Jetzt ist mir wieder eingefallen wie das damals bei X/Motif hieß: UIL (User Interface Language): archive.opengroup.org/motif/motif.data.sheet.htm Scheint aber extrem aus der Mode gekommen zu sein. Immerhin konnte ich ein Beispiel auftreiben: github.com/lkujaw/motif/blob/t…hellomotif/hellomotif.uil

    Das soll aber nicht missverstanden werden als dass das uralte Zeugs irgendwie nachgebaut werden sollte... Sondern nur als Beispiel für die grundsätzlichen Vorgehensweisen.

    Das AppKitUI klingt aber auch sehr interessant, wobei es anscheinend GUI-Beschreibung und Code mischt, wenn ich es richtig deute.

    Jetzt wäre die (natürlich wissenschaftlich und theoretisch) interessante Frage, ob man irgendwie eine Zwischensprache definieren kann, die eine Art Interface-Builder als grafischen Editor erlaubt aber gleichzeitig auch diff/merge-fähig ist. Vermutlich sehr schwierig: denn wenn ich einen Button oder ein Label verschiebe, ändert das "nur" die Koordinaten. Wenn dann der Kollege parallel den Knopf in einen anderen Container gesteckt hat, ändert das die Position in der Hierarchieund meine Koordinatenänderung müsste umgerechnet werden. Und schon ist ein merge-Konflikt sehr wahrscheinlich. Oder jemand ändert die Klasse eines Objekts und es ist nicht zusammenführbar. So ähnlich kennt man das ja auch von TextEdit-Dateien im RTF-Format in iCloud wo dann nur angeboten wir die eine oder die andere Datei weiterzuverwenden...
  • Hi @MCDan,
    spannendes Projekt. Meine Frage geht so in die Richtung von @MyMattes. SwiftUI scheint ja nicht für alle Dinge ausgereift zu sein. Hast du dir mal Compose Multiplatform angeschaut? Das ist schon erschreckend gut.

    Auch stellt sich mir immer mehr die Frage wie viel überhaupt noch nativ für macOS gebaut wird. Vlt. auch ein Grund, warum Swift UI so hinterhinkt? Ich meine man kann selbst iPhone Apps auf dem Mac ausführen. Ich habe glaub keine einzige native App auf meinem Mac laufen (Apple Apps mal ausgenommen). Alles nur noch Electron, Java... Ob das gut ist mag ich nicht zu bewerten. Ich meine nur: Macht es Sinn sich mit noch einem extra UI-System zu bemühen, wo es schon so viele Alternativen gibt und es dann vlt. doch keiner nutzt?

    Ich find es aber auf jeden Fall einen spannenden Ansatz und berichte gerne, wie es weiter geht!

    Nils