How to create Grids View in SwiftUI inside a ScrollView? - swiftui

I need to create a simple Grid View inside a ScrollView in my SwiftUI project.
Basically, I need to place a few Buttons side by side (2 in a row) in the scrollview.
But when I compile my app, the buttons are underneath each other!
I need to create something like this:
This is my code:
ScrollView(.vertical){
Spacer(minLength: 10)
Button(action: { }) {
Image("mountain")
//declare your image as resizable, otherwise it will keep its original size
.resizable()
//declare the frame of the image (i.e. the size you want it to resize to)
.frame(width: 160, height: 200)
// place the red rectangle with text in an overlay
// as opposed to HStack where elements are side by side
// here the image will be placed under the rest
.overlay(
//This HStack places the text in the middle with some padding
VStack(spacing: 0) {
Text("Learning to")
.font(Font.custom("Nagietha-Regular", size: 40))
.foregroundColor(.white)
// .padding(.vertical)
Text("Meditate")
.font(.title)
.foregroundColor(.white)
.fontWeight(.heavy)
//.padding(.vertical)
}
//set the background of the text to be semitransparent red
.background(Color.clear.opacity(0.6)),
//this defines the alignment of the overlay
alignment: .center)
//clip everything to get rounded corners
.clipShape(RoundedRectangle(cornerRadius: 10))
}
Button(action: { }) {
Image("mountain")
//declare your image as resizable, otherwise it will keep its original size
.resizable()
//declare the frame of the image (i.e. the size you want it to resize to)
.frame(width: 160, height: 200)
// place the red rectangle with text in an overlay
// as opposed to HStack where elements are side by side
// here the image will be placed under the rest
.overlay(
//This HStack places the text in the middle with some padding
VStack(spacing: 0) {
Text("Learning to")
.font(Font.custom("Nagietha-Regular", size: 40))
.foregroundColor(.white)
// .padding(.vertical)
Text("Meditate")
.font(.title)
.foregroundColor(.white)
.fontWeight(.heavy)
//.padding(.vertical)
}
//set the background of the text to be semitransparent red
.background(Color.clear.opacity(0.6)),
//this defines the alignment of the overlay
alignment: .center)
//clip everything to get rounded corners
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
Could someone please advice on this?

struct SimpleGridView: View {
#State var yourArray: [Int] = [1,2,3,4,5,6,7,8,9]
var columns: [GridItem] =
Array(repeating: .init(.flexible()), count: 2)
#State var selection: Int = 1
var body: some View {
VStack{
Picker(selection: $selection, label: Text("GridStyles"), content: {
Text("Easily grows").tag(1)
Text("manual").tag(2)
}).pickerStyle(SegmentedPickerStyle())
switch selection {
case 1:
ScrollView{
LazyVGrid(columns: columns){
//This doesnt have to be a loop but it would make it scalable
ForEach(yourArray, id: \.self){ item in
Button(action: {
print(item.description)
}, label: {
Rectangle().overlay(Text(item.description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
}
}
case 2:
VStack{
HStack{
Button(action: {
print(yourArray[0].description)
}, label: {
Rectangle().overlay(Text(yourArray[0].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[1].description)
}, label: {
Rectangle().overlay(Text(yourArray[1].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
HStack{
Button(action: {
print(yourArray[2].description)
}, label: {
Rectangle().overlay(Text(yourArray[2].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[3].description)
}, label: {
Rectangle().overlay(Text(yourArray[3].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
HStack{
Button(action: {
print(yourArray[4].description)
}, label: {
Rectangle().overlay(Text(yourArray[4].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[5].description)
}, label: {
Rectangle().overlay(Text(yourArray[5].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
}
default:
Text("unknown selection")
}
}
}
}

Related

How do you center Text in ScrollView at SwiftUI

How do you center text here ?
Texts are at the left side of scrollViews.
What is .center for ?
var scrolls = ["One", "Two", "Three"]
VStack {
Spacer()
ForEach(0..<scrolls.count
, id: \.self) { index in
ScrollView(.horizontal, showsIndicators: true) {
Text(" \(scrolls[index])")
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .center)
.background(Color.gray)
}
.frame(maxWidth: .infinity,
maxHeight:50,
alignment:
.center)
.background(Color.green)
}.frame(alignment: .bottom)
}
}
You can use GeometryReader and frame(minWidth: for that, like below.
Tested with Xcode 13.2 / iOS 15.2
var body: some View {
GeometryReader { gr in
VStack {
Spacer()
ForEach(0..<scrolls.count
, id: \.self) { index in
ScrollView(.horizontal, showsIndicators: true) {
Text(" \(scrolls[index])")
.frame(minWidth: gr.size.width, // << here !!
maxHeight: .infinity,
alignment: .center)
.background(Color.gray)
}
.frame(maxWidth: .infinity,
maxHeight:50,
alignment:
.center)
.background(Color.green)
}.frame(alignment: .bottom)
}
}
}

Custom table view SwiftUI iOS

I need to create a table so that the text in the cell expands to the desired size.
I create Content View:
struct ContentView: View {
let items: [String] = [
"1 One Line",
"1 One Line",
"1 One Line"
]
var body: some View {
ScrollView {
VStack(spacing: 1) {
ForEach(self.items, id: \.hash) { (item) in
HStack(spacing: 1) {
Text(item)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.white)
Text("One Line")
.frame(width: 70)
.background(Color.white)
Text("One Line")
.frame(width: 70)
.background(Color.white)
}
}
// MARK: Total
HStack(spacing: 1) {
Text("Title")
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.white)
Text("One Line")
.frame(width: 141)
.background(Color.white)
}
}
.padding(1)
.background(Color.orange)
}
.padding(15)
}
}
and it works well:
But if the text becomes multi-line, then the table breaks:
let items: [String] = [
"1 One Line",
"2 Two Line\nTwo Line",
"3 Three lines\nThree lines\nThree lines"
]
borders are getting wider:
if you make maxHeight = infinity then this solves the problem with borders, but the lines become equal to the maximum height.
The possible solution is to use maxHeight: .infinity with .fixedSize
var body: some View {
ScrollView {
VStack(spacing: 1) {
ForEach(self.items, id: \.hash) { (item) in
HStack(spacing: 1) {
Text(item)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.white)
Text("One Line")
.frame(maxHeight: .infinity, alignment: .center)
.background(Color.white)
Text("One Line")
.frame(maxHeight: .infinity, alignment: .center)
.background(Color.white)
}
.fixedSize(horizontal: false, vertical: true) //<---Here
}
// MARK: Total
HStack(spacing: 1) {
Text("Title")
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.white)
Text("One Line")
.frame(width: 137)
.background(Color.white)
}
}
.padding(1)
.background(Color.orange)
}
.padding(15)
}

ScrollView text alignment SwiftUI

I have a vertical ScrollView with a ForEach loop that has HStack's inside with Text, Image, and Two Text's. I'd like to keep each row's items in alignment with each other. I've attempted to wrap in a VStack with alignment .leading but did nothing. The issue I'm having is the text doesn't line up. Still very new to SwiftUI so any help would be appreciated.
ScrollView(.vertical) {
ForEach(self.daily.forecast, id: \.date) { forecast in
HStack (spacing : 10){
Text(forecast.date ?? "")
.fontWeight(.bold)
Spacer()
RequestImage(Url("IMAGE_URL"))
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.clipped()
Spacer()
Text("\(forecast.maxTemp)")
.fontWeight(.bold)
Text("\(forecast.minTemp)")
.fontWeight(.bold)
}
}
}
.padding(.all, 15)
.onAppear() {
authorize {_ in
loadForecast()
}
}
UPDATED:
I was able to get a bit closer but things still aren't aligned to where I'd like them to be.
ScrollView(.vertical) {
ForEach(self.daily.forecast, id: \.date) { forecast in
HStack (spacing : 10){
Text(date)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 45)
Spacer()
RequestImage(Url("IMAGE_URL"))
.aspectRatio(contentMode: .fit)
.frame(width: 35, height: 35)
.frame(minWidth: 50)
.clipped()
Spacer()
Text(maxTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 45)
Text(minTemp)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(Color.secondary)
.frame(minWidth: 45)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
}
.padding(.all, 15)
.onAppear() {
authorize {_ in
loadForecast()
}
}
Here is a demo of possible solution. Prepared with Xcode 12.4 / iOS 14.4
struct DemoWeatherLayout: View {
private var dayOfTheWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sut"]
var body: some View {
ScrollView {
VStack {
ForEach(dayOfTheWeek, id: \.self) {
WeatherRowView(day: $0,
icon: ["sun.max", "cloud.sun", "cloud", "cloud.snow"].randomElement()!,
low: Array(-5..<0).randomElement()!,
high: Array(1..<15).randomElement()!)
}
}.padding()
}
}
}
struct WeatherRowView: View {
var day: String
var icon: String
var low: Int
var high: Int
var body: some View {
HStack {
Text(day)
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: icon)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
Text("+XXº").foregroundColor(.clear)
.overlay(Text(String(format: "%+d", low)), alignment: .leading)
Text("+XXº").foregroundColor(.clear)
.overlay(Text(String(format: "%+d", high)), alignment: .leading)
}
}
}
}
Three suggestions:
add alignment parameter to HStack:
HStack(alignment: .lastTextBaseline, spacing: 10) { ... //others options: .top, .center, .firstTextBaseline...
Add minLength parameter to Spacer:
Spacer(minLength: 0) but with spacing: 10 in HStack it is a bit redundant. You can remove spacing: 10 for Spacer(minLength: 10)
Add a Spacer(minLength: 0) at the end after Text("\(forecast.minTemp)").fontWeight(.bold)
I'm going to accept #Asperi answer as correct. The UI has indeed changed since to better fit said labels etc. Appreciate everyone's else. The code is very much ugly but works. Not sure if it's 100% correct.
HStack (spacing: 10){
Text(date)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Spacer()
RequestImage(Url("IMAGE_URL_HERE"))
.aspectRatio(contentMode: .fit)
.frame(width: 35, height: 35)
.frame(minWidth: 55)
.clipped()
Spacer()
Text(maxTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 50)
.background(Color.orange)
.cornerRadius(5)
Text(minTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 50)
.background(Color.blue)
.cornerRadius(5)
}

Swift UI Button doesnt compile

I am trying to understand SwiftUI and got stuck on a problem where Xcode refuses to compile.
I have no clue why it allows to add 2 buttons, but complains when adding a third one.
Couldn't find an answer on SO.
Here's the code:
import SwiftUI
struct LoginUI: View {
#State private var name = "dfdfdfdfdf"
#State private var logintitle = "LOGIN/LOGOUT"
#State private var xxx = 0
var body: some View {
VStack{
Text("login")
.font(.largeTitle)
.multilineTextAlignment(.center)
HStack {
VStack {
Text("loginusernametext")
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
TextField(/*#START_MENU_TOKEN#*/"Placeholder"/*#END_MENU_TOKEN#*/, text: $name)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
}
HStack {
VStack {
Text("loginpassword")
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
TextField("Password", text: $name)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
}
HStack {
VStack {
Text("databasereference")
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
TextField("Database", text: $name)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
}
HStack {
VStack {
Text("carplate")
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
TextField("Database", text: $name)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
}
HStack {
//GeometryReader { metrics in
// self.heigth = metrics.size.heigth
Button(action: {
self.doLogin()
}) {
Text(String("jaja"))
.font(.largeTitle)
.foregroundColor(.red)
.background(Color.blue)
}
//}
//Image("aa14b")
// .background(Color.green)
// .frame(minWidth: 0, maxWidth: 80, minHeight: 0, maxHeight: self.heigth, alignment: .topLeading)
Button(action: {
self.doPause()
}) {
Text(String(self.xxx))
.font(.largeTitle)
.foregroundColor(.red)
.background(Color.blue)
}
}
HStack {
Button(action: {
self.doLogin()
}) {
Text(String("odometerdelta"))
.font(.largeTitle)
.foregroundColor(.red)
}
}
HStack{
Text(String("Loginexplain"))
.font(.largeTitle)
.multilineTextAlignment(.center)
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
}
func loginauthdelta(){
}
func doPause(){
xxx += 1
}
func doLogin(){
}
struct LoginUI_Previews: PreviewProvider {
static var previews: some View {
LoginUI()
}
}
}
and here's the error
SO says: "
It looks like your post is mostly code; please add some more details.", dont knwo how much I have to write until that error message goes away. Never seen this message before...
SwiftUI has a hard limit on the number of elements in a view.
Try wrapping some of your top-level elements in a group...

Vertically centering text in Swift UI

I want the "$0.00" to be in the middle of the screen but I can't figure out how to do it.
This is my code:
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", (dolaresVM.dolares.last?.v)!))
.font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}.navigationBarTitle("Test")
.onAppear(perform: self.dolaresVM.fetchDolares)
}
}
What am I doing wrong?
ScrollView has infinite inner space for its children. The VStack can't take all of this space. So VStack's height is defined by its content (in our case - Text).
Without ScrollView it will work like you want:
var body: some View {
NavigationView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", 0)).font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("Test")
}
}
Providing idealHeight for the VStack can be helpful as well. You can use GeometryReader to get the 'outer' height of the ScrollView:
NavigationView {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", 0)).font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, idealHeight: geometry.size.height, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}.navigationBarTitle("Test")
}
}
As we discussed above, you don't need ScrollView so can write .navigationBarTitle("Test") inside NavigationView. So that NavigationBarTitle and Text("$0.00") both will be display on your screen.
Here i put static value of Text, you can replace it with dynamic value which you are setting up from your Model.
struct ContentView: View {
var body: some View {
NavigationView {
VStack(alignment: .center) {
Text("$0.00")
.font(.largeTitle)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("Test")
}
}
}