MyEqualWidthHStack WWDC example not working for me

Hi

I really liked the video by Paul Lettieri, creating a replacement for HStack that gives equal width to each, so I wrote one for my app. But the app doesn't compile for me. It gives me "Trailing closure passed to parameter of type 'HorizontalAlignment' that does not accept a closure" against the top VStack, not the place where the equal lengths HStack replacement appears.

This is my version of his struct:

extension LayoutSubviews {
   
  func maxSize() -> CGSize {
    let subviewSizes = map { $0.sizeThatFits(.unspecified) }
    return subviewSizes.reduce(CGSize.zero) { CGSize(width: Swift.max($0.width, $1.width), height: Swift.max($0.height, $1.height)) }
  }// maxSize()
   
  func spacing() -> [Double] {
    return indices.map { index in
      guard index < count - 1 else { return 0.0 }
      return self[index].spacing.distance(to: self[index + 1].spacing, along: .horizontal)
    }
  }// spacing()
   
}// extension LayoutSubviews


struct EqualWidthHStack: Layout {
   
  func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
    let maxsize = subviews.maxSize()
    let totalSpacing = subviews.spacing().reduce(0) { $0 + $1 }
    return CGSize(width: maxsize.width * Double(subviews.count) + totalSpacing, height: maxsize.height)
  }// sizeThatFits
   
  func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
    let maxsize = subviews.maxSize()
    let spacing = subviews.spacing()
    let sizeProposal = ProposedViewSize(width: maxsize.width, height: maxsize.height)
    var x = bounds.minX + maxsize.width / 2
    for index in subviews.indices {
      subviews[index].place(at: CGPoint(x: x, y: bounds.midX), anchor: .center, proposal: sizeProposal)
      x += maxsize.width + spacing[index]
    }
  }// placeSubviews
   
}// EqualWidthHStack

I wrote this trivial View to test it:


struct ContentView: View {
  var body: some View {
    VStack {
      HStack {
        Button("Hi") { print("Hi!") }
        Button("Hello") { print("Hello!") }
        Button("Good evening") { print("Good evening!") }
      }
      EqualWidthHStack {
        Button("Hi") { print("Hi!") }
        Button("Hello") { print("Hello!") }
        Button("Good evening") { print("Good evening!") }
      }
      Image(systemName: "globe")
        .imageScale(.large)
        .foregroundColor(.accentColor)
      Text("Hello, world!")
    }
  }
}

I'm using Version 14.0 beta 2 (14A5229c) of Xcode. I was having a problem with the exact same message in another app with a much more complex main view.

I hope someone can tell me what to do or what is going on here.

Regards,

Mark

Accepted Reply

A lot of people have been experiencing different behaviours with the new Layout API and they each have different solutions.

Resolved in iOS & iPadOS 16 beta 2, passing multiple children to a custom Layout now compiles. This means that a Group or another container is no longer required.

The other issue arises from how the layout views are created under the hood.

Two solutions for this are either wrapping the layout name in parentheses:

(CustomLayout()) {
    Text("Hello, World")
    Text("Hello, World")
}

or creating an initialiser for the custom Layout that accepts no parameters:

init() {}
...
CustomLayout {
    Text("Hello, World")
    Text("Hello, World")
}

Replies

I've only been able to get it to work if I wrap everything in a ForEach. Definitely seems like a bug

Thanks, Bryan. I'll wait for the next version of Xcode and then file a radar.

A lot of people have been experiencing different behaviours with the new Layout API and they each have different solutions.

Resolved in iOS & iPadOS 16 beta 2, passing multiple children to a custom Layout now compiles. This means that a Group or another container is no longer required.

The other issue arises from how the layout views are created under the hood.

Two solutions for this are either wrapping the layout name in parentheses:

(CustomLayout()) {
    Text("Hello, World")
    Text("Hello, World")
}

or creating an initialiser for the custom Layout that accepts no parameters:

init() {}
...
CustomLayout {
    Text("Hello, World")
    Text("Hello, World")
}

Hah! You were right, but you shouldn't have been, BabyJ. I added 4 parentheses and it worked:

struct ContentView: View {
    var body: some View {
        VStack {
            HStack {
                Button("Hi") { print("Hi!") }
                Button("Hello") { print("Hello!") }
                Button("Good evening") { print("Good evening!") }
            }
            (EqualWidthHStack()) {
                Button("Hi") { print("Hi!") }
                Button("Hello") { print("Hello!") }
                Button("Good evening") { print("Good evening!") }
            }
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
    }
}

SwiftUI 5 (iOS 17, macOS 14)

MyEqualWidthHStack {
                Button {
                    Task {
                        await update()
                    }
                } label: {
                    Text("update")
                        .frame(maxWidth: .infinity)
                }
                .border(.yellow)
                
                
                Button {
                    Task {
                        await sync()
                    }
                } label: {
                    Text("sync")
                        .frame(maxWidth: .infinity)
                }
                .border(.yellow)
            }