How to center align a Section item in a Form - forms

I am trying to center align an array of views inside a section, however all I get is leading alignment. I tried using VStack instead of HStack, and using .frame(width: ..., alignment: .center) but these don't work for me. If I substitute the ScrollView with a Text then that works. I'm using Xcode 12.3 and iOS 14.3.
struct TestSectionAlign : View {
var body : some View {
Form {
Section(header: Text("background color")) {
viewForBackgroundColor
}
}
}
var viewForBackgroundColor : some View {
HStack {
Spacer()
ScrollView.init(.horizontal, showsIndicators: false) {
HStack {
Spacer()
ForEach(0..<15) { i in
Button(action: {
// do something
}) {
Image(systemName: "circle.fill").foregroundColor(Color.init(white: Double(i)/16.0))
}
.buttonStyle(PlainButtonStyle())
}
Spacer()
}
}
Spacer()
}
}
}

Here is a solution using GeometryReader, then setting manually width of the HStack and aligning it center
struct ContentView : View {
var body : some View {
Form {
Section(header: Text("background color")) {
viewForBackgroundColor
}
}
}
var viewForBackgroundColor : some View {
GeometryReader { geometry in
HStack(alignment: .center) {
ScrollView.init(.horizontal, showsIndicators: false) {
HStack(alignment: .center) {
ForEach(0..<15) { i in
Button(action: {
// do something
}) {
Image(systemName: "circle.fill").foregroundColor(Color.init(white: Double(i)/16.0))
}
.buttonStyle(PlainButtonStyle())
}
}.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center) //<< here now center
}
}.border(Color.red)
}
}
}

Related

Why is LazyHGrid not as high as its items?

I've got a LazyHGrid that shows multiple Text views in one row. That LazyHGrid is inside a VStack. However, the LazyHGrid is higher than it needs to be. What causes the extra space?
struct ContentView: View {
let rows = [GridItem()]
var body: some View {
VStack {
ScrollView(.horizontal) {
LazyHGrid(rows: rows) {
ForEach(0..<10) { index in
Text("Test \(index)")
.padding()
.foregroundColor(Color.white)
.background(Color.black)
}
}
}
.background(Color.green)
ScrollView(.vertical) {
VStack {
ForEach(0..<100) { index in
Text(String(index))
.frame(maxWidth: .infinity)
}
}
}
.background(Color.blue)
}
}
}
You just have to add .fixedSize() modifier to your grid, and it works...
LazyHGrid(rows: rows) {
ForEach(0..<10) { index in
Text("Test \(index)")
.padding()
.foregroundColor(Color.white)
.background(Color.black)
}
}
.fixedSize()
This works: Reading the height of the cells with an overlay with GeometryReader
struct ContentView: View {
let rows = [ GridItem() ]
#State private var contentSize = CGFloat.zero
var body: some View {
VStack {
ScrollView(.horizontal) {
LazyHGrid(rows: rows) {
ForEach(0..<10) { index in
Text("Test \(index)")
.padding()
.foregroundColor(.white)
.background(.black)
.overlay(
GeometryReader { geo in
Color.clear.onAppear {
contentSize = geo.size.height
}
}
)
}
}
}
.frame(height: contentSize + 20)
.background(Color.green)
ScrollView(.vertical) {
VStack {
ForEach(0..<100) { index in
Text(String(index))
.frame(maxWidth: .infinity)
}
}
}
.background(Color.blue)
}
}
}

Why is my SwiftUI VStack not aligning properly?

