SwiftUI in den Regalen

Jedes Mal, wenn ein neues Framework in einer Programmiersprache erscheint, erscheinen früher oder später Leute, die die Sprache daraus lernen. Dies war wahrscheinlich in der iOS-Entwicklung zum Zeitpunkt des Erscheinens von Swift der Fall: Zuerst wurde es als Ergänzung zu Objective-C betrachtet - aber ich habe es nicht bereits gefunden. Wenn Sie jetzt von vorne anfangen, lohnt es sich nicht mehr, eine Sprache zu wählen. Swift ist außer Konkurrenz.

Dasselbe, aber in kleinerem Maßstab, passiert mit Frameworks. Das Aussehen von SwiftUI ist keine Ausnahme. Ich bin wahrscheinlich ein Vertreter der ersten Entwicklergeneration, die SwiftUI lernte und UIKit ignorierte. Dies hat einen Preis - bisher gibt es nur sehr wenige Schulungsunterlagen und Beispiele für Arbeitscode. Ja, das Netzwerk verfügt bereits über eine Reihe von Artikeln, die sich auf eine bestimmte Funktion oder ein bestimmtes Tool beziehen. Unter www.hackingwithswift.com gibt es bereits eine ganze Reihe von Codebeispielen mit Erklärungen. Sie tun jedoch wenig, um denen zu helfen, die SwiftUI von Grund auf lernen möchten, wie mir. Die meisten Materialien im Netzwerk sind Antworten auf spezifische, formulierte Fragen. Ein erfahrener Entwickler kann leicht herausfinden, wie alles funktioniert, warum es so ist und warum es angewendet werden sollte. Für einen Anfänger müssen Sie zunächst verstehen, welche Frage zu stellen ist, und erst dann kann er auf diese Artikel zugreifen.



Unter dem Kürzel werde ich versuchen, das zu systematisieren und zu sortieren, was ich im Moment gelernt habe. Das Format des Artikels ist beinahe ein Leitfaden, vielmehr ein Spickzettel, den ich in der Form zusammengestellt habe, in der ich ihn zu Beginn meiner Reise selbst lesen möchte. Für erfahrene Entwickler, die sich noch nicht tief mit SwiftUI befasst haben, gibt es auch einige interessante Codebeispiele, und Erläuterungen in Textform können diagonal gelesen werden.

Ich hoffe, dieser Artikel spart Ihnen etwas Zeit, wenn Sie auch ein bisschen Magie spüren möchten.

Für den Anfang ein wenig über dich


Ich habe praktisch keinen mobilen Entwicklungshintergrund und bedeutende Erfahrung
in 1s konnte hier nicht viel helfen. Wie und warum ich mich dazu entschlossen habe, SwiftUI zu lernen, erkläre ich Ihnen zu einem späteren Zeitpunkt, ob es für jemanden interessant sein wird.

So kam es, dass der Beginn meines Eintauchens in die mobile Entwicklung mit der Veröffentlichung von iOS 13 und SwiftUI zusammenfiel. Dies ist ein Zeichen, dachte ich und beschloss, sofort damit zu beginnen und UIKit zu ignorieren. Ich fand es ein amüsanter Zufall, dass ich zu solchen Zeiten mit 1c angefangen habe: Dann erschienen einfach verwaltete Formulare. Im Fall von 1c dauerte die Popularisierung neuer Technologien fast fünf Jahre. Jedes Mal, wenn ein Entwickler angewiesen wurde, einige neue Funktionen zu implementieren, stand er vor der Wahl, diese schnell und zuverlässig mit vertrauten Werkzeugen auszuführen oder viel Zeit mit neuen Werkzeugen und ohne Garantie für Ergebnisse zu verbringen. Die Wahl fiel in der Regel gerade jetzt auf Geschwindigkeit und Qualität, und die Investition in neue Werkzeuge verzögerte sich sehr lange.

Anscheinend ist die Situation bei SwiftUI ungefähr gleich. Jeder ist interessiert, jeder versteht, dass dies die Zukunft ist, aber bisher haben sich nur wenige die Zeit genommen, sie zu studieren. Es sei denn für Haustierprojekte.

