SwiftUI Basics

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

  • SwiftUI Basics

    Moinsen,

    seit Tagen suche ich die Lösung für eine ganz simple Aufgabe. Offensichtlich bin ich völlig falsch, oder die Lösung ist so simple, dass ich nicht selber drauf komme. Habe unendlich viele Beispiele zu @StateObject, @Published, etc gelesen, nicht passt zu meiner einfachen Aufgabe:

    Ich habe ein simples Label (Text). Beim Initialisieren kann ich einen Wert übergeben, der dann auch angezeigt wird. Jetzt möchte ich den Text im Label ändern - daran verzweifle ich gerade. Wie geht das von außerhalb z.B. durch die Funktion changeText?

    Kann ich mal bitte jemand in die richtige Richtung schubsen?

    Quellcode

    1. import SwiftUI
    2. class TestClass: NSObject {
    3. @objc class public func changeText(newText:String) {
    4. // ?????
    5. }
    6. }
    7. struct ContentView: View {
    8. var text:String
    9. var body: some View {
    10. Text("\(text)")
    11. }
    12. }
    13. struct ContentView_Previews: PreviewProvider {
    14. static var previews: some View {
    15. ContentView(text:"Preview")
    16. }
    17. }
    Alles anzeigen
  • Hey,

    grundsätzlich ganz simpel: du änderst den Wert deines Text-Arguments. Also z.B. mittels ContentView(text: myTextVariable, wobei myTextVariable der Wert ist, den du in deiner Funktion ändern kannst. Oder du speicherst den Wert intern in der ContentView als State und aktualisierst ihn dort. Dank der Reactiveness von SwiftUI wird sich der Wert automatisch aktualisieren. Jetzt ist lediglich noch die Frage, weshalb du das ganze denn von einer separaten, völlig unverbundene Klasse aus machen möchtest - die hat doch überhaupt keinen Bezug zu deiner View-Hierarchy? Fang doch damit an, deine changeText Funktion zunächst einmal in deine ContentView zu packen und sie erst später in z.B. ein ViewModel auszulagern - das macht das ganze anfangs etwas übersichtlicher, auch bzgl. der Frage wann du @State, @Published etc benötigst.

    VG
  • Danke für deine Hilfe Osxer. Seit fünf Tage drehe ich mich im Kreis. Das ist hier ja nur ein Beispiel für mein Problem. Eigentlich handelt es sich um eine recht komplexe Objective C macOS App, wo ich nun ein SwiftUI View in einem Fenster anzeigen möchte. Funktioniert eigentlich alles, ich bekomme es nur nicht hin, den View upzudaten wenn sich Daten ändern. Die Daten übergebe ich als NSArray an eine Swift Methode. Von da aus müsste nun der View aktualisiert werden.

    Osxer schrieb:

    Oder du speicherst den Wert intern in der ContentView als State und aktualisierst ihn dort.
    Aber wie? Genau bekomme ich nicht hin.


    Osxer schrieb:

    Fang doch damit an, deine changeText Funktion zunächst einmal in deine ContentView zu packen und sie erst später in z.B. ein ViewModel auszulagern
    Auch das will mir nicht gelingen.
  • meinst sowas?

    Quellcode

    1. import SwiftUI
    2. import Combine
    3. struct ContentView: View {
    4. @StateObject private var obj = MyClass()
    5. var body: some View {
    6. VStack {
    7. Text(obj.text)
    8. }
    9. }
    10. }
    11. class MyClass: ObservableObject {
    12. private var timer: AnyCancellable? = nil
    13. private(set) var text = ""
    14. init() {
    15. timer = Timer
    16. .publish(every: 1, on: .main, in: .common)
    17. .autoconnect()
    18. .sink(receiveValue: { value in
    19. self.text = value.description
    20. self.objectWillChange.send()
    21. })
    22. }
    23. }
    Alles anzeigen
  • MyClass ist doch nicht die ContentView. Die View wird zum aktuellisiwren aufgefordert über objectWillChange. und du brauchst halt eine Möglichkeit den Inhalt einer variablen zu setzen.

    Wenn du MyClass ohne Verbindung zur ContentView haben möchtest, musst halt den Inhalt via Notification senden und dann die Variable im .onReceive aktualisieren.

    Statt StateObject kannst au natürlich auch ein EnvironmentObhject in die View klemmen.


    Aber noch einen Hinweis, in SwiftUI sagst du der View nicht, dass sie sich aktualisieren soll. sondern du sagst, die Daten haben sich geändert. Sie wird sich dann selbst aktualisieren…
  • ich glaube das Problem liegt eher darin, dass du nicht genau weisst, was du eigentlich erreichen möchtest. Versuche das doch erst einmal "in Worten" zu beschreiben. Denn eine Klasse, die keinerlei Verbindung zum PresentationLayer aufweist, die Darstellung in diesem jedoch per Timer manipulieren soll, dass klingt schon sehr nach #missedDesign.
  • 322 schrieb:

    ich glaube das Problem liegt eher darin, dass du nicht genau weisst, was du eigentlich erreichen möchtest. Versuche das doch erst einmal "in Worten" zu beschreiben. Denn eine Klasse, die keinerlei Verbindung zum PresentationLayer aufweist, die Darstellung in diesem jedoch per Timer manipulieren soll, dass klingt schon sehr nach #missedDesign.
    Was ich erreichen will, ist eigentlich so einfach, dass es mir peinlich ist hier zu fragen.

    Ich habe ein SwiftUI View mit einem Textlabel. Nun soll der Text geändert werden. In Obj. C erstelle ich mir ein IBOutlet und ändere darüber den Text. Das packe ich in eine Methode, welche ich mit dem Text als Parameter von anderer Stelle aufrufen kann. SwiftUI funktioniert so aber nicht. Dafür suche ich nun ein analoges Beispiel.
  • Quellcode

    1. @State private var meinText : String = "Hello World"
    2. var body : some View {
    3. VStack
    4. {
    5. Text(meinText)
    6. .font(.title)
    7. TextField("Ändere Text", text: $meinText)
    8. .textFieldStyle(RoundedBorderTextFieldStyle())
    9. }
    10. .padding()
    11. }
    Alles anzeigen
    Du suchst aber nicht sowas ? Das ist die wohl banalste Lösung Text über ein Textfield zu ändern.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von ashtari ()

  • Quellcode

    1. Das packe ich in eine Methode, welche ich mit dem Text als Parameter von anderer Stelle aufrufen kann.
    Von wem soll wo und warum die Methode aufgerufen werden.
    Allgemein:
    wenn du einen Parameter der View manipulierst, so gehört die Methode dafür ( func set( text: String) ) eigentlich in das ViewModel vom View. Allerdings möchtest du diese Methode im ViewModel von aussen aufrufen?
    Wenn die Änderung jedoch von aussen hereingereicht werden soll, wozu dann die Methode, dann reiche doch gleich einen Publisher in den View (oder ViewModel, so vorhanden) und reagiere auf die Änderung des Publisherzustands.
  • Tolibi schrieb:

    ashtari schrieb:

    Du suchst aber nicht sowas ? Das ist die wohl banalste Lösung Text über ein Textfield zu ändern.
    Ja, genau! Soweit war ich auch schon. Nur leider erhalte ich da den Fehler:error build: Property wrappers are not yet supported in top-level code

    Also wohin damit?
    Achsooooo :D Okay, jetzt verstehe ich ungefähr wo du bist.

    Das war ja nur ein Ausschnitt aus einem struct.. Du würdest das ganze noch in eine View packen..
    Der Error sagt dir das du einen Property wrapper irgendwo rein packen musst, was ja auch logisch ist. Also, du musst das alles in ein Struct packen.

    Der grundsätzliche Aufbau in SwiftUi sieht ungefähr so aus..

    Quellcode

    1. #include SwiftUI
    2. struct ContentView: View {
    3. ...
    4. var body: some View {
    5. ....
    6. }
    7. }
    8. }



    Im oberen Bereich hast du deine Includes und dann würdest du deine Views bauen.

    In dem Fall hast du die ContentView.
    Über dem var body würdest du deine Variablen deklarieren / initialisieren.

    Also zum Beispiel so...

    Quellcode

    1. #include SwiftUI
    2. struct ContentView : View {
    3. @State private var meinText = "Hallo"
    4. var body: some View
    5. {
    6. ....
    7. }
    8. }

    Jetzt hast du die Variable meinText mit "Hallo" initialisiert. Du kannst dem Compiler auch sagen, es handelt sich dabei definitiv um 'nen String.. Also @State private var meinText : String = "Hallo"

    Das Interessante an SwiftUi passiert jetzt.. Denn in deinem var body: some View baust du dir deine UI.

    In unserem Fall..

    Quellcode

    1. var body: some View {
    2. VStack{
    3. Text(meinText)
    4. .font(.title)
    5. TextField("Ändere Text", text: $meinText)
    6. .textFieldStyle(RoundedBorderTextFieldStyle())
    7. }
    8. .padding()
    9. }

    Was tun wir hier?

    Wir legen einen V(ertical)Stack an, der uns irgendwelche Daten (in diesem Fall ein Text und ein TextField) VERTIKAL angeordnet ausgibt. Es gibt auch noch einen ZStack und einen HStack. ZStack dient dazu, Dinge übereinander darzustellen und der HStack macht das gleiche wie der VStack nur in Horizontaler Anordnung.

    Jetzt wollen wir natürlich, dass unser Text angezeigt wird, also machen wir einfach eine Text anzeige, die die Variable meinText anzeigt.

    Zum ändern des Textes benötigen wir ja ein Textfeld in dem wir eine Eingabe machen können. Also fügen wir VERTIKAL unter unserem Text ein TextField ein, mit dem "Standardtext" Ändere Text (also, der Text der angezeigt wird, wenn aktuell nichts im TextField getippt wurde) und der Variable die geändert werden soll, meinText. Das $-Zeichen lassen wir an diesem Punkt erstmal in der Erklärung weg, damit es nicht verwirrt.

    Jetzt haben wir also schon die grundsätzliche Funktionalität die du wolltest. Es gibt aber in Swift noch die Modifier, mit denen wir unsere einzelnen Elemente anpassen können.
    In o.g. Fall haben wir dem Text den modifier .font(.title) mitgegeben. Was bedeutet das? Damit erreichen wir, dass der Text die Schriftart "Title" (System schriftart) hat.

    Ähnliches machen wir auch bei dem TextField und geben den modifier .textFieldStyle(RoundedBorderTextFieldStyle()) mit, damit unser TextFeld abgerundete Kanten hat - sieht einfach etwas netter aus.

    Bei .padding() siehst du das selbe Pattern, aber angewandt auf den VStack (der auch modifier haben kann).


    Ich hoffe das hilft dir weiter.

    Sollte das "zu basic" gewesen sein, sorry ^^

    EDIT:

    Für weitere Informationen lies dir gerne mal das durch:
    kofler.info/swiftui/#:~:text=B…e,bottom%2C%2020)%20).%20.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von ashtari ()

  • Wolf schrieb:

    Oh…
    bei dem obigen Beispiel, muss das natürlich so heissen:

    Quellcode

    1. @State private var meinText: String = "Hello World"
    wie oben, kann das gar nicht gehen, das ist ein wunderbarer Mix aus class und struct
    ... Das passiert dann wohl, wenn man nur mit einer Hirnhälfte so etwas verfasst :D
  • Was du nicht sagst…

    Quellcode

    1. import SwiftUI
    2. import Combine
    3. struct ContentView: View {
    4. @StateObject private var obj = MyClass()
    5. @State private var text = "Edit me"
    6. var body: some View {
    7. VStack {
    8. TextField("Text", text: $text)
    9. .padding(.horizontal)
    10. .textFieldStyle(RoundedBorderTextFieldStyle())
    11. Text(obj.text)
    12. }
    13. }
    14. }
    15. class MyClass: ObservableObject {
    16. private var timer: AnyCancellable? = nil
    17. private(set) var text = ""
    18. init() {
    19. timer = Timer
    20. .publish(every: 1, on: .main, in: .common)
    21. .autoconnect()
    22. .sink(receiveValue: { value in
    23. self.text = value.description
    24. self.objectWillChange.send()
    25. })
    26. }
    27. }
    Alles anzeigen