Comment puis-je ajouter une propriété supplémentaire en fonction d'une condition?
Avec mon code ci-dessous, j'ai l'erreur Cannot assign value of type 'some View' (result of 'Self.overlay(_:alignment:)') to type 'some View' (result of 'Self.onTapGesture(count:perform:)')
import SwiftUI struct ConditionalProperty: View { @State var overlay: Bool var body: some View { var view = Image(systemName: "photo") .resizable() .onTapGesture(count: 2, perform: self.tap) if self.overlay { view = view.overlay(Circle().foregroundColor(Color.red)) } return view } func tap() { // ... } }
6 Réponses :
Dans la terminologie SwiftUI, vous n'ajoutez pas de propriété. Vous ajoutez un modificateur.
Le problème ici est que, dans SwiftUI, chaque modificateur renvoie un type qui dépend de ce qu'il modifie.
var body: some View { let view = Image(systemName: "photo") .resizable() .onTapGesture(count: 2, perform: self.tap) if overlay { return AnyView(view.overlay(Circle().foregroundColor(.red))) } else { return AnyView(view) } }
Puisque newView
a un type différent de view
, vous ne pouvez pas simplement attribuer view = newView
.
Une façon de résoudre ce problème est de toujours utiliser le modificateur de overlay
, mais de rendre la superposition transparente lorsque vous ne voulez pas qu'elle soit visible:
var body: some View { return Image(systemName: "photo") .resizable() .onTapGesture(count: 2, perform: self.tap) .overlay(Circle().foregroundColor(overlay ? .red : .clear)) }
Une autre façon de gérer cela consiste à utiliser la gomme de type AnyView
:
var view = Image(systemName: "photo") .resizable() .onTapGesture(count: 2, perform: self.tap) // view has some deduced type like GestureModifier<SizeModifier<Image>> if self.overlay { let newView = view.overlay(Circle().foregroundColor(Color.red)) // newView has a different type than view, something like // OverlayModifier<GestureModifier<SizeModifier<Image>>> }
Les recommandations que j'ai vues des ingénieurs Apple sont d'éviter d'utiliser AnyView
car il est moins efficace que d'utiliser des vues entièrement typées. Par exemple, ce tweet et ce tweet .
Les tests de performances réels d' AnyView
ne révèlent aucune implication négative lors de son utilisation: medium.com/swlh / ...
Rob Mayoff a déjà assez bien expliqué pourquoi ce comportement apparaît et propose deux solutions (superpositions transparentes ou utilisant AnyView
). Une troisième manière plus élégante consiste à utiliser _ConditionalContent .
Un moyen simple de créer _ConditionalContent
consiste à écrire des instructions if
else
dans un groupe ou une pile. Voici un exemple utilisant un groupe:
import SwiftUI struct ConditionalProperty: View { @State var overlay: Bool var body: some View { Group { if overlay { base.overlay(Circle().foregroundColor(Color.red)) } else { base } } } var base: some View { Image("photo") .resizable() .onTapGesture(count: 2, perform: self.tap) } func tap() { // ... } }
Notez que par défaut, SwiftUI traite le type statique d'une vue comme son identifiant pour les animations ( expliqué par Joe Groff ici ). Dans votre solution, les deux sous-vues (avec / sans superposition) sont donc traitées comme des vues non liées. Si l'on applique une animation, elle peut s'appliquer différemment dans votre solution que dans la mienne. Ce n'est pas nécessairement faux, juste quelque chose dont il faut être conscient. Il peut être contourné en utilisant le modificateur .id
.
Je pense que la méthode préférée pour implémenter les Conditional Properties
est ci-dessous. Cela fonctionne bien si vous avez appliqué des animations.
J'ai essayé plusieurs autres méthodes, mais cela affecte l'animation que j'ai appliquée.
Vous pouvez ajouter votre condition à la place de false J'ai gardé .conditionalView ( false
) .
Ci-dessous, vous pouvez passer à true
pour vérifier le "Hello, World!" sera affiché en gras .
struct ContentView: View { var body: some View { Text("Hello, World!") .conditionalView(false) // or use: // .modifier(ConditionalModifier(isBold: false)) } } extension View { func conditionalView(_ value: Bool) -> some View { self.modifier(ConditionalModifier(isBold: value)) } } struct ConditionalModifier: ViewModifier { var isBold: Bool func body(content: Content) -> some View { Group { if self.isBold { content.font(.custom("HelveticaNeue-Bold", size: 14)) }else{ content.font(.custom("HelveticaNeue", size: 14)) } } } }
Il n'est pas nécessaire d'utiliser l'extension conditionalView
pour View
. Initialisez la structure ConditionalModifier
directement à l'aide de .modifier(ConditionalModifier(isBold : viewIsBold))
@PeterSchorn Je pense que j'ai compris votre point, je vous suggère d'ajouter votre réponse aussi.
Voici une réponse similaire à ce que Kiran Jasvanee a publié, mais simplifiée:
struct ContentView: View { @State private var makeBold = false var body: some View { Button(action: { self.makeBold.toggle() }, label: { Text("Tap me to Toggle Bold") .modifier(ConditionalModifier(isBold: makeBold)) }) } } struct ConditionalModifier: ViewModifier { var isBold: Bool func body(content: Content) -> some View { Group { if self.isBold { content.font(.custom("HelveticaNeue-Bold", size: 14)) } else{ content.font(.custom("HelveticaNeue", size: 14)) } } } }
La différence est qu'il n'est pas nécessaire d'ajouter cette extension:
struct ConditionalModifier: ViewModifier { var isBold: Bool @ViewBuilder func body(content: Content) -> some View { // Group { if self.isBold { content.font(.custom("HelveticaNeue-Bold", size: 14)) } else{ content.font(.custom("HelveticaNeue", size: 14)) } // } } }
Dans la structure ConditionalModifier
, vous pouvez également éliminer la vue Groupe et utiliser à la @ViewBuilder
décorateur @ViewBuilder
, comme ceci:
extension View { func conditionalView(_ value: Bool) -> some View { self.modifier(ConditionalModifier(isBold: value)) } }
Vous devez utiliser une vue de groupe (ou une autre vue parente) ou le décorateur @ViewBuilder
si vous souhaitez afficher des vues de manière conditionnelle.
Voici un autre exemple dans lequel le texte d'un bouton peut être basculé entre gras et non gras:
struct ContentView: View { var body: some View { Text("Hello, World!") .modifier(ConditionalModifier(isBold: true)) } } struct ConditionalModifier: ViewModifier { var isBold: Bool func body(content: Content) -> some View { Group { if self.isBold { content.font(.custom("HelveticaNeue-Bold", size: 14)) } else{ content.font(.custom("HelveticaNeue", size: 14)) } } } }
Grâce à ce post, j'ai trouvé une option avec une interface très simple:
extension View { @ViewBuilder func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View { if condition { transform(self) } else { self } } }
Activé par cette extension:
Text("Hello") .if(shouldBeRed) { $0.foregroundColor(.red) }
Voici un excellent article de blog avec des conseils supplémentaires pour les modificateurs conditionnels: https://fivestars.blog/swiftui/conditional-modifiers.html
C'est la meilleure solution.
Aussi simple que ça
import SwiftUI struct SomeView: View { let value: Double let maxValue: Double private let lineWidth: CGFloat = 1 var body: some View { Circle() .strokeBorder(Color.yellow, lineWidth: lineWidth) .overlay(optionalOverlay) } private var progressEnd: CGFloat { CGFloat(value) / CGFloat(maxValue) } private var showProgress: Bool { value != maxValue } @ViewBuilder private var optionalOverlay: some View { if showProgress { Circle() .inset(by: lineWidth / 2) .trim(from: 0, to: progressEnd) .stroke(Color.blue, lineWidth: lineWidth) .rotationEffect(.init(degrees: 90)) } } } struct SomeView_Previews: PreviewProvider { static var previews: some View { Group { SomeView(value: 40, maxValue: 100) SomeView(value: 100, maxValue: 100) } .previewLayout(.fixed(width: 100, height: 100)) } }