Im Großen und Ganzen war es mir egal, welchen Rahmen ich studieren sollte, und ich beschloss, trotz der allgemeinen Meinung, dass es möglich sein würde, ihn in ein oder zwei Jahren in Produktion zu bringen, ein Risiko einzugehen. Und da ich zufällig zu den Pionieren gehörte, beschloss ich, praktische Erfahrungen auszutauschen. Ich möchte sagen, dass ich kein Guru bin und generell in der mobilen Entwicklung - ein Wasserkocher. Trotzdem bin ich schon einen bestimmten Weg gegangen, bei dem ich das ganze Internet nach Informationen abgesucht habe, und ich kann mit Zuversicht sagen, dass dies nicht ausreicht und praktisch nicht systematisiert ist. Aber auf Russisch ist es natürlich praktisch nicht vorhanden. In diesem Fall beschloss ich, meine Kräfte zu sammeln, den Betrüger- Komplex beiseite zu schieben und der Gemeinde mitzuteilen , was ich selbst herausgefunden hatte. Ich gehe davon aus, dass der Leser mit SwiftUI zumindest minimal vertraut ist, und ich werde Dinge wie VStack{…} , Text(…) usw. nicht entschlüsseln.

Ich betone noch einmal, dass ich meine eigenen Eindrücke von Versuchen, das gewünschte Ergebnis von SwiftUI zu erzielen, weiter beschreiben werde. Ich konnte gut und gerne etwas nicht verstehen und aus einigen Versuchen falsche oder ungenaue Schlussfolgerungen ziehen, daher sind Korrekturen und Klarstellungen ausdrücklich erwünscht.

Für erfahrene Entwickler scheint dieser Artikel voller Beschreibungen offensichtlicher Dinge zu sein, er wird jedoch nicht streng beurteilt. Tutorials für Dummies auf SwiftUI wurden noch nicht geschrieben.

Was zur Hölle ist das SwiftUI?


Also, ich würde wahrscheinlich mit dem anfangen, worum es geht, das ist deine SwiftUI. Auch hier taucht meine 1. Vergangenheit auf. Die Analogie zu verwalteten Formularen wurde erst deutlicher, als ich mir einige Tutorial-Videos zum Layout von Benutzeroberflächen in Storyboard ansah (d. H. Bei der Arbeit mit UIKit). Ich nahm Nostalgie nach „unkontrollierbaren“ Formen in 1s: manuelle Platzierung von Elementen auf dem Formular und insbesondere Bindungen ... Oh, als der Autor des Trainingsvideos etwa 20 Minuten lang über die Feinheiten der Bindung verschiedener Elemente aneinander und an die Ränder des Bildschirms sprach, erinnerte ich mich mit einem Lächeln 1C - Vor kontrollierten Formen war alles das Gleiche. Na ja, fast ... ein bisschen schlimmer natürlich, na ja und dementsprechend - einfacher. Und SwiftUI besteht grob aus verwalteten Formularen von Apple. Keine Bindungen. Keine Storyboards und Segways. Sie beschreiben einfach die Struktur Ihrer Ansicht im Code. Und alle. Alle Parameter, Größen usw. werden direkt im Code eingestellt - aber ganz einfach. Genauer gesagt, Sie können die Parameter vorhandener Objekte in Canvas bearbeiten, müssen sie jedoch erst im Code hinzufügen. Ehrlich gesagt, ich weiß nicht, wie dies in großen Entwicklungsteams funktionieren wird, in denen es üblich ist, das Layout des Designs und den Inhalt der Ansicht selbst zu trennen, aber als Indie-Entwickler mag ich diesen Ansatz wirklich.

Deklarativer Stil


