skip to Main Content

I’ve been working on a SwiftUI widget and came across an interesting observation.

I’ve always used .lineLimit(1) directly on the Label to limit the number of lines it displays:

struct TodoWidgetEntryView : View {
    var entry: Provider.Entry
    let todoItems: [TodoItem] = DataManager().todoItems
    
    var body: some View {
        VStack(alignment: .leading, spacing: 1) {
            ForEach(0..<todoItems.count, id: .self) { index in
                Button(intent: TodoIntent(item: todoItems[index].taskName)) {
                    Label(todoItems[index].taskName, systemImage: "circle(todoItems[index].isCompleted ? ".fill" : "")")
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .lineLimit(1) // Applied directly on Label
                }.tint(.black)
            }
            Spacer(minLength: 0)
        }.background(SwiftUI.Color.fromInt(0xfff7ce46))
    }
}

However, I later realized I could also apply .lineLimit(1) to the end of ForEach:

struct TodoWidgetEntryView : View {
    var entry: Provider.Entry
    let todoItems: [TodoItem] = DataManager().todoItems
    
    var body: some View {
        VStack(alignment: .leading, spacing: 1) {
            ForEach(0..<todoItems.count, id: .self) { index in
                Button(intent: TodoIntent(item: todoItems[index].taskName)) {
                    Label(todoItems[index].taskName, systemImage: "circle(todoItems[index].isCompleted ? ".fill" : "")")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }.tint(.black)
            }
            .lineLimit(1) // Applied on ForEach
            Spacer(minLength: 0)
        }.background(SwiftUI.Color.fromInt(0xfff7ce46))
    }
}

Can someone explain why applying .lineLimit(1) to the ForEach is also effective in limiting the lines of the labels inside it?

2

Answers


  1. A lot of view modifiers in SwiftUI are designed this way – applying the modifier to an "outer" view will apply the same modifier to all the views "nested" inside that view. Other than lineLimit, there is also environment, font, multilineTextAlignment, foregroundStyle, the various view-specific style modifiers like buttonStyle, pickerStyle, toggleStyle, and so on.

    For lineLimit specifically, the documentation points this out:

    The line limit applies to all Text instances within a hierarchy. For example, an HStack with multiple pieces of text longer than three lines caps each piece of text to three lines rather than capping the total number of lines across the HStack.

    You can make this kind of modifiers yourself by using environment, which can "trickle down" any kind of value you want. You can start by creating your own EnvironmentKey and EnvironmentValues.

    struct FooEnvironmentKey: EnvironmentKey {
        static var defaultValue: Int = 0
    }
    
    extension EnvironmentValues {
        var foo: Int {
            get { self[FooEnvironmentKey.self] }
            set { self[FooEnvironmentKey.self] = newValue }
        }
    }
    
    extension View {
        func foo(_ number: Int) -> some View {
            environment(.foo, number)
        }
    }
    

    Then you can do

    ForEach(0..<5) { _ in
        SomeView()
    }
    .foo(5)
    
    // The value of "@Environment(.foo) var foo" in SomeView would be 5
    

    See my question here for a more complicated example.

    Login or Signup to reply.
  2. In SwiftUI, applying the .lineLimit(1) modifier to the ForEach view is effective in limiting the lines of the labels inside it due to the way SwiftUI handles view modifiers and their cascading effects.

    When you apply .lineLimit(1) directly to the Label view, you are specifically limiting the number of lines for that particular view. However, when you apply the .lineLimit(1) modifier to the ForEach view, it propagates down to the child views, including the Label views generated within the ForEach.

    SwiftUI applies modifiers recursively to child views, which means that modifiers applied to a container view can affect its child views. In this case, when you apply .lineLimit(1) to the ForEach view, it is applied to each Label view generated inside the loop.

    The reason for this behavior lies in how SwiftUI treats the view hierarchy and modifier application. The modifiers are passed down the view hierarchy, and each child view in the hierarchy inherits the modifiers applied to its parent unless overridden. This mechanism makes it convenient to apply modifiers at different levels of the view hierarchy, allowing for a concise and efficient way to apply similar styles to a group of views.

    Therefore, in your case, both approaches of applying the .lineLimit(1) modifier work effectively, and the choice between the two largely depends on how you want to organize your code and apply the modifier within the view hierarchy.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search