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:
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:
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:
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:
Alles anzeigen
Der komplette Code für das o.a. Fenster würde so aussehen
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.
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
- Grid(columns: [GridColumn(alignment: .trailing), GridColumn(alignment: .leading, size: .flexible)], verticalSpacing: 5, horizontalSpacing: 2,
- content: Label("Anrede:"), PopUpButton(items: model.salutations, binding: model.$salutation).width(80),
- Label("Vorname:"), TextField(placeholderString: "Vorname", binding: model.$firstname),
- Label("Nachname:"), TextField(placeholderString: "Nachname", binding: model.$lastname),
- Label("PLZ/Ort:"), HStack(spacing: 5,
- content: TextField(placeholderString: "PLZ", binding: model.$zip).width(50).formatter(IntegerFormatter(maximumLength: 5)),
- TextField(placeholderString: "Ort", binding: model.$city)
- )
- )
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
- class TestModel {
- @Binding var salutation: String? = nil
- @Binding var firstname: String? = nil
- @Binding var lastname: String? = nil
- @Binding var zip: String? = nil
- @Binding var city: String? = nil
- let salutations: [(String?,String)] = [(nil, ""),("mr", "Herr"), ("mrs", "Frau"), ("company", "Firma")]
- init(saluation: String? = nil, firstname: String? = nil, lastname: String? = nil, zip: String? = nil, city: String? = nil) {
- self.salutation = salutation
- self.firstname = firstname
- self.lastname = lastname
- self.zip = zip
- self.city = city
- }
- }
Der komplette Code für das o.a. Fenster würde so aussehen
Quellcode
- class TestViewController: ViewController {
- let model = TestModel(firstname: "Hans", lastname: "Testmann", zip: "04711", city: "Teststadt")
- override func buildContent() -> Element? {
- self.title = "Formular Test"
- self.autoresizingMask = [.flexibleWidth]
- let grid = Grid(columns: [GridColumn(alignment: .trailing), GridColumn(alignment: .leading, size: .flexible)], verticalSpacing: 5, horizontalSpacing: 2,
- content: Label("Anrede:"), PopUpButton(items: model.salutations, binding: model.$salutation).width(80),
- Label("Vorname:"), TextField(placeholderString: "Vorname", binding: model.$firstname),
- Label("Nachname:"), TextField(placeholderString: "Nachname", binding: model.$lastname),
- Label("PLZ/Ort:"), HStack(spacing: 5,
- content: TextField(placeholderString: "PLZ", binding: model.$zip).width(50).formatter(IntegerFormatter(maximumLength: 5)),
- TextField(placeholderString: "Ort", binding: model.$city)
- )
- )
- let buttonsStack = HStack(layout: .equal, spacing: 20,
- content: Button("Test").action( { print("Button test!") } ),
- Button("OK").action({ [weak self] in
- guard let model = self?.model else { return }
- 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))")
- })
- )
- let vStack = VStack(spacing: 10,
- content: grid,
- HStack(content: Spacer(), buttonsStack, Spacer())
- ).padding(Insets(10))
- return vStack
- }
- }
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.