SwiftUI geht davon aus, dass die Beschreibung der Struktur Ihrer Ansicht vollständig im Code enthalten ist. Darüber hinaus bietet uns Apple einen deklarativen Schreibstil für diesen Code. Das heißt, so etwas:
„Dies ist eine Ansicht. Es besteht aus zwei Textfeldern und einem Bild (aus irgendeinem Grund möchte ich „Ansehen“ sagen und dementsprechend eine Deklination auf ein weibliches Wort anwenden) . Textfelder werden horizontal hintereinander angeordnet. Das Bild ist darunter und seine Ränder sind in Form eines Kreises zugeschnitten. "
Klingt ungewöhnlich, oder? Normalerweise beschreiben wir im Code den Prozess selbst, was getan werden muss, um das Ergebnis zu erzielen, das wir in unserem Kopf haben:
"Fügen Sie einen Block ein, fügen Sie ein Textfeld in diesen Block ein, gefolgt von einem anderen Textfeld, und machen Sie danach ein Bild, schneiden Sie die Kanten durch Abrunden ab und fügen Sie sie unten ein."
Es klingt wie eine Anleitung für Möbel von Ikea. Und in swiftUI sehen wir sofort, was das Ergebnis sein sollte. Auch ohne Canvas oder Debugging spiegelt die Struktur des Codes deutlich die Struktur der Ansicht wider. Es ist klar, was und in welcher Reihenfolge und mit welchen Effekten angezeigt wird.

Ein ausgezeichneter Artikel über FunctionBuilder und darüber, wie Sie Code deklarativ schreiben können, ist bereits auf Habré verfügbar.

Grundsätzlich wurde viel über den deklarativen Stil und seine Vorteile geschrieben, deshalb werde ich ihn abrunden. Ich füge von mir selbst hinzu, dass ich mich ein wenig daran gewöhnt habe und wirklich gespürt habe, wie bequem es ist, Code in diesem Stil zu schreiben, wenn es um Schnittstellen geht. Damit traf Apple das sogenannte Bullseye!

Woraus besteht View?


Aber schauen wir uns das mal genauer an. Apple schlägt vor, dass der deklarative Stil so aussieht:

 struct ContentView: View { var text1 = "some text" var text2 = "some more text" var body: some View { VStack{ Text(text1) .padding() .frame(width: 100, height: 50) Text(text2) .background(Color.gray) .border(Color.green) } } } 

Bitte beachten Sie, dass View eine Struktur mit einigen Parametern ist. Um die Strukturansicht zu View , müssen wir den berechneten Parameterkörper festlegen, der some View zurückgibt. Wir werden später darüber sprechen. Der Inhalt des body: some View { … } ist die Beschreibung dessen, was auf dem Bildschirm angezeigt wird. Tatsächlich ist dies alles, was für unsere Struktur erforderlich ist, um die Anforderungen des View-Protokolls zu erfüllen. Ich schlage vor, sich in erster Linie auf den body .

Und so Regale


Insgesamt habe ich drei Arten von Elementen gezählt, aus denen der Hauptteil der Ansicht aufgebaut ist:

  • Andere Ansicht
    Das heißt Jede Ansicht enthält eine oder mehrere andere View . Diese wiederum können sowohl vordefinierte Systemansichten wie Text() als auch benutzerdefinierte, komplexe, vom Entwickler geschriebene enthalten. Es stellt sich eine Art Nistpuppe mit unbegrenztem Verschachtelungsgrad heraus.
  • Modifikatoren
    Mit Hilfe von Modifikatoren geschieht jede Magie. Dank ihnen teilen wir SwiftUI kurz und deutlich mit, welche Art von Ansicht wir sehen möchten. Wie es funktioniert, werden wir noch herausfinden, aber die Hauptsache ist, dass Modifikatoren das gewünschte Teil zum Inhalt einer bestimmten View hinzufügen.
  • Behälter
    Die ersten Container, mit denen der Standard "Hallo Welt" beginnt, sind HStack und VStack . Wenig später erscheinen Group , Section und andere. Container haben dieselbe Ansicht, jedoch eine Funktion. Sie übergeben ihnen einige Inhalte, die Sie anzeigen möchten. Das ganze Merkmal des Containers ist, dass er die Elemente dieses Inhalts irgendwie gruppieren und anzeigen muss. In diesem Sinne ähneln Container Modifikatoren, mit dem einzigen Unterschied, dass Modifikatoren eine vorgefertigte Ansicht ändern sollen und Container diese Ansicht (Inhaltselemente oder deklarative VStack{...} ) in einer bestimmten Reihenfolge anordnen, z. B. vertikal oder horizontal ( VStack{...} HStack{...} ). Es gibt auch spezielle Container wie ForEach oder GeometryReader , auf die wir später noch ForEach werden.

    Im Allgemeinen betrachte ich Container als jede Ansicht, in die Content als Parameter übergeben werden kann.

