The following code makes a view where a rectangle is at the bottom of the view. I put .edgesIgnoringSafeArea(.bottom) so the rectangle goes all the way down but it doesn't work. Im simulating this on an Iphone 11 and always leaves a blank space below.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
List {
Text("Hello, World!")
}
Spacer()
Rectangle()
.frame(height: 150)
.edgesIgnoringSafeArea(.bottom)
}
}
}
}
The rectangle is inside a VStack, and the VStack doesn't ignore the safe area. Even if the rectangle ignores the safe area, it can't extend beyond its parent to fill the whole screen.
You should put edgesIgnoringSafeArea after the VStack, and the rectangle will naturally fill the VStack, hence filling the whole screen.
var body: some View {
NavigationView {
VStack {
List {
Text("Hello, World!")
}
Spacer()
Rectangle()
.frame(height: 150)
}
.edgesIgnoringSafeArea(.bottom)
}
}
Related
I want to create a simple NavigationView. But with code outside of it. Like this:
struct TestView: View {
var body: some View {
VStack{
RoundedRectangle(cornerRadius: 10)
.frame(width: 100, height: 100)
NavigationView {
VStack{
NavigationLink {
Text("HEllo")
} label: {
Text("Click me")
}
.navigationViewStyle(.columns)
}
.navigationTitle("A Title")
}
}
}
}
I do that so the navigation Title is below the item outside the NavigationView.
This code gives me this:
Image because I am not allowed to insert images yet.
When I click on the NavigationLink though I see this:
The Image
As you see the RoundedRectangle still is viewable at top of the screen. How can I fix that, so that the Rectangle disappears and the Destination is viewable in full screen?
Set navigation view first, You have to put everything inside the navigation view.
NavigationView { // Here
VStack{
RoundedRectangle(cornerRadius: 10)
.frame(width: 100, height: 100)
VStack{ // Remove from above
The RoundedRectangle is still visible because it is outside of the NavigationView, only the content of the NavigationView will move with the NavigationLink
Something you could do is to use the toolbar of the NavigationView to place items on the top of the screen
struct TestView: View {
var body: some View {
NavigationView {
NavigationLink {
Text("HEllo")
} label: {
Text("Click me")
}
.navigationViewStyle(.columns)
.navigationTitle("A Title")
.toolbar {
ToolbarItem {
RoundedRectangle(cornerRadius: 10)
.frame(width: 100, height: 100)
}
}
}
}
}
I can't find a solution to change my background color view, I tried a lot of options and nothing works.
I'm trying solutions but the isn't changing
There is my struct of the code:
struct ContentView: View {
var body: some View {
VStack {
Text("Trying ColorView")
.font(.largeTitle)
.bold()
Button("ColorView") {
}
}
.accentColor(Color.black)
}
}
First of all you have already mistakes in your posted code above, your XCode should normally tell you that.
Which view you want to change..?
This might be a solution... You can change it like you need it.
struct testViewTwo: View {
var body: some View {
NavigationView {
VStack {
VStack(spacing: -15) {
HStack {
HStack {
Text("Hello World")
}.background(Color.blue)
}.foregroundColor(Color.white)
}
}.background(Image("Background"))
}
}
}
You change a background color with the background modifier.
If you want to change the background color of the whole view, you should create a ZStack that spans the whole view with a background modifier.
ZStack {
...
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color(.blue)
You can simply use Color("Green") to change the color. Here's a sample.
var body: some View {
NavigationView{
VStack {
VStack(spacing: 15){
HStack {
Color("Green")
.edgesIgnoringSafeArea(.top)
}
}
}
}
}
In the following code snippet, the red circles obviously don't fit onto the screen and therefore only the trailing part of the HStack is shown. Instead of that behavior, I want the leading text of the HStack to always be visible and truncate the trailing red circles that don't fit into the available space (replaced with ...).
How can I do this?
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.layoutPriority(1)
Spacer()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
}
}
So I want this:
instead of this:
Can get everything except the ellipses with:
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.fixedSize()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
}
In order to get the ellipses you'll probably have to calculate frame sizes or try to get your images into an attributed string which could get pretty complicated. You might also consider a scroll view or grid.
I am using Mapkit to show the user location. I want the map to fill half of the screen and stay on the top side. This is the code I tried to make the map stay top. However, I have shrunken the map, but I can't get the map go to the top. Any help will be very appreciated.
ZStack(alignment: .top){
MapView().ignoresSafeArea(.all,edges: .all)
.environmentObject(mapData).frame(height: sizeOfMap, alignment: .topLeading)
}
The right way of doing this is using GeometryReader to keep layout same on every device!
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
VStack(spacing: 0.0) {
ZStack {
Color.blue
Text("MapView here!") // MapView()
}
.frame(height: proxy.size.height/2)
ZStack {
Color.red
Text("other View here!")
}
.frame(height: proxy.size.height/2)
}
}
.ignoresSafeArea()
}
}
I have a header view which extends its background to be under the status bar using edgesIgnoringSafeArea. To align the content/subviews of the header view correctly, I need the safeAreaInsets from GeometryReader. However, when using GeometryReader, my view doesn't have a fitted size anymore.
Code without using GeometryReader
struct MyView : View {
var body: some View {
VStack(alignment: .leading) {
CustomView()
}
.padding(.horizontal)
.padding(.bottom, 64)
.background(Color.blue)
}
}
Preview
Code using GeometryReader
struct MyView : View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
CustomView()
}
.padding(.horizontal)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.bottom, 64)
.background(Color.blue)
.fixedSize()
}
}
}
Preview
Is there a way to use GeometryReader without modifying the underlying view size?
Answer to the question in the title:
It is possible to wrap the GeometryReader in an .overlay() or .background(). Doing so will mitigate the layout changing effect of GeometryReader. The view will be laid out as normal, the GeometryReader will expand to the full size of the view and emit the geometry into its content builder closure.
It's also possible to set the frame of the GeometryReader to stop its eagerness in expanding.
For example, this example renders a blue rectangle, and a "Hello world" text inside at 3/4th the height of the rectangle (instead of the rectangle filling up all available space) by wrapping the GeometryReader in an overlay:
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.overlay(GeometryReader { geo in
Text("Hello world").padding(.top, geo.size.height * 3 / 4)
})
Spacer()
}
}
Another example to achieve the same effect by setting the frame on the GeometryReader:
struct MyView : View {
var body: some View {
GeometryReader { geo in
Rectangle().fill(Color.blue)
Text("Hello world").padding(.top, geo.size.height * 3 / 4)
}
.frame(height: 150)
Spacer()
}
}
However, there are caveats / not very obvious behaviors
1
View modifiers apply to anything up to the point that they are applied, and not to anything after. An overlay / background that is added after .edgesIgnoringSafeArea(.all) will respect the safe area (not participate in ignoring the safe area).
This code renders "Hello world" inside the safe area, while the blue rectangle ignores the safe area:
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.edgesIgnoringSafeArea(.all)
.overlay(VStack {
Text("Hello world")
Spacer()
})
Spacer()
}
}
2
Applying .edgesIgnoringSafeArea(.all) to the background makes GeometryReader ignore the SafeArea:
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.overlay(GeometryReader { geo in
VStack {
Text("Hello world")
// No effect, safe area is set to be ignored.
.padding(.top, geo.safeAreaInsets.top)
Spacer()
}
})
.edgesIgnoringSafeArea(.all)
Spacer()
}
}
It is possible to compose many layouts by adding multiple overlays / backgrounds.
3
A measured geometry will be available to the content of the GeometryReader. Not to parent or sibling views; even if the values are extracted into a State or ObservableObject. SwiftUI will emit a runtime warning if that happens:
struct MyView : View {
#State private var safeAreaInsets = EdgeInsets()
var body: some View {
Text("Hello world")
.edgesIgnoringSafeArea(.all)
.background(GeometryReader(content: set(geometry:)))
.padding(.top, safeAreaInsets.top)
Spacer()
}
private func set(geometry: GeometryProxy) -> some View {
self.safeAreaInsets = geometry.safeAreaInsets
return Color.blue
}
}
I tried with the previewLayout and I see what you mean. However, I think the behavior is as expected. The definition of .sizeThatFits is:
Fit the container (A) to the size of the preview (B) when offered the
size of the device (C) on which the preview is running.
I intercalated some letters to define each part and make it more clear:
A = the final size of the preview.
B = The size of what you are modifying with .previewLayout(). In the first case, it's the VStack. But in the second case, it's the GeometryReader.
C = The size of the screen of the device.
Both views act differently, because VStack is not greedy, and only takes what it needs. GeometryReader, on the other side, tries to have it all, because it does not know what its child will want to use. If the child wants to use less, it can do it, but it has to start by being offered everything.
Perhaps if you edit your question to explain exactly what you would like to accomplish, I can refine my answer a little.
If you would like GeometryReader to report the size of the VStack. you may do so by putting it inside a .background modifier. But again, I am not sure what's the goal, so maybe that's a no go.
I have written an article about the different uses of GeometryReader. Here's the link, in case it helps: https://swiftui-lab.com/geometryreader-to-the-rescue/
UPDATE
Ok, with your additional explanation, here you have a working solution. Note that the Preview will not work, because safeInsets are reported as zero. On the simulator, however, it works fine:
As you will see, I use view preferences. They are not explained anywhere, but I am currently writing an article about them that I will post soon.
It may all look too verbose, but if you find yourself using it too often, you can encapsulate it inside a custom modifier.
import SwiftUI
struct InsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
typealias Value = CGFloat
}
struct InsetGetter: View {
var body: some View {
GeometryReader { geometry in
return Rectangle().preference(key: InsetPreferenceKey.self, value: geometry.safeAreaInsets.top)
}
}
}
struct ContentView : View {
var body: some View {
MyView()
}
}
struct MyView : View {
#State private var topInset: CGFloat = 0
var body: some View {
VStack {
CustomView(inset: topInset)
.padding(.horizontal)
.padding(.bottom, 64)
.padding(.top, topInset)
.background(Color.blue)
.background(InsetGetter())
.edgesIgnoringSafeArea(.all)
.onPreferenceChange(InsetPreferenceKey.self) { self.topInset = $0 }
Spacer()
}
}
}
struct CustomView: View {
let inset: CGFloat
var body: some View {
VStack {
HStack {
Text("C \(inset)").color(.white).fontWeight(.bold).font(.title)
Spacer()
}
HStack {
Text("A").color(.white)
Text("B").color(.white)
Spacer()
}
}
}
}
I managed to solve this by wrapping the page main view inside a GeometryReader and pass down the safeAreaInsets to MyView. Since it is the main page view where we want the entire screen thus it is ok to be as greedy as possible.