I had an earlier question that I got awesome help to but there's something not quite right with the layout still. Figured I'd create a new question rather than continue that one.
I'm making a custom picker using a button and want it laid out like the other pickers, textfields, etc on my form. In the previous question I learned to use the alignmentGuide. However that isn't working as the field isn't quite lined up with the others AND I can only make the window a bit smaller and then it locks into place. I want it to line up with above and be dynamic to window size adjustments when running.
Here's what it looks like right now
This is as small as I can make it:
And here's the current code:
import SwiftUI
struct ContentView: View {
@State var myName:String = "Kyra"
@State var selectedPickerItem: String?
var pickerItems = ["item 1",
"item 2",
"item 3",
"item 4",
"item 5",
"item 6"]
@State var showingPopover:Bool = false
@State var selectedItems = [String]()
@State var allItems:[String] = ["more items",
"another item",
"and more",
"still more",
"yet still more",
"and the final item"]
@State private var commonSize = CGSize()
@State private var commonTextSize = CGSize()
var body: some View {
Form {
TextField("My Name:", text: $myName, prompt: Text("What's your name?"))
.foregroundColor(.white)
.background(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
Picker(selection: $selectedPickerItem, label: Text("Pick Something:")) {
Text("No Chosen Item").tag(nil as String?)
ForEach(pickerItems, id: \.self) { item in
Text(item).tag(item as String?)
}
}
.foregroundColor(.white)
.background(Color(red: 0.2645, green: 0.3347, blue: 0.4008))
HStack() {
Text("Select Items:")
.foregroundColor(.white)
.readSize { textSize in
commonTextSize = textSize
}
Button(action: {
showingPopover.toggle()
}) {
HStack {
Spacer()
Image(systemName: "\($selectedItems.count).circle")
.foregroundColor(.secondary)
.font(.title2)
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
.font(.caption)
}
}
.readSize { textSize in
commonSize = textSize
}
.popover(isPresented: $showingPopover) {
EmptyView()
}
}
.alignmentGuide(.leading, computeValue: { d in (d.width - commonSize.width) })
.background(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
}
.padding()
}
}
// FROM https://stackoverflow.com/questions/57577462/get-width-of-a-view-using-in-swiftui
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
I've since upgraded to Ventura 13.0 beta on my Mac and upgraded Xcode too. With the upgrade I still have this issue; however, the SwiftUI upgrade includes the Grid
control which fixes this layout bug. I figured with this current upgrade Grid
I'd answer this question and mark as solved.
With the upgrade the controls still didn't line up, although I was able to make it as small as I wanted.
Form controls are still not lined up:
But I can make the form as small as I want however the controls' start and end locations jump around as I do.
With the update I was able to use the new Grid
control (documentation link) to layout my controls making them look so much better.
Here it is using Grid. I can drag the edges of the window to make it as small or large as I want without any weirdness.
To do this I replaced my VStack
with Grid
and enclosed each control section with GridRow
. My two bottom controls were too skinny so I combined the grid cells together so they'd take up the whole space by using the modifier .gridCellColumns(3)
. Code is:
Grid {
GridRow {
TextField("My Name:", text: $myName, prompt: Text("What's your name?"))
.foregroundColor(.white)
.background(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
}
.gridCellColumns(3)
GridRow {
Picker(selection: $selectedPickerItem, label: Text("Pick Something:")) {
Text("No Chosen Item").tag(nil as String?)
ForEach(pickerItems, id: \.self) { item in
Text(item).tag(item as String?)
}
}
.foregroundColor(.white)
.background(Color(red: 0.2645, green: 0.3347, blue: 0.4008))
}
.gridCellColumns(3)
GridRow {
HStack() {
// Rather than a picker we're using Text for the label and a button for the picker itself
Text("Select Items:")
.foregroundColor(.white)
Button(action: {
// The only job of the button is to toggle the showing popover boolean so it pops up and we can select our items
showingPopover.toggle()
}) {
HStack {
Spacer()
Image(systemName: "\($selectedItems.count).circle")
.font(.title2)
Image(systemName: "chevron.right")
.font(.caption)
}
}
.popover(isPresented: $showingPopover) {
MultiSelectPickerView(allItems: allItems, selectedItems: $selectedItems)
// If you have issues with it being too skinny you can hardcode the width
.frame(width: 300)
}
}
.background(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
}
.gridCellColumns(3)
}
.padding()
// Made a quick text section so we can see what we selected
Section("My selected items are:", content: {
Text(selectedItems.joined(separator: "\n"))
})
Hope this helps you out if you come across a similar issue.