Und das ist alles. Alle Elemente der reinrassigen SwiftUI können einem dieser Typen zugeordnet werden. Ja, dies reicht nicht aus, um Ihre Ansicht mit Funktionen zu füllen, aber dies ist alles, was Sie benötigen, um Ihre Funktionen auf dem Bildschirm anzuzeigen.

.modifiers () - wie sind sie angeordnet?


Beginnen wir mit dem Einfachsten. Ein Modifikator ist eigentlich eine sehr einfache Sache. Er nimmt nur eine View , wendet einige Änderungen an (oder tut es?) Und gibt sie zurück. Das heißt Der Modifikator ist eine Funktion der View selbst, die self zurückgibt, nachdem zuvor einige Änderungen vorgenommen wurden.

Unten ist ein Beispiel für Code, mit dem ich meinen eigenen Modifikator deklariere. Genauer gesagt überlade ich den vorhandenen Modifikatorrahmen frame(width:height:) , mit dem Sie die spezifischen Abmessungen einer bestimmten Ansicht festlegen können. In dem dafür vorgesehenen Feld müssen Sie die Breite und Höhe angeben, und ich musste ein CGSize Objekt mit einem Argument übergeben, das nur die Länge und Breite beschreibt. Warum ich das brauchte, werde ich etwas später erzählen.

 struct FrameFromSize: ViewModifier{ let size: CGSize func body(content: Content) -> some View { content .frame(width: size.width, height: size.height) } } 

Mit diesem Code haben wir eine Struktur erstellt, die dem ViewModifier Protokoll entspricht. Dieses Protokoll erfordert, dass die body() -Funktion in dieser Struktur implementiert ist, deren Eingabe ein gewisser Content ist und deren Ausgabe eine some View : derselbe Typ wie der body Parameter unserer View (wir werden im Folgenden über eine gewisse Sicht sprechen). . Was für ein Content das?

Content + ViewBuilder = Anzeigen


In der eingebauten Dokumentation über ihn heißt es so:
`content` ist ein Proxy für die Ansicht, auf die der durch` Self` dargestellte Modifikator angewendet wird.
Dies ist ein Proxy-Typ, bei dem es sich um ein View-Prefab handelt, auf das Modifikatoren angewendet werden können. Eine Art Halbzeug. Tatsächlich ist Content ein deklarativer Stilabschluss, der die View beschreibt. Nennen wir diesen Modifikator für eine bestimmte Ansicht, erhält er lediglich einen Abschluss vom body und übergibt ihn an unsere body , in der wir unsere fünf Cent zu diesem Abschluss addieren.

View ist wiederum in erster Linie eine Struktur, in der alle Parameter gespeichert sind, die zur Erzeugung eines Bildes auf dem Bildschirm erforderlich sind. Inklusive Montageanleitung, von der Content . Ein mit ViewBuilder verarbeiteter Abschluss in einem deklarativen Stil ( Content ) gibt uns also eine View zurück.

Kehren wir zu unserem Modifikator zurück. Grundsätzlich reicht die Deklaration der FrameFromSize Struktur bereits aus, um sie anzuwenden. Innerhalb des body wir so schreiben:

 RoundedRectangle(cornerRadius: 4).modifier(FrameFromSize(size: size)) 

modifier ist eine View-Protokollmethode, die Inhalt aus der zu ViewBuilder View extrahiert, an die body-Funktion der Modifikatorstruktur übergibt und das Ergebnis an die ViewBuilder Verarbeitung oder an den nächsten Modifikator ViewBuilder , wenn eine Änderungskette ViewBuilder .

Sie können es jedoch noch präziser gestalten, indem Sie Ihren eigenen Modifikator als Funktion deklarieren und damit die Funktionen des View-Protokolls erweitern.

 extension View{ func frame(_ size: CGSize) -> some View { self.modifier(FrameFromSize(size: size)) } } 

In diesem Fall habe ich den vorhandenen Modifikator .frame(width: height:) anderen Variante der .frame(width: height:) überladen. Jetzt können wir die Option verwenden, den Modifikator frame(size:) für jede View aufzurufen. Es stellte sich heraus, dass nichts kompliziert war.

