Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash when using justification #22

Open
josipbernat opened this issue Dec 5, 2024 · 3 comments
Open

Crash when using justification #22

josipbernat opened this issue Dec 5, 2024 · 3 comments

Comments

@josipbernat
Copy link

When I want to wrap RadioCollection in HFlow and use justification I am getting following crash:

SwiftUICore/Layout.swift:1534: Fatal error: view origin is invalid: (nan, 0.0), UnitPoint(x: 0.0, y: 0.0), (inf, 20.5)
Screenshot 2024-12-05 at 10 31 33

Am I doing something wrong or is this a bug in the library?

Btw. awesome work, keep going on!

@tevelee
Copy link
Owner

tevelee commented Dec 5, 2024

Thanks for the crash report! Do you have sample code to reproduce the issue?

I tried the following but didn't crash:

import Flow
import SwiftUI
import SwiftUIRadioButtons

@main
struct ExampleApp: App {
    var body: some Scene {
        WindowGroup {
            Bug()
        }
    }
}

struct Bug: View {
    @State private var selected: String? = nil

    var body: some View {
        HFlow(justification: .stretchItemsAndSpaces) {
            HRadioCollection(
                selectedData: $selected,
                data: ["a", "b", "c"]
            ) { value in
                Text(value)
            }
        }
    }
}

extension String: @retroactive Identifiable {
    public var id: String { self }
}

@josipbernat
Copy link
Author

josipbernat commented Dec 17, 2024

I really tried to reproduce this crash using sample code and extending it but I can't. It happens after doing drag & drop and even though I added this functionality to the sample app it still works properly. I checked and both apps are using the latest v2.5.0 release. Unfortunately the crash is still happening in the app.

But maybe this can help you:

The crash cause happens here:

    @usableFromInline
    func placeSubviews(
        in bounds: CGRect,
        proposal: ProposedViewSize,
        subviews: some Subviews,
        cache: inout FlowLayoutCache
    ) {
        guard !subviews.isEmpty else { return }

        var target = bounds.origin.size(on: axis)
        var reversedBreadth = self.reversedBreadth

        let lines = calculateLayout(in: proposal, of: subviews, cache: cache)    ---> This function returns `Size` that has `breadth: nan`, check the console output below.

        for line in lines {
            adjust(&target, for: line, on: .vertical, reversed: reversedDepth) { target in
                target.breadth = reversedBreadth ? bounds.maximumValue(on: axis) : bounds.minimumValue(on: axis)

                for item in line.item {
                    adjust(&target, for: item, on: .horizontal, reversed: reversedBreadth) { target in
                        alignAndPlace(item, in: line, at: target)
                    }
                }

                if alternatingReversedBreadth {
                    reversedBreadth.toggle()
                }
            }
        }
    }

Maybe this output can help you identify where the problem occurs?