I'm trying to make my VStack padding consistent regardless of the content, and it doesn't seem to be working in certain instances (as if the Scrollview aligned with the center instead).
Ideally, I would like the padding to be consistent regardless of the content on the screen.
Such as this (when padding isn't consistent):
Here is the code used:
var body: some View {
VStack {
HStack{
Button(action: {}) {
Image(systemName: "arrow.backward")
}
Spacer(minLength: 0)
Button(action: {}) {
Image(systemName: "bookmark")
}
}
.padding(.vertical, 10)
ScrollView {
VStack{
HStack{
VStack (alignment: .leading, spacing: 5) {
Text("Today's Poem, \(currentDate)")
if let poem = fetch.poems.first {
Text("\(poem.title)")
Text("BY "+poem.author.uppercased())
VStack (alignment: .leading) {
ForEach(poem.lines, id: \.self) {
Text($0)
.multilineTextAlignment(.leading)
}
}
} else {
Spacer()
}
}
}
}
}
Button("Get Next Poem") { fetch.getPoem() }
}
.background(Color.white.ignoresSafeArea())
.padding(.horizontal)
}
}
Any ideas? Thanks in advance.
Change the innermost
VStack {
to
VStack(alignment: .leading) {

SwiftUI - custom TabBar height based on device

I am trying to set up the height of my custom TabBar based on Device, my code:
struct MyTabBar: View {
#Binding var index: Int
var body: some View {
HStack {
Button(action: {
self.index = 0
}) {
Image(ImageText.iconHome.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 1
}) {
Image(ImageText.iconBell.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 2
}) {
Image(ImageText.iconAdd.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 3
}) {
Image(ImageText.iconSearch.image).foregroundColor(Color.red)
}
Spacer(minLength: 0)
Button(action: {
self.index = 4
}) {
Image(ImageText.iconHamburger.image)
}
}.padding(.horizontal, 26).frame(height: 56)
}
}
If the devices have notch, how can I set my custom TabBar to have higher height? Does SwiftUI have something that can be useful here?
You can use the GeometryReader element to access the safe area insets, and add it as a padding using .safeAreaInsets.bottom (note that the Xcode autocompletion is almost never working on GeometryProxy properties):
var body: some View {
GeometryReader { proxy in
VStack {
// Content goes here
Spacer()
// Custom tab bar
HStack {
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
}
.padding(.bottom, proxy.safeAreaInsets.bottom)
.background(Color.red)
}
}.edgesIgnoringSafeArea(.bottom)
}

SwiftUI content is not displayed on top of the screen

I have a NavigationView and then a VStack. The issue is that everything inside VStack is being displayed in the middle of the screen and not on top. Why is that?
var body: some View {
VStack {
VStack(alignment: .leading) {
if (request?.receiverID ?? "") == userDataViewModel.userID {
Text("\(sendertFirstName) wants to ship your package").font(.title)
} else {
Text("You want to ship \(recieverFirstName)'s package").font(.title)
}
HStack{
Image(systemName: "clock.fill").font(.subheadline)
Text("\(createdAt, formatter: DateService().shortTime)").font(.subheadline).foregroundColor(.secondary)
}
HStack {
VStack(alignment: .leading) {
HStack {
Image(systemName: "person.fill").font(.subheadline)
Text(sendertFirstName).font(.subheadline).foregroundColor(.secondary)
}
}
Spacer()
VStack(alignment: .leading) {
HStack {
Image(systemName: "star.fill")
Text(rating).font(.subheadline).foregroundColor(.secondary)
}
}
}
}
VStack(alignment: .center) {
HStack {
Button("Accept") {
//print(self.request.createdAt)
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "accepted")
}
.padding()
Button("Decline") {
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "declined")
}
.foregroundColor(.red)
.padding()
}
}
ScrollView {
ForEach(messages) { (message: Message) in
if message.senderId == self.userDataViewModel.userID {
HStack {
Spacer()
Text(message.text).font(.subheadline).padding(7.5).background(self.blue).cornerRadius(30).foregroundColor(.white)
}.padding(.bottom, 2)
} else {
HStack {
Text(message.text).font(.subheadline).padding(7.5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Spacer()
}.padding(.bottom, 2)
}
}
}
HStack {
TextField("Message", text: $inputMessage).padding(5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Button(action: {
let msg: [String: Any] = [
"text": self.inputMessage,
"createdAt": Timestamp(),
"senderId": self.userDataViewModel.userID
]
self.reference.updateData([
"messages": FieldValue.arrayUnion([msg])
])
self.messages.append(Message(dictionary: msg))
self.inputMessage = ""
}) {
Image(systemName: "arrow.up.circle.fill").foregroundColor(self.blue).font(.title)
}
}
Spacer()
}
.padding()
.border(Color.red)
}
I want the content to start on top not on the middle. What am I doing wrong here?
You don't need the NavigationView - the way you have everything set up now you are wrapping the VStack in a NavigationView twice and the massive white space is the second empty navigation bar.
By default most Views take only the amount of space they need and they are placed in the center of their parent view. So if you want your content to be placed at the top, you need to add Spacer() as the last element in your VStack:
var body: some View {
VStack {
/* ... */
Spacer()
}
}
Spacer is one of the rare Views that takes all space offered to it by the parent view and will push all of your content upwards.
Maybe you can try this way:
NavigationView {
VStack {
...
}.frame(maxHeight:.infinity, alignment: .top)
.padding()
.border(Color.red).navigationBarTitle("", displayMode: .inline)
}
Add a .navigationBarHidden(true) to your VStack

How to prevent a Spacer to make a VStack greedly grow beyond necessary?

I roughly have:
var body: some View
{
HStack(alignment: .top) {
AvatarView()
MessageBubble()
if message.isDeleted != true
{
VStack {
Button(action: {
// ...
}) {
Image(systemName: "chevron.down")
}
Spacer() // THIS SPACER
Button(action: {
// ...
}) {
Text("😀")
}
}
}
}
}
The root HStack~'s height is dictated by the sizeMessageBubblewhich is always taller than itsAvatarViewandVStack` siblings.
The problem is when I add a Spacer inside the VStack, then the whole HStack incredibly grows. The Spacer is "greedly" making everything grow with no limits.
I do want to have a button at the very top and the other at the very bottom of the VStack while being limited to MessageBubble's height.
How could I make it grow no more than MessageBubble?
If I understood correctly you expected something like this (elements alignment)
Here is demo code (frames for coloured rects are not important here, sizes just for demo, you will place your views instead there):
struct TestAlignmentSize: View {
#State var height: CGFloat = 40
var body: some View {
HStack(alignment: .top, spacing: 20) {
Rectangle()
.fill(Color.blue)
.frame(width: 50, height: 50)
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 200)
.alignmentGuide(.top, computeValue: { d in // [!!]
self.height = d.height
return d[.top]
})
VStack {
Button(action: {}) {
Image(systemName: "chevron.down")
}
Spacer()
Button(action: {}) {
Text("Button")
}
}.frame(height: self.height) // [!!]
}
}
}