Ein bisschen über Fehler
Übrigens dachte ich, dass es nicht notwendig ist, das gesamte Protokoll zu erweitern, es würde ausreichen, das RoundedRectangle in meinem Fall speziell zu erweitern, und es hätte funktionieren sollen, wie es mir schien - aber es scheint, als hätte Xcode eine solche Unverschämtheit nicht erwartet und fiel mit einem unverständlichen Fehler aus: „ Abort trap: 6 ”und ein Vorschlag, ein Dump an die Entwickler zu senden. Allgemein gesagt, zeigen in SwiftUI Fehlerbeschreibungen die Ursache dieses Fehlers häufig nicht vollständig an.

Auf die gleiche Weise können Sie benutzerdefinierte Modifikatoren erstellen und wie die integrierte SwiftUI verwenden:

 RoundedRectangle(cornerRadius: 4).frame(size) 

Praktisch, kurz und deutlich.

Ich stelle mir eine Kette von Modifikationen als Perlen vor, die an einem Faden aufgereiht sind - unsere Ansicht. Diese Analogie gilt auch in dem Sinne, dass es auf die Reihenfolge ankommt, in der Modifikationen genannt werden.



Fast alles in SwiftUI ist View
Übrigens eine interessante Bemerkung. Als Eingabeparameter akzeptiert Hintergrund keine Farbe, sondern Ansicht. Das heißt Die Color-Klasse ist nicht nur eine Beschreibung der Farbe, sondern eine vollständige Ansicht, auf die Modifikatoren und mehr angewendet werden können. Und als Hintergrund können Sie dementsprechend eine andere Ansicht übergeben.

Modifikatoren - nur für Modifikationen
Vielleicht lohnt es sich, noch einen Punkt zu erwähnen. Modifikatoren, die den Quellinhalt nicht ändern, werden von SwiftUI einfach ignoriert und nicht aufgerufen. Das heißt Sie können keinen Trigger basierend auf dem Modifikator ausführen, der einige Ereignisse verursacht, aber keine Aktionen mit dem Inhalt ausführen. Apple drängt uns beharrlich, einige Aktionen zur Laufzeit abzubrechen, wenn wir die Benutzeroberfläche rendern, und dem deklarativen Stil zu vertrauen.

Standbildansicht


Früher haben wir darüber gesprochen, woraus der body besteht, aus dem Körper der View oder aus den Montageanweisungen. Kehren wir zur View selbst zurück. Erstens ist es eine Struktur, in der einige Parameter deklariert werden können, und body ist nur einer von ihnen. Wie wir bereits sagten, Content body , um herauszufinden, was Content , eine Anweisung zum Zusammenstellen der gewünschten Ansicht, die einen deklarativen Abschluss darstellt. Aber was soll unsere Schließung zurückbringen?

einige Ansicht - Bequemlichkeit




Und wir kommen reibungslos zu einer Frage, die ich lange Zeit nicht herausfinden konnte, obwohl dies mich nicht davon abhielt, Arbeitscode zu schreiben. Was ist das für some View ? Die Dokumentation besagt, dass diese Beschreibung ein „undurchsichtiger Ergebnistyp“ ist - aber das macht wenig Sinn.

Das Schlüsselwort some ist eine "generische" Version einer Beschreibung eines Typs, die von einem Abschluss zurückgegeben wird, der von nichts anderem als dem Code selbst abhängt. Das heißt Das Ergebnis des Zugriffs auf die berechnete Eigenschaft des Körpers unserer Ansicht sollte eine Struktur sein, die das Ansichtsprotokoll erfüllt. Möglicherweise gibt es viele davon - Text, Bild oder eine von Ihnen deklarierte Struktur. Der gesamte Chip des Schlüsselworts some besteht darin, "generic" zu deklarieren, das dem View-Protokoll entspricht. Es wird statisch durch den Code bestimmt, der im Hauptteil Ihrer Ansicht implementiert ist, und Xcode ist durchaus in der Lage, diesen Code zu analysieren und die spezifische Signatur des Rückgabewerts zu berechnen ( na ja , fast immer) . Und manche sind nur ein Versuch, den Entwickler nicht mit unnötigen Zeremonien zu belasten. Es reicht für den Entwickler zu sagen: "Es wird eine Art von Ansicht geben." Der Schlüssel dabei ist, dass der konkrete Typ nicht wie beim üblichen generischen Typ durch die Eingabeparameter bestimmt wird, sondern direkt durch den Code. Daher habe ich oben generisch zitiert.

