Adjusting big or small screens using geometry reader - swift

I am currently stuck in trouble i am not really able to resize image and texts to fit bigger or small screens. This is just a simple card that i want to be able to be resizable and should be in the same proportions. I have even tried geometry reader. I am having trouble as even if I have used geometry reader reader the white spaces I wanted are not there as the picture takes the entire width and height which looks bad not ideal. If there is a way for me to have the same proportion of the card for all different screen sizes then I am all open to ideas.
Image of my card:
Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Image("shops")
.resizable()
.aspectRatio(contentMode: .fill)
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
}
.padding(.bottom, 30)
.frame(width: 400)
}
.frame(width: 380, height: 270)
.cornerRadius(20)
.clipped()
.shadow(radius: 8)
.padding(.top, 20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
ContentView()
}
}
}

I think where you are running into issues is in using the fixed frame. The issue with using it is that it does not adjust for sizes. If you had something complicated, you could use a GeometryReader to resize the frame based on a proportion of the available space. However, the simplest implementation is setting your Text as an .overlay() on your image and setting the .aspectRatio(contentMode: .fit).
The code would now be:
struct ContentView: View {
var body: some View {
Image("shops")
.resizable()
.aspectRatio(contentMode: .fit)
.overlay(
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
}
)
.cornerRadius(20)
.clipped()
.shadow(radius: 8)
.padding(30)
}
}
The overlay is never bigger than the Image, which can be an issue using a ZStack, and it will move with the Image. I then just through some padding on the Image all around to keep it in from the sides, and away from other views.

import SwiftUI
struct ContentView: View {
var body: some View {
ZStack() {
Image("shops")
.resizable()
.aspectRatio(1.4, contentMode: .fit) // 380 / 270 = 1.4
.overlay(
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
.lineLimit(1)
.minimumScaleFactor(0.5)
.padding()
})
}
// .frame is not required, but Change .infinity to a number will resize the card
.frame(maxWidth: .infinity)
.cornerRadius(20)
.shadow(radius: 8)
.padding(.horizontal, 20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 13 Pro")
ContentView()
.previewDevice("iPod touch (7th generation)")
ContentView()
.previewDevice("iPad Pro (12.9-inch) (5th generation)")
}
}

Related

Gab being caused by unknown element in swift design

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()
}
}
}

Unexpected layout issues with SwiftUI Image and ZStack

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()
}
}
}
}

Swift UI with Nested Navigation Views

I am new to SwiftUI and Stack Overflow as well, so any help here is appreciated. I am currently making an app that has nested Navigation Views. However, one issue I am having is that when I am using these nested Views, the Navigation Titles (ex. the back button) are lining below each other, forming a Navigation Bar that is quite tall. I looked at this link and it used a List, but I do not want my app to be in this format. I would like to use text elements that are formatted in different colors and font sizes (please see the attached screenshot), however, this thread only has this list function, not in the format I would like it. Please help me figure out how to get rid of these extra navigation bars!
Text Elements:
Nested Navigation Bar:
Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
Image("img6").resizable().aspectRatio(contentMode: .fill).ignoresSafeArea()
VStack {
Text("SLED").font(.largeTitle).fontWeight(.heavy).padding().background(Color.blue).cornerRadius(10.0)
Spacer()
Text("Shelf Life Expiry Date Tracker").font(.title).fontWeight(.bold)
Spacer()
ZStack {
Image(systemName: "cloud")
.padding()
.font(.system(size: 90))
//try to fix this
//Image("logo1")
Image(systemName: "clock.fill")
.padding()
.font(.system(size: 40))
}
NavigationLink(destination: SeeTestKit()) {
Text("See Test Kit")
}.foregroundColor(Color(red: 0.0, green: 0.0, blue: 0.0))
.padding(20)
.background(Color.pink)
.cornerRadius(10.0)
//Spacer()
HStack {
Spacer()
Image(systemName: "bag")
.padding()
.font(.system(size: 40))
Spacer()
Image(systemName: "alarm.fill")
.padding()
.font(.system(size: 40))
Spacer()
Image(systemName: "calendar")
.padding()
.font(.system(size: 40))
Spacer()
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}```
```import SwiftUI
struct SeeTestKit: View {
var body: some View {
NavigationView {
ZStack {
Image("background2")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
VStack {
Spacer()
Image("testkit")
.resizable()
.aspectRatio(contentMode: .fit)
Spacer()
Text("The test kit serves as a supplement to the SLED Tracking System.")
.font(.title)
.multilineTextAlignment(.center)
Spacer()
Text("The test kit make the observation process more efficient by not requiring you to find your own materials.")
.font(.title2)
.multilineTextAlignment(.center)
Spacer()
Text("It also serves as an alternative to \npeople who may not have access \nto the technology required for the app.").font(.title2).multilineTextAlignment(.center)
Spacer()
Group {
NavigationLink(destination: TestKitMaterials()) {
Text("Materials")
}.foregroundColor(Color(red: 0.0, green: 0.0, blue: 0.0))
.padding(20)
.background(Color.blue)
.cornerRadius(10.0)
.navigationBarTitle("Test Kit", displayMode: .inline).padding()
Spacer()
}
}
}
}
}
}
struct SeeTestKit_Previews: PreviewProvider {
static var previews: some View {
SeeTestKit()
}
}```
```import SwiftUI
struct TestKitMaterials: View {
var body: some View {
ZStack {
Image("bckgrd")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
VStack {
Text("Test Kit Materials")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
.padding(.all, 9.0)
.background(Color(red: 0.5, green: 0.6, blue: 0.9, opacity: 1.0)) .cornerRadius(10.0)
//FIX this
//Text("Safety Glasses").bold()
Spacer()
Group {
Spacer()
Text("Safety glasses were included to prevent \ncontamination or illness from \npotentially spoiled foods.")
.multilineTextAlignment(.center)
.padding(.all, 3.0)
Text("A thermometer was included to test \ntemperature of foods to test for spoilage \nat a certain temperature.")
.multilineTextAlignment(.center)
.padding()
}
Group {
Text("pH strips were used to test the pH of \nthe milk and eggs.")
.multilineTextAlignment(.center)
.padding()
Text("The flashlight and ruler are included for \nthe user to measure the width of the air\n sac of the egg in a dark room.")
.multilineTextAlignment(.center)
.padding()
}
Group {
Text("A pipette is needed to extract samples \nof the food to test certain characteristics.")
.multilineTextAlignment(.center)
.padding()
Text("The test tube provides a separate vessel \nto hold the milk to make observations.")
.multilineTextAlignment(.center)
.padding()
Text("A checklist is included as a guideline for the \nuser to measure characteristics and to\n measure spoilage in the absence of the app.")
.multilineTextAlignment(.center)
.padding()
}
Spacer()
Image(systemName: "img")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
}
struct TestKitMaterials_Previews: PreviewProvider {
static var previews: some View {
TestKitMaterials()
}
}```
Thank you!
As stated earlier in the comments you need to remove a NavigationView. And almost always you'll want to remove any NavigationView on the children views.
Essentially what's happening is you are double stacking your NavViews and can cause some really funky behavior.
Read more on NavigationView

Resize image to screen width using `Vstack` while maintaining aspect ratio

I am working with VStack to show an image (they will be several with different sizes from a json)
I need to show it to occupy the screen width (the width of the vstack and maintaining aspect ratio) and to be properly resized, respecting the height according to the width of the screen.
I have tried in different ways, but I manage to display the image correctly.
My View is:
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
.foregroundColor(.white)
}
.padding()
.frame(alignment: .topLeading)
.foregroundColor(Color.white) .background(RoundedRectangle(cornerRadius: 10) .foregroundColor(.blue))
.padding()
GeometryReader { geometry in
VStack {
Image("sample")
.resizable()
//.frame(width: geometry.size.width)
.aspectRatio(contentMode: .fit)
}
.foregroundColor(.white)
}
.frame(alignment: .topLeading)
.foregroundColor(Color.white) .background(RoundedRectangle(cornerRadius: 10) .foregroundColor(.blue))
.padding()
}
.font(.title)
}
}
When I use .frame (width: geometry.size.width) by assigning the width of the geometry, the width is shown to the entire screen but the height is not maintaining aspect ratio. (looks crushed)
How can I get the dimensions of the image and find its proportion to use it with .aspectRatio (myratio, contentMode: .fit)
There is another way to display the image correctly, any suggestions
You need to eliminate the second GeometryReader, because having two children in the VStack that both accept as much space as they are offered is going to make the VStack unable to give the Image the correct amount of space.
You also need to raise the layout priority of the Image so that the VStack offers it space first, so it can take as much as it needs.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}.foregroundColor(.white)
}.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue))
.padding()
Image(uiImage: UIImage(named: "sample")!)
.resizable()
.aspectRatio(contentMode: .fit)
.layoutPriority(1)
}
}
}
import PlaygroundSupport
let host = UIHostingController(rootView: ContentView())
host.preferredContentSize = .init(width: 414, height: 896)
PlaygroundPage.current.liveView = host
Result:

SwiftUI: Prevent Image() from expanding view rect outside of screen bounds

What I'm trying to achieve
I'm trying to create a SwiftUI view where an image should expand the entire screen (edgesIgnoringSafeArea(.all)), and then overlay a view on top of that, that also fills the entire screen, but respects the safe area.
What I've tried
This is my code, which comes close:
struct Overlay: View {
var body: some View {
VStack {
HStack {
EmptyView()
Spacer()
Text("My top/right aligned view.")
.padding()
.background(Color.red)
}
Spacer()
HStack {
Text("My bottom view")
.padding()
.background(Color.pink)
}
}
}
}
struct Overlay_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Image(uiImage: UIImage(named: "background")!)
.resizable()
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fill)
Overlay()
}
}
}
The issue and tested solutions
The issue is that the image is not clipped it looks like, so it expands the parent view to a width larger than the screen width, which then makes the top right aligned red text box float off screen (see image).
I tried using .clipped() in various places, with no luck. I would preferably avoid using GeometryReader if possible.
Q: How can I make the image view only fill the screen?
You have to limit the frame size of the out-of-bounds Image before it is being picked up by the ZStack to avoid the ZStack to grow and so the Overlay to go out of position.
edit: aheze shows with his answer a way around using GeometryReader by putting the Image into the background of Overlay() with .background(Image()..). This avoids the usage of ZStack and GeometryReader completely and is possibly a cleaner solution.
Based on parent view size
struct IgnoringEdgeInsetsView2: View {
var body: some View {
ZStack {
GeometryReader { geometry in
Image("smile")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: geometry.size.width,
maxHeight: geometry.size.height)
}
Overlay()
}
}
}
Based on screen size
struct IgnoringEdgeInsetsView: View {
var body: some View {
ZStack {
Image("smile-photo")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
Overlay()
}
}
}
No need to mess with GeometryReader. Instead, you can prevent the image from overflowing by using the .background() modifier.
struct ContentView: View {
var body: some View {
Overlay()
.background( /// here!
Image("City")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
)
}
}
Result: