6
votes

The issue

I'm getting a thread issue that says "Invalid frame dimension (negative or non-finite)."

Here's my code:

struct CellStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .frame(width: .infinity, height: 56, alignment: .center)
            .padding(.horizontal, 16)
    }
}

The code runs fine, and I it seems to do what it's supposed to, so I'm confused at why it's giving me thread issue. Why will the code run without a finite width/height, contradictory to the Apple docs? (see the "Some reading" section below)

The fix

This code fixes the issue just fine, but I still wish I understood why the previous code doesn't crash or create an error.

struct CellStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .frame(height: 56, alignment: .center)
            .frame(maxWidth: .infinity)
            .padding(.horizontal, 16)
    }
}

As far as I can tell, it gives me that thread issue because I tell .frame, that I want a width that isn't finite and it wants a width that's finite.

Other questions on stack overflow

I've found two questions that ask the same thing:
iOS 14 Invalid frame dimension (negative or non-finite)

SwiftUI iOS14 GeometryReader Invalid frame dimension

In the first question, the thread issue makes sense because given the code .frame(width: p.size.width - padding) there's no guarantee that p.size.width will be less than padding. So as far as I can tell, it's an issue where the given value can be negative.
This issue I'm having has to do with the given value not being finite, so the questions don't relate.
The second question still hasn't been answered and is a bit vague, so it isn't that helpful for me right now. Maybe later someone will answer with a helpful answer, but as of yet, it's not helpful.

Some reading

Looking at the Apple docs I find this:

Use this method to specify a fixed size for a view’s width, height, or both. If you only specify one of the dimensions, the resulting view assumes this view’s sizing behavior in the other dimension.

So then why is it that .frame(width: .infinity, height: 56, alignment: .center) even runs at all?
What does Apple mean by "the resulting view assumes this view’s sizing behavior in the other dimension."? Will it always assume that view to be a "push out" view, thus giving the same result as .infinity?

I tested this and ran this code:

struct CellStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .frame(height: 56, alignment: .center)
            .padding(.horizontal, 16)
    }
} 

It seems to give the same result, so now I'm curious is there ever a case in which swift "assuming" the width will result in a behavior other than .infinity?

Final question

What's the behavior difference between .frame(width: .infinity) vs .frame(width: nil) (the equivalent of emitting either width or height).

func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View

Apple docs

Thanks for the help!

2

2 Answers

2
votes

Because it is documented interface contract:

/// - Parameters:
///   - width: A fixed width for the resulting view. If `width` is `nil`,
///     the resulting view assumes this view's sizing behavior.
///   - height: A fixed height for the resulting view. If `height` is `nil`,
///     the resulting view assumes this view's sizing behavior.
///   - alignment: The alignment of this view inside the resulting view.
///     `alignment` applies if this view is smaller than the size given by
///     the resulting frame.
///
/// - Returns: A view with fixed dimensions of `width` and `height`, for the
///   parameters that are non-`nil`.
@inlinable public func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View

whereas modifier type with min/max arguments allows flexible constraints (not copied here - read in generated SwiftUI module).

1
votes

What .frame(width: nil) does

Basically .frame will take the given values and return another view that is updated with the values you put.
If you omit one of the parameters or set it to nil, then it will take the respective width/height value of the previous view.

Test code:

struct ContentView: View {
    @State var keyValue = ""
    var body: some View {
        VStack(spacing: 10) {
            Text("Something")
                .background(Color.blue)
            
            Text("Something")
                .frame(width: 300, height: nil, alignment: .center)
                .background(Color.blue)
            
            Text("Something")
                .frame(width: nil, height: 200, alignment: .center)
                .background(Color.blue)
            
            Text("Something")
                .frame(width: nil, height: nil, alignment: .center)
                .background(Color.blue)
        }
    }
}

Image of the result of the above code

Why .frame(width: .infinity) still runs

.infinity (Apple doc on .infinity) is of type Float, so there's no way for .frame not to except it, but it still can create problems. (Thus the thread issue)

Thanks to Mark Moeykens for the help in getting me to this answer!

Note: .frame(maxWidth: .infinity) instead of .frame(width: .infinity)