Xcode sollte in der Lage sein, einen bestimmten Typ zu bestimmen, ohne genau zu wissen, welche Werte Sie an diese Struktur übergeben. Dies ist wichtig zu verstehen - nach dem Kompilieren wird der Ausdruck Einige Ansichten durch den spezifischen Typ Ihrer View . Dieser Typ ist ziemlich deterministisch und kann beispielsweise so komplex sein: Group<TupleView<(Text, ForEach<[SomeClass], SomeClass.ID, Text>)>> .

Beispielcode kann von diesem Typ wiederhergestellt werden:

 Group{ Text(…) ForEach(…){(value: SomeClass) in Text(…) } } 

ForEach , wie aus der ForEach , keine Laufzeitschleife. Dies ist nur eine View die auf der Grundlage eines Arrays von SomeClass Objekten erstellt wird. Als die Kennung einer bestimmten subView die dem Auflistungselement zugeordnet ist, wird die Element-ID angegeben, und für jedes Element wird eine subView Typ Text subView . Text und ForEach in TupleView kombiniert, und all dies wird in Group platziert. Wir werden näher auf ForEach eingehen.

Stellen Sie sich vor, wie viel Schreiben wäre, wenn wir gezwungen wären, eine genaue Signatur wie den Parameterkörper zu beschreiben? Um dies zu vermeiden, wurde das Schlüsselwort some erstellt.

Zusammenfassung
some ist dies „generisch - umgekehrt“. Wir bekommen das klassische Generikum von außerhalb der Funktion, und da wir den spezifischen Typ des generischen Typs bereits kennen, definiert Xcode, wie unsere Funktion funktioniert. some- hängt nicht von den Eingabeparametern ab, sondern nur vom Code selbst. Dies ist lediglich eine Abkürzung, mit der kein bestimmter Typ definiert werden kann, sondern nur die Familie des von der Funktion (dem Protokoll) zurückgegebenen Werts.

Einige Sichtweisen - und Konsequenzen


Der Ansatz, den statischen Typ eines Ausdrucks im Körper zu berechnen, wirft meiner Meinung nach zwei wichtige Punkte auf:

  • Beim Kompilieren analysiert Xcode den Inhalt des Körpers, um den spezifischen Rückgabetyp zu berechnen. In komplexen Körpern kann dies einige Zeit dauern. In einigen besonders komplexen Körpern ist er möglicherweise überhaupt nicht in der Lage, mit vernünftiger Zeit umzugehen, und er wird es direkt sagen.

    Im Allgemeinen muss View so einfach wie möglich gehalten werden. Komplexe Strukturen lassen sich am besten in einer separaten Ansicht platzieren. Somit werden ganze Ketten von realen Typen durch einen Typ ersetzt - Ihre CustomView, die es dem Compiler ermöglicht, nicht mit all diesem Durcheinander durchzudrehen.
    Übrigens ist es sehr praktisch, ein kleines Stück einer großen Ansicht direkt hier zu debuggen und das Ergebnis in Canvas zu empfangen und zu beobachten.
  • Wir können den Fluss nicht direkt steuern. Wenn If - else SwiftUI es weiterhin verarbeiten kann, indem eine „Schrödinger-Ansicht“ vom Typ <_ConditionalContent <Text, TextField >> erstellt wird, kann der Trinar-Bedingungsoperator nur zur Auswahl eines bestimmten Parameterwerts, nicht jedoch eines Typs oder sogar zur Auswahl einer Folge von Modifikatoren verwendet werden.

    Es lohnt sich jedoch, die gleiche Reihenfolge von Modifikatoren wiederherzustellen, und ein solcher Datensatz ist kein Problem mehr.

Außer Körper


Möglicherweise enthält die Struktur jedoch andere Parameter, mit denen Sie arbeiten können. Als Parameter können wir folgende Dinge deklarieren.