(lldb) po line
▿ ItemWithSpacing<Array<ItemWithSpacing<(subview: Subview, cache: SubviewCache)>>>
  ▿ item : 3 elements
    ▿ 0 : ItemWithSpacing<(subview: Subview, cache: SubviewCache)>
      ▿ item : 2 elements
        ▿ subview : LayoutSubview
          ▿ proxy : LayoutProxy
            ▿ context : AnyRuleContext
              ▿ attribute : #442544
                - rawValue : 442544
            ▿ attributes : LayoutProxyAttributes
              ▿ _layoutComputer : #448392
                ▿ base : #448392
                  ▿ identifier : #448392
                    - rawValue : 448392
              ▿ _traitsList : #446824
                ▿ base : #446824
                  ▿ identifier : #446824
                    - rawValue : 446824
          - index : 0
          - containerLayoutDirection : SwiftUI.LayoutDirection.leftToRight
        ▿ cache : SubviewCache
          - priority : 0.0
          ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
            ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
              ▿ minima : 3 elements
                ▿ 0 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062e0).EdgeAboveText
                    - edge : SwiftUI.AbsoluteEdge.bottom
                  ▿ value : Value
                    - distance : 0.0
                ▿ 1 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d4806328).EdgeLeftText
                    - edge : SwiftUI.AbsoluteEdge.right
                  ▿ value : Value
                    - distance : 0.0
                ▿ 2 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062bc).EdgeBelowText
                    - edge : SwiftUI.AbsoluteEdge.top
                  ▿ value : Value
                    - distance : 0.0
            ▿ layoutDirection : Optional<LayoutDirection>
              - some : SwiftUI.LayoutDirection.leftToRight
          ▿ min : Size
            - breadth : 28.0
            - depth : 35.0
          ▿ ideal : Size
            - breadth : 104.5
            - depth : 36.5
          ▿ max : Size
            - breadth : inf
            - depth : 36.5
      ▿ size : Size
        - breadth : 104.5
        - depth : 36.5
      - leadingSpace : nan
    ▿ 1 : ItemWithSpacing<(subview: Subview, cache: SubviewCache)>
      ▿ item : 2 elements
        ▿ subview : LayoutSubview
          ▿ proxy : LayoutProxy
            ▿ context : AnyRuleContext
              ▿ attribute : #442544
                - rawValue : 442544
            ▿ attributes : LayoutProxyAttributes
              ▿ _layoutComputer : #452488
                ▿ base : #452488
                  ▿ identifier : #452488
                    - rawValue : 452488
              ▿ _traitsList : #450920
                ▿ base : #450920
                  ▿ identifier : #450920
                    - rawValue : 450920
          - index : 1
          - containerLayoutDirection : SwiftUI.LayoutDirection.leftToRight
        ▿ cache : SubviewCache
          - priority : 0.0
          ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
            ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
              ▿ minima : 3 elements
                ▿ 0 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062e0).EdgeAboveText
                    - edge : SwiftUI.AbsoluteEdge.bottom
                  ▿ value : Value
                    - distance : 0.0
                ▿ 1 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d4806328).EdgeLeftText
                    - edge : SwiftUI.AbsoluteEdge.right
                  ▿ value : Value
                    - distance : 0.0
                ▿ 2 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062bc).EdgeBelowText
                    - edge : SwiftUI.AbsoluteEdge.top
                  ▿ value : Value
                    - distance : 0.0
            ▿ layoutDirection : Optional<LayoutDirection>
              - some : SwiftUI.LayoutDirection.leftToRight
          ▿ min : Size
            - breadth : 28.0
            - depth : 35.0
          ▿ ideal : Size
            - breadth : 112.0
            - depth : 36.5
          ▿ max : Size
            - breadth : inf
            - depth : 36.5
      ▿ size : Size
        - breadth : 112.0
        - depth : 36.5
      - leadingSpace : inf
    ▿ 2 : ItemWithSpacing<(subview: Subview, cache: SubviewCache)>
      ▿ item : 2 elements
        ▿ subview : LayoutSubview
          ▿ proxy : LayoutProxy
            ▿ context : AnyRuleContext
              ▿ attribute : #442544
                - rawValue : 442544
            ▿ attributes : LayoutProxyAttributes
              ▿ _layoutComputer : #456584
                ▿ base : #456584
                  ▿ identifier : #456584
                    - rawValue : 456584
              ▿ _traitsList : #455016
                ▿ base : #455016
                  ▿ identifier : #455016
                    - rawValue : 455016
          - index : 2
          - containerLayoutDirection : SwiftUI.LayoutDirection.leftToRight
        ▿ cache : SubviewCache
          - priority : 0.0
          ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
            ▿ spacing : Spacing [
  (EdgeBelowText, top) : 0.0
  (EdgeAboveText, bottom) : 0.0
  (EdgeLeftText, right) : 0.0
]
              ▿ minima : 3 elements
                ▿ 0 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d4806328).EdgeLeftText
                    - edge : SwiftUI.AbsoluteEdge.right
                  ▿ value : Value
                    - distance : 0.0
                ▿ 1 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062e0).EdgeAboveText
                    - edge : SwiftUI.AbsoluteEdge.bottom
                  ▿ value : Value
                    - distance : 0.0
                ▿ 2 : 2 elements
                  ▿ key : Key
                    ▿ category : Optional<Category>
                      ▿ some : Category
                        - type : SwiftUI.Spacing.Category.(unknown context at $1d48062bc).EdgeBelowText
                    - edge : SwiftUI.AbsoluteEdge.top
                  ▿ value : Value
                    - distance : 0.0
            ▿ layoutDirection : Optional<LayoutDirection>
              - some : SwiftUI.LayoutDirection.leftToRight
          ▿ min : Size
            - breadth : 28.0
            - depth : 35.0
          ▿ ideal : Size
            - breadth : 109.0
            - depth : 36.5
          ▿ max : Size
            - breadth : inf
            - depth : 36.5
      ▿ size : Size
        - breadth : 109.0
        - depth : 36.5
      - leadingSpace : inf
  ▿ size : Size
    - breadth : nan     ----> Here it is!
    - depth : 36.5
  - leadingSpace : 0.0

Then when the execution reaches:

private func adjust<T>(
        _ target: inout Size,
        for item: ItemWithSpacing<T>,
        on axis: Axis,
        reversed: Bool,
        body: (inout Size) -> Void
    ) {
        let leadingSpace = item.leadingSpace
        let size = item.size[axis]
        target[axis] += reversed ? -leadingSpace-size : leadingSpace ----> It assignees `nan` here
        body(&target)
        target[axis] += reversed ? 0 : size
    }

And the crash finally happens here:

    private func alignAndPlace(
        _ item: Line.Element,
        in line: Lines.Element,
        at target: Size
    ) {
        var position = target
        let lineDepth = line.size.depth
        let size = Size(breadth: item.size.breadth, depth: lineDepth)
        let proposedSize = ProposedViewSize(size: size, axis: axis)
        let itemDepth = item.size.depth
        if itemDepth > 0 {
            let dimensions = item.item.subview.dimensions(proposedSize)
            let alignedPosition = alignmentOnDepth(dimensions)
            position.depth += (alignedPosition / itemDepth) * (lineDepth - itemDepth)
        }
        let point = CGPoint(size: position, axis: axis)
        item.item.subview.place(at: point, anchor: .topLeading, proposal: proposedSize) ---> here is the actual crash.
    }

I thin that the root cause is in the fact that

    public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: LayoutSubviews, cache: inout FlowLayoutCache) {
        layout.placeSubviews(in: bounds, proposal: proposal, subviews: subviews, cache: &cache)
    }

bounds had width: 0

Printing description of bounds:
▿ (0.0, 0.0, 0.0, 36.5)
  ▿ origin : (0.0, 0.0)
    - x : 0.0
    - y : 0.0
  ▿ size : (0.0, 36.5)
    - width : 0.0
    - height : 36.5

I hope this helps!

@tevelee
Copy link
Owner

tevelee commented Dec 19, 2024

Thank you for the debug info! I'll take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants