I would like to add vertical scrolling to my custom layout. I don't know how to do it. I have added my code below for reference. Thanks in Advance.
struct TokenData {
let email: String
}
let gradientColor = LinearGradient(colors: [Color(red: 0/255, green: 204/255, blue: 172/255), Color(red: 1/255, green: 99/255, blue: 240/255)], startPoint: .top, endPoint: .bottom)
struct TokenView: View {
@Binding var dataSource: [TokenData]
@Binding var tokenData: TokenData
var body: some View {
HStack {
Text(tokenData.email)
.foregroundStyle(.white)
Button(action: {
if let index = self.dataSource.firstIndex(where: { $0.email == tokenData.email }) {
self.dataSource.remove(at: index)
} else {
assertionFailure("Should not come here.. ")
}
}, label: {
Image(systemName: "multiply")
.renderingMode(.template)
})
.buttonStyle(.plain)
.foregroundStyle(.white)
}
.padding(3)
.background(gradientColor)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
}
struct TokenFieldView: View {
@State var resultData: [TokenData] = []
@State var inputData: String = ""
@State var layout: AnyLayout = AnyLayout(CustomTokenFieldLayout())
var body: some View {
VStack(alignment: .center) {
HStack(alignment: .center) {
TextField("Enter mail Ids..", text: $inputData)
.onSubmit {
resultData.append(TokenData(email: inputData))
}
Button {
self.resultData.removeAll()
} label: {
Text("clear")
}
}
Divider()
ScrollView(.vertical) {
layout {
ForEach(resultData.indices, id: \.self) { index in
TokenView(dataSource: $resultData, tokenData: self.$resultData[index])
}
}
}
}
.onAppear(perform: {
let window = NSApplication.shared.windows.first
window?.level = .floating
})
}
}
struct CustomTokenFieldLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
return CGSize(width: proposal.width!, height: proposal.height!)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
guard !subviews.isEmpty else {
return
}
var point = bounds.origin
let hSpacing: Double = 5
let vSpacing: Double = 5
point.x += hSpacing
point.y += vSpacing
for subview in subviews {
if (point.x + subview.dimensions(in: .unspecified).width + hSpacing) > bounds.width {
point.y += (subview.dimensions(in: .unspecified).height + vSpacing)
point.x = hSpacing
}
subview.place(at: point, anchor: .leading, proposal: proposal)
point.x += (subview.dimensions(in: .unspecified).width + hSpacing)
}
}
}
When I try to add my custom layout inside scrollview, I get zero proposalview.width and height in Layout definition. Could anyone please explain why that is happening?
struct TokenFieldView: View {
@State var resultData: [TokenData] = []
@State var inputData: String = ""
@State var layout: AnyLayout = AnyLayout(CustomTokenFieldLayout())
var body: some View {
VStack(alignment: .center) {
HStack(alignment: .center) {
TextField("Enter mail Ids..", text: $inputData)
.onSubmit {
resultData.append(TokenData(email: inputData))
}
Button {
self.resultData.removeAll()
} label: {
Text("clear")
}
}
Divider()
ScrollView {
layout {
ForEach(resultData.indices, id: \.self) { index in
TokenView(dataSource: $resultData, tokenData: self.$resultData[index])
}
}
}
}
.onAppear(perform: {
let window = NSApplication.shared.windows.first
window?.level = .floating
})
}
}

When I tried your example code, it crashed in
CustomTokenFieldLayout.sizeThatFits, because it was trying to unwrap an undefined optional.Try changing the implementation of
sizeThatFitsto the following:This stops it crashing, but it is still only returning the proposal it is given, so it is not calculating the actual size that really fits. This prevents the
ScrollViewfrom scrolling properly over the full height of its contents.To fix, the function
sizeThatfitsneeds to iterate over the subviews and work out the real size that fits, as explained by the documentation.Looking at
placeSubviews, I assume you are trying to achieve a flow layout, where as many items are shown on a row as possible. I see that you are also adding leading and trailing padding to the rows and to the overall height.The current implementation of
placeSubviewsis a bit flawed, for the following reasons:anchor: .leading, which means that half their height will be above the position they are placed at.Addressing these issues:
.topLeadingis used instead:dimensionsdelivered by the subview. But in many cases, it probably works to supply the container proposal.So based on the way that
placeSubviewsis working, here is an example implementation ofsizeThatFitsthat computes the size appropriately:This scrolls fine:
One other suggestion: it might be better to apply external padding to the
layoutcontainer, instead of it having internal padding at the sides and at top and bottom. This way, the size proposals that the layout receives will represent the actual space available and the layout functions do not need to be concerned with padding.