Externe Parameter


Dies sind einfache Strukturparameter, die wir bei der Initialisierung von außen übergeben müssen, damit die Ansicht sie irgendwie wiedergibt:

 struct TextView: View { let textValue: String var body: some View { Text(textValue) } } 

In diesem Beispiel ist textValue für die TextView Struktur ein Parameter, der textValue TextView werden muss, da er keinen Standardwert hat. Da die Strukturen die automatische Generierung von Initialisierern unterstützen, können wir diese Ansicht einfach verwenden:

  TextView(textValue: "some text") 

Von außen können Sie auch Abschlüsse übertragen, die ausgeführt werden müssen, wenn ein Ereignis eintritt. Zum Beispiel Button(lable:action:) genau das: Button(lable:action:) den übergebenen Aktionsabschluss durch, wenn auf eine Schaltfläche geklickt wird.

Zustand - Parameter


SwiftUI nutzt die neue Funktion von Swift 5.1 - Property Wrapper - sehr aktiv.

Zuallererst sind dies Zustandsvariablen - gespeicherte Parameter unserer Struktur, deren Änderung sich auf dem Bildschirm widerspiegeln sollte. @State — , @ObservedObject — . ObservableObject — , (View, @ObservedObject ) . @Published .

, , ObservableObjectPublisher , willSet() , , , .

, , body — ? - State-, - State- body . , body — , , stateless . View , , body . . State- View . , , , . , body — . , , , .

Und noch eine Bemerkung
didSet willSet , - . , . , — - , .

Classic State Beispiel :

 struct ContentView: View { @State var tapCount = 0 var body: some View { VStack { Button(action: {self.tapCount += 1}, label: { Text("Tap count \(tapCount)") }) } } } 

Bindungsparameter


, - View @State @ObservedObject . View ? SwiftUI PropertyWrapper — @Binding . . View , , , , View . @State — , , . , @Binding . Property Wrapper, , , View . inout View . , , View, . inout , $, , . React .

 struct ContentView: View { @State var tapCount = 0 var body: some View { VStack{ SomeView(count: $tapCount) Text("you tap \(tapCount) times") } } } 

. @Binding var tapCount: Int , , Int ,

 Binding<Int> 

, , View .

 struct SomeView: View{ @Binding var tapCount: Int init(count: Binding<Int>){ self._tapCount = count //    -    } var body: some View{ Button(action: {self.tapCount += 1}, label: { Text("Tap me") }) } } 

, init , - @PropertyWrapper self._ — , self . , self._ . .

, , - PropertyWrapper , -,

 Binding<Int> 

Int .wrappedValue .

,
, Binding . View View. View , @Binding-. , View State @Binding — , State- Binding. -, , .

EnvironmentObject


, EnvironmentObjectBinding , View , .

 ContentView().environmentObject(session) 

, , - , View. , , - , EnvironmentObject , View. View, , , @EnvironmentObject

  @EnvironmentObject var session: Session 

. EnvironmentObject , . 3-, , , , . EnvironmentObject , View . , Binding .

@Environment — . — , .. . , , ( ), , .. , CoreData:

 @Environment(\.managedObjectContext) var moc: NSManagedObjectContext 

, CoreData SwiftUI . , , . .

Custom @PropertyWrapper


, PropertyWrapper — setter- getter-, , property wrapper . , getter{} setter{} , , View , . , PropertyWrapper UserDefaults .

 @propertyWrapper struct UserDefault<T> { var key: String var initialValue: T var wrappedValue: T { set { UserDefaults.standard.set(newValue, forKey: key) } get { UserDefaults.standard.object(forKey: key) as? T ?? initialValue } } } 

, UserDefaults . Apple , , , , .

, - ( ), , UserDefaults , :

 enum UserPreferences { @UserDefault(key: "isCheatModeEnabled", initialValue: false) static var isCheatModeEnabled: Bool @UserDefault(key: "highestScore", initialValue: 10000) static var highestScore: Int @UserDefault(key: "nickname", initialValue: "cloudstrife97") static var nickname: String } 

, , , .

 UserPreferences.isCheatModeEnabled = true UserPreferences.highestScore = 25000 UserPreferences.nickname = "squallleonhart” 

.

Behälter


, , . , body . , , View . , . , — . , @ViewBuilder , View, View, ( ). , — . VStack , HStack , . , View, Content , , View . , View . , HStack{Text(…)} TupleView<Text, Image> .

, , View , — , , body. , , Text(«a») Text(«b») HStack . offset() position() , , HStack :
HStack(spacing:, alingment:, context:).
, , . — .

ForEach


ForEach . , . , , - forEach(…) . , ForEach View , . Das heißt , , — .
, ForEach - , , — , , , , ( List ).

ForEach : ( data: RandomAccesCollection ), ( id: Hashable ) ( content: ()->Content ). : , ForEach Content — .. . , content , ForEach , .

ForEach , RandomAccesCollection . sorted(by:) , RandomAccesCollection .

ForEachsubView , . , SwiftUI , subView . , View . . Hashable , , id: \.self . , . , Identifiable — . , id subView . - , , Hashable — :

 ForEach(values, id: \.value){item in …} 

, valuesSomeObject , value: Int . , View , . , - . View 1 1 ( ), , @Binding View .

, , Identifiable, . Zum Beispiel so:
 ForEach(keys.indices){ind in SomeView(key: self.keys[ind]) } 

, , . , . , , , , , , JSON . , .

, Content , ForEach . , , (.. , 2 — ). , Groupe{} — .

. , ViewBuilder . , .frame(size:) ? . ( , ). CGSize, . , size, .ftame(width: size.width, height: size.height) — . , — .

Custom container View


, . , “1:N” . dict: [KeyObject: [SomeObject]] .

, KeyObject ( Hashable ), — — SomeObject .

 class SomeObject: Identifiable{ let value: Int public let id: UUID = UUID() init(value: Int){ self.value = value } } class KeyObject: Hashable, Comparable{ var name: String init(name: String){ self.name = name } static func < (lhs: KeyObject, rhs: KeyObject) -> Bool { lhs.name < rhs.name } static func == (lhs: KeyObject, rhs: KeyObject) -> Bool { return lhs.name == rhs.name } func hash(into hasher: inout Hasher) { hasher.combine(name) } } 

- , , . , , generic. , , :

 struct TreeView<K: Hashable, V: Identifiable, KeyContent, ValueContent>: View where K: Comparable, KeyContent: View, ValueContent: View{ let data: [K: [V]] let keyContent: (K)->KeyContent let valueContent: (V)->ValueContent var body: some View{ VStack(alignment: .leading, spacing: 0){ ForEach(data.keys.sorted(), id: \.self){(key: K) in VStack(alignment: .trailing, spacing: 0){ self.keyContent(key) ForEach(self.data[key]!){(value: V) in self.valueContent(value) } } } } } } 

, [K: [V]] ( K — - , V — , ), : , — . , ViewBuilder- ( ), ForEach . RandomAccessCollection , dict.keys , . Comparable KeyObject .

ForEach . , ( \.self) als Bezeichner jedes verschachtelten View. Ich könnte das machen, weil Wörterbuchschlüssel sollten das Protokoll trotzdem unterstützen Hashable. Im zweiten Fall habe ich der Klasse SomeObjectProtokollunterstützung hinzugefügtIdentifiable . — id. id . — , — id. , . .. , id. id — . — id , ForEach .

:

 struct ContentView: View { let dict: [KeyObject: [SomeObject]] = [ KeyObject(name: "1st group") : [SomeObject(value: 1), SomeObject(value: 2), SomeObject(value: 3)], KeyObject(name: "2nd group") : [SomeObject(value: 4), SomeObject(value: 5), SomeObject(value: 6)], KeyObject(name: "3rd group") : [SomeObject(value: 7), SomeObject(value: 8), SomeObject(value: 9)] ] var body: some View { TreeView(data: dict, keyContent: {keyObject in Text("the key is: \(keyObject.name)") } ){valueObject in Text("value: \(valueObject.value)") } } } 

Canvas:



to be continued


. , , CoreData SwiftUI, , , , SwiftUI , . , , .

, — . .

Source: https://habr.com/ru/post/de485548/


All Articles