Ok I have tried previous suggestions of just placing it in another vstack but still my view is showing the rectangle at the mid of the screen is their any reason why, also the edges have no padding around them making it edge to edge which I don't want?
struct ProfileView : View
{
var body: some View {
GeometryReader{ geo in
VStack {
VStack(alignment: .trailing, spacing: 10) {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.primary, lineWidth: 2)
.frame(width: geo.size.width, height: 200)
.padding(.horizontal)
}.frame(width: geo.size.width, height: geo.size.height) // <<=== Here }
}
}
}
My Views are being injected into the content area via this page
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
TabView{
HomeView().tabItem {
Image(systemName: "house")
Text("Home")
}
ProfileView()
.tabItem {
Image(systemName: "person.circle.fill")
Text("Profile")
}
ZStack {
StatsView()
.tabItem {
Image(systemName: "murry").renderingMode(.original).padding()
Text("Plus")
}
}
StatsView()
.tabItem {
Image(systemName: "dumbbell.fill").renderingMode(.original).padding()
Text("Stats")
}
StatsView()
.tabItem {
Image(systemName: "dumbbell.fill").renderingMode(.original).padding()
Text("Notes")
}
}
}.safeAreaInset(edge: .bottom, alignment: .center, spacing: 0) {
Color.clear
.frame(height: 20)
.background(Material.bar)
}
}}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewInterfaceOrientation(.landscapeLeft)
}
}
On Simlualtor and real device its placing it in the middle of the screen.
I want this frame so that I can place other elements in side it is a rounded rectangle the best way to go for this?
You are using GeometryReader as the outer most view in the ProfileView, and GeometryReader has the behavior to take as much space as there is available, if you want your rectangle to show in the top or bottom of the screen consider using a Spacer() and if you want to add padding to your rectangle so it does not go to the edges of the screen try removing the .frame(width: geo.size.width) because you are forcing the rectangle to be the width of the screen, in nature the rectangle will take as much space as it can so you do not have to specify that.
This is your code but with some tweaks:
struct ProfileView : View {
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.primary, lineWidth: 2)
.frame(height: 200)
.padding(.horizontal)
Spacer()
}
}
}
Related
I'm trying to create a view composed of two parts - the top part is an image with a piece of text overlaid in the top left corner. The top part should take up 2/3 of the height of the view. The bottom part is text information and a button, contained in an HStack, and takes up the remaining 1/3 of the view height.
struct MainView: View {
var body: some View {
GeometryReader { gr in
VStack(spacing: 0) {
ImageInfoView()
.frame(height: gr.size.height * 0.66)
BottomInfoView()
}
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}
}
struct ImageInfoView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
HStack {
Text("Top left text")
.foregroundColor(.white)
.padding()
}
}
}
}
struct BottomInfoView: View {
var body: some View {
HStack {
VStack(alignment:.leading) {
Text("Some title")
Text("Some subtitle")
}
Spacer()
Button("BUTTON") {
}
}
.padding()
.background(.gray)
}
}
In the below sample code, if I set the frame height to 400 I will see the top left text, but when I set it to 200, I do not. What is happening here? The text should be anchored to the top left corner of ImageInfoView no matter what. Further, when set to 400, why does the ImageInfoView take up more than 66% of the height?
struct TestView: View {
var body: some View {
MainView()
.frame(height: 200)
.padding([.leading, .trailing], 16)
}
}
An image's fill pushes ZStack boundaries, so Text goes off screen (see this for details).
A possible solution is to put image into overlay of other container so it respects provided space instead of imposing own.
Tested with Xcode 13.4 / iOS 15.5
ZStack(alignment: .topLeading) {
Color.clear.overlay( // << here !!
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
)
// ... other code as-is
This fixes the problem:
ZStack(alignment: .topLeading) {
GeometryReader { gr in
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: gr.size.width, maxHeight: gr.size.height)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
HStack {
Text("Top left text")
.foregroundColor(.white)
.padding()
}
}
}
}
I want the status bar and at the bottom to be white (Same as root background color), but no idea, do i need to get status bar height and add margin top and bottom?
Here is my code and the preview below
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(
alignment: .leading,
spacing: 10
) {
Text("Title")
.font(
.system(size: 32)
.weight(.heavy)
)
Text("Content")
}
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
.padding(
EdgeInsets(
top: 0,
leading: 20,
bottom: 0,
trailing: 20
)
)
.background(Color.gray)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
}
}
}
If it were me tackling this kind of UI, I would use some other nice Views that SwiftUI provides for us (like ZStack).
The ZStack places objects one on top of another from the bottom up. So you would want your color first, then the VStack after. It would look something like this:
struct ContentView: View {
var body: some View {
ZStack {
Color.gray
VStack(alignment: .leading, spacing: 10) {
Text("Title")
.font(.system(size: 32).weight(.heavy))
Text("Content")
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
add a vertical padding of 1:
struct ContentView: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Title")
.font(.system(size: 32).weight(.heavy))
Text("Content")
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(.horizontal, 20)
.background(Color.gray)
.padding(.vertical, 1) // here
}
}
Let me add another answer that goes with a different approach, without using .frame(). Instead, it uses the full width and height of HStack and VStack to fill the screen. For the status bar and the bottom area, this approach uses a .layoutPriority() modifier to the gray color but not allowing it to overlap the safe area.
While the other answers work quite fine, my purpose with this example is to open the range of possibilities.
struct Example: View {
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 10) {
Text("Title")
.font(
.system(size: 32)
.weight(.heavy)
)
Text("Content")
Spacer() // This spacer will extend the VStack to full height
}
Spacer() // This spacer will extend the HStack to full width
}
.padding()
.background {
VStack {
// Status bar
Color.clear
.ignoresSafeArea()
// Rest of the view: gray has the priority but can't overlap
// the status bar
Color.gray
.layoutPriority(1)
}
}
}
}
I'm currently building a UI that has a big red 250px height box on the top that holds information and a scrollview underneath it. I build this all in a VStack(). I put a spacer() at the bottom of the VStack to push everything to the top of the screen. But it has a big white space between the red container at the top and the scrollview. Even though both don't have any padding(). Does anyone know how this is possible? This is my code:
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
VStack {
...
code
...
}
.frame(width: UIScreen.main.bounds.width, height: 250)
.background(Color.red)
.ignoresSafeArea()
// somehow it has a white space here...
ScrollView {
LazyVGrid(columns: columns, spacing: 2) {
ForEach((0...56), id: \.self) {_ in
Image("testimage")
.resizable()
.scaleEffect(CGSize(width: 1.0, height: 2.0))
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.cornerRadius(10)
}.padding(.bottom, 75)
}
.frame(width: UIScreen.main.bounds.width)
.padding(.top, -10)
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
// this Spacer should push everything to the top
Spacer()
}
}
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
Edit:
To make my question better understandable I made this summery:
I have build a VStack with 2 items in it. there is a big white space between the two and I don't know how to get rid of this white space. If I remove .ignoresSafeArea() the white space disappears. The thing is I need .ignoresSafeArea() to push the first item in VStack up. Does anyone know what I can do to keep .ignoresSafeArea() but get rid of the white space?
I added a screenshot of the problem. I gave the scrollview a green background so that the white spacing between the VStack and the Scrollview is visable.
You need to embed everything in a VStack with 0 spacing.
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
VStack(spacing: 0) {
VStack {
Text("Hello World!") //Easier to test
}
.frame(height: 250) //Dont use UIScreen
.background(Color.red)
.ignoresSafeArea()
// somehow it has a white space here...
ScrollView {
LazyVGrid(columns: columns, spacing: 2) {
ForEach((0...56), id: \.self) {_ in
Image("testimage")
.resizable()
.scaleEffect(CGSize(width: 1.0, height: 2.0))
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.cornerRadius(10)
}.padding(.bottom, 75)
}
.frame(width: UIScreen.main.bounds.width)
.padding(.top, -10)
}
Spacer()
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
}
Also, you might need to give the ScrollView a max height to push it upwards. .frame(maxHeight: 175)
As you haven't given a reproducible example or an image. I cant tell you for certain what will work
I guess Arnavs Answer is correct.
Notice:
When you are trying to implement multiple Views on one page, 90% of the time it is better to have them in one H/V/Z-Stack.
For a better understanding start a new project and try ...
Navigationview{
VStack{
Text("Hello Vertical World")
Text("Hello Vertical World2")
}
HStack{
Text("Hello Horizontal World")
Text("Hello Horizontal World2")
}
}
and the same thing but "different"
NavigationView {
VStack{
Text("Hello Vertical World")
Text("Hello Vertical World2")
HStack{
Text("Hello Horizontal World")
Text("Hello Horizontal World2")
}
}
}
I can't double check bc i am on my phone, but this should look very different.
I fixed the issue by putting the VStack and the Scrollview in an other VStack in the navigationView. Then I put the .ignoresSafeArea() on this new VStack.
Code:
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
// new VStack that includes the topbar VStack, the
// scrollview and has the .ignoresSafeArea() on it.
VStack {
VStack {
...
Code topbar
...
}
.frame(width: UIScreen.main.bounds.width, height: 250)
.background(Color.red)
Scrollview {
...
Code scrollview
...
}
Spacer()
}
.ignoresSafeArea()
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
I'm trying to make the circles fit into the HStack such that the HStack size does not increase.
How can I make the circles fit without specifying a fixed frame?
struct ContentView: View {
var body: some View {
NavigationView {
Form {
HStack {
Circle()
.fill(Color.red)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
HStack {
Circle()
.fill(Color.blue)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
}
}
}
}
Here is a sample of various containers to chose from. SwiftUI will do all the layout, automatically handle rotations and device resolutions.
struct CirclesView: View {
var body: some View {
VStack(spacing: 0) {
Label("Circles", systemImage: "circle").font(.system(size: 24, weight: .black, design: .rounded)).foregroundColor(.pink)
HStack {
Circle()
.foregroundColor(.yellow)
.frame(width: 32, height: 32)
Text("This is a yellow circle")
Spacer()
}
Circle()
.foregroundColor(.orange)
.shadow(radius: 10)
.frame(width: 75)
Divider()
HStack {
VStack {
Circle().foregroundColor(.blue)
Text("Blue").font(.title3)
HStack {
Circle().foregroundColor(.purple)
Text("Purple").font(.caption)
}
}
.padding()
.background(Color.yellow)
ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
Circle().foregroundColor(.green)
Text("Green").foregroundColor(.primary)
}
}
}
}
}
When adding a standard Scrollview I can scroll to the end of my list without any issues, it's after adding a GeometryReader on my button that the scroll list doesn't scroll to the end of the list but stops somewhere at the top of my button. The geometryReader does not scale to the size of the child object, how can I change this without specifying a hard coded frame size for the GeometryReader ? I'm using the GeometryReader to later identify the top of my button corresponding the position on the screen with geo.frame(in: .global).maxY
struct scroll: View {
private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
ScrollView {
VStack {
LazyVGrid(columns: gridItemLayout) {
Circle()
.scaledToFill()
.foregroundColor(.red)
Circle()
.scaledToFill()
.foregroundColor(.green)
Circle()
.scaledToFill()
.foregroundColor(.blue)
Circle()
.scaledToFill()
.foregroundColor(.yellow)
}
GeometryReader { geo in
Button(action: {
}) {
Text("Adjust time")
}
.cornerRadius(18.0)
.padding(.top, 5)
}
//.frame(width: .infinity, height: 50)
}
}
}
}
When checking the height and trying to set the frame of the GeometryReader equal to the height of the button, the button it also gives me 0 as value. When I try to read the width of the button with geo.frame.width, it gives me a correct value.?
struct scroll: View {
private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible())]
#State private var buttonHeight: CGFloat = .zero
var body: some View {
ScrollView {
VStack {
Text("\(buttonHeight)")
LazyVGrid(columns: gridItemLayout) {
Circle()
.scaledToFill()
.foregroundColor(.red)
Circle()
.scaledToFill()
.foregroundColor(.green)
Circle()
.scaledToFill()
.foregroundColor(.blue)
Circle()
.scaledToFill()
.foregroundColor(.yellow)
}
GeometryReader { geo in
Button(action: {
}) {
Text("Adjust time")
}
.onAppear(perform: {
buttonHeight = geo.size.height
})
.cornerRadius(18.0)
.padding(.top, 5)
}
.frame(width: .infinity, height: buttonHeight)
.background(Color.gray)
}
}
}
}
You can read it by putting GeometryReader into background of button, like
Button(action: {
}) {
Text("Adjust time")
}
.cornerRadius(18.0)
.padding(.top, 5)
.background(GeometryReader { geo in
// geo.frame(in: .global).maxY // << global position of button
})
You can then even store it in view preference to pass into state, like in https://stackoverflow.com/a/62466397/12299030.