1

Based on a bool, I would like to add one more modifier to a Text in SwiftUI.

Ideally, I would do something like that:

Text(text)
if(true) {
    .bold()
}
    .foregroundColor(Color.black)
    .frame(alignment: .leading)

which throws errors - the only "uncomplicated" alternative I can think of is to, depending on the bool value, create 2 different Texts. However, this results in a lot of code duplication. What can I do instead?

I've also tried declaring the Text as a let variable to access it later in the code however this prevents the element from showing up.

What IS possible is the following setup:

let title = Text("text")
    .foregroundColor(Color.black)

and then in the body do

if(true) {
            title
                .bold()
        }

However, if I add one more modifier to the declaration, it tells me Property definition has inferred type 'some View', involving the 'some' return type of another declaration

TTJi
  • 19
  • 3

3 Answers3

5

Using conditional modifiers is not recommended by Apple, as it breaks the View's identity once the condition flips. An easy alternative for your usecase would be the ternary operator:

Text(text)
   .fontWeight(condition ? .bold : .regular)
   .foregroundColor(Color.black)
Florian S
  • 552
  • 3
  • 15
  • You can even write `.fontWeight(condition ? .bold : nil)`. Many APIs support `nil` for *no change* – vadian Aug 19 '23 at 08:17
0

Ditto on what Florian S said, you should use a ternary as inline conditionals on view modifiers can lead to many issues, but... they can be useful sometimes as well, so if you want to use inline conditional operations on view modifiers do this.

Add an some extensions to view.. you don't need both of these but depending on how you want to to use it, each has their strengths

extension View {
    
    @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
        if (condition) {
            transform(self)
        } else {
            self
        }
    }
    
    @ViewBuilder func `ifInline`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
        if condition() {
            transform(self)
        } else {
            self
        }
    }
}

then, on the view you want to use the extension with do something like this

ForEach(values: self.multiDevice ? devices : device) { device in
    Group {
        ForEach(values: ColorScheme.allCases) { scheme in
            self.viewToPreview
              .background(Color.backgroundColor)
              .colorScheme(scheme)
              .previewDevice(PreviewDevice(rawValue: device))
              .previewDisplayName("\(displayName) - \(scheme.previewName) - \(device)")
              .if(self.wrapped) { view in
                  view.previewLayout(.sizeThatFits)
              }
        }
    }
}

To use the second extension, the '.if' would turn into a '.ifInline'.

A small note, this use case is from a GenPreviews class I make in my projects to more easily show canvas previews on various devices and color schemes with a descriptive title I can provide a name for from the Provider and some bools I can pass to show either one device or multiple from two lists of options I include as well as wrapping or showing the view on a device preview.

The reason I bring this up is because this use case not only isn't used in production runtime, but isn't even included when compiling a release... which goes back to my first statement that I agree with Florian S. I have used inline conditionals on view modifiers for running code before, but it is not good practice and shouldn't be done unless circumstances require and permit it. A ternary operator for your situation would be the best approach.

theZ3r0CooL
  • 147
  • 2
  • 10
0

my two cents, hope can helps other. (I arrived here to see tips about) I did read DOES discourage modifier, so a totally different approach can be a tip for others.

I needed to hide / show Search in MANY places without messing around too much.

//  Created by ing.conti on 19/08/23.
//

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var showIt = true

    var body: some View {
        NavigationStack {
            Text("typed: \(searchText)")
                .onChange(of: self.searchText, perform: { newValue in
                    if self.searchText.count>5{
                        self.showIt.toggle()
                    }
                })
                .navigationTitle("Hide/ Show Sample")
            
            Spacer()
            Button( self.showIt ?  "Hide" : "Show") {
                self.showIt.toggle()
            }.padding(4).border(.blue)
        }
        .eventuallySearchable(with: $searchText, showIt: self.showIt)
    }
}


// Modifier:
struct SearchText: ViewModifier {
    @Binding var searchText: String
    let showIt: Bool
    
    func body(content: Content) -> some View {
        if showIt{
            content
                .searchable(text: $searchText)
        }else{
            content
        }
    }
}


public extension View {
    func eventuallySearchable(with text: Binding<String>, showIt: Bool) -> some View {
        modifier(SearchText(searchText: text, showIt: showIt))
    }
}
 
ingconti
  • 10,876
  • 3
  • 61
  • 48