Dynamic height for ScrollView content not working - swiftui

I have content in my ScrollView that is sometimes short and other times longer than the screen. I'm trying to stretch the content to the bottom if it is too short, but no matter what I do the content stays short:
This is what the code looks like:
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
Text("Header")
.padding()
VStack {
Text("Content")
Image(systemName: "bell")
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.systemBackground))
}
}
.background(Color.yellow.ignoresSafeArea())
}
}
I would've expected the Spacer() in my VStack to stretch it to the end. Or the maxHeight: .infinity to help me out as well, but the content always stays short even tho the ScrollView is the screen height (because of the yellow background on it). This becomes more of a problem on iPad when the one screen shows the big gap. How can this be solved for various screen height?

Using #CenkBilgen's idea of using an overlay + GeometryReader, you could do the following:
struct ContentView: View {
var body: some View {
Color.clear
.overlay(GeometryReader { geo in
ScrollView {
VStack {
ForEach(1..<10) {
Text("\($0)").padding()
}
}
HStack { Spacer() }
}
.clipped()
.frame(width: geo.size.width, height: geo.size.height)
})
}
}

Related

Display View on top off bottom sheet

I'm trying to implement a view that displays error message for my whole app.
I want this view to always be above every other view, but I also use sheets in my app and in that case the error message is hidden behind the sheet, since the sheet is displayed above every other view.
Here is a View to reproduce my situation:
struct AppView: View {
#State var isPresentingSheet = false
var body: some View {
ZStack {
VStack {
Button("Toggle sheet") {
isPresentingSheet.toggle()
}
}
.sheet(isPresented: $isPresentingSheet) {
Text("Im above everything else")
}
VStack {
HStack {
Image(systemName: "xclose")
Text("I want to be even above the sheet")
}
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(Padding.l)
.background(Color.red.opacity(0.2))
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(Color.red),
alignment: .bottom
)
Spacer()
}
}
}
}
I want to know if it's possible to display a view above a sheet, but to me it looks like the sheet is in a completely different window?
But maybe it's possible to create a custom sheet that moves in from the top and is displayed above other native sheets?
If anybody is interested, I have created a custom bottom sheet with simple controls and snap functionality.
The bottom sheet has a zIndex of 1 so you can easily place views above it with a greater zIndex.
You can find it here: AlternativeSheet.
Here is the code to achieve the view of the image above.
import AlternativeSheet
...
ZStack {
VStack {
Button("Toggle sheet") {
isPresentingSheet.toggle()
}
}
.alternativeSheet(isPresented: $isPresentingSheet, snaps: [0.95]) {
Text("Im above everything else")
}
.isDraggable()
.dampenDrag()
VStack {
HStack {
Image(systemName: "xclose")
Text("I want to be even above the sheet")
}
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(Padding.l)
.background(Color.red.opacity(0.3))
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(Color.red),
alignment: .bottom
)
Spacer()
}
}

Why do the views extend wider than the screen?

Edit: Substitute your "system name:" of choice. "pencil.circle" works fine. "edit" is not a valid SF Symbol.
(I've simplified my code so you can cut and paste. That's why you see .frame, resizable, etc. where much simpler code might your first instinct.)
I have created a view which is a vertical list of row items (table view).
Each row item has a horizontal view with two images inside it.
The images take up too much space and do not fit correctly on the screen:
import SwiftUI
#main
struct StackOverflowDemoApp: App {
var body: some Scene {
WindowGroup {
TandemView()
}
}
}
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(height: 80)
.aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(width: 200, height: 80)
}
.padding()
.fixedSize()
}
}
struct TandemView_Previews: PreviewProvider {
static var previews: some View {
TandemView()
}
}
The above is the closest I can get to the desired layout (it just needs to fit horizontally). I experimented with GeometryReader but that did not produce desired results.
Here are some things I tried:
The code as provided
NoConstraintsOnPencilOrHStack
NoConstraintsOnTandemView
NoConstraintsOnImageInPaddedViewButWithFrameConstraint
I am trying to get a row view which consists of two Images (my actual source consists of UIImage objects) that fits within the width of the screen.
Edit:
After Accepting cedricbahirwe's spot-on response, I was able to simplify the code further. New results:
I added at the top level
TandemView()
.padding(.horizontal)
I removed:
// Spacer()
at the end of PaddedImageView
updated TandemView -- changed both frames and removed 3 lines:
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(width: 80, height: 80)
// .aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(height: 80)
}
// .padding()
// .fixedSize()
}
}
This is happening because of the layout of PaddedImageView View, you can actually remove the Spacer since it is not needed there.
So change
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
to
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
}
}
Note:
SwiftUI Engine infers the layout of your view from the implementation of the body property. It's recommended to have one Parent View inside the body property.

How to position text and a button in this manner?

This is an example of what I am trying to do.Link to image of design to implement. I am unsure how to position a button and text at the top of the screen in this manner coding in swiftui. Alternatively I thought I could use the navigation bar inline and customise that but I am unsure.
var body: some View {
VStack (alignment: .trailing) {
HStack(spacing:10) {
Button(action: {
}) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
//padding(.leading,40)
Spacer()
}
.padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top)
.background(customPurpleColour)
Spacer()
}
.edgesIgnoringSafeArea(.top)
}
}
Seems like you're on the right track. In order to keep the title centered, it seemed easier to make a ZStack. The menu button gets it's own .leading-aligned VStack and then the title goes on top of that.
The edgesIgnoringSafeArea and padding can be simplified so that you don't have to use the screen size safe areas.
struct ContentView: View {
var body: some View {
VStack {
ZStack {
VStack {
Button(action: { }) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}.padding(.leading)
}.frame(maxWidth: .infinity, alignment: .leading)
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
}
.background(Color.purple.edgesIgnoringSafeArea(.top))
Spacer() //other content goes here
}
}
}

How to Handle Safe Area Space in SwiftUI While Ignoring It

I want to completely color the background of my app while at the same time positioning content at the top so that its far enough from the status bar and top notch on those devices that have it.
If I do something like this:
#main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
VStack {
Text("Top of my view")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.yellow)
}
}
}
This displays the text at the top of the content, below the safe area. But only the content area is yellow. The safe areas are white.
So I add .edgesIgnoringSafeAreas(.all) below the .background modifier.
Now my text is below the notch (or smack up at the top of the screen below the Status bar text) and not visible.
I don't want to randomly guess at a top padding because that might be fine on phones with notches but look wrong on phones or iPads without notices.
If I place my VStack inside of a GeometryReader, the reader.safeAreaInsets.top is zero (0) when the top safe area is ignored.
I hope this a clear enough question. Has anyone run into this and have a solution?
You need to apply the edgesIgnoringSafeArea modifier to Color.yellow only.
A possible solution is to use a ZStack:
#main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ZStack {
Color.yellow
.edgesIgnoringSafeArea(.all)
VStack {
Text("Top of my view")
Spacer()
}
}
}
}
}
One way to fix it is to apply .edgesIgnoringSafeArea(.all) just to the Color.yellow inside of .background():
#main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
VStack {
Text("Top of my view")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.yellow.edgesIgnoringSafeArea(.all))
}
}
}

How position HStack in bottom of my screen no use the spacer()

I want move the HStack in bottom of my screen no use the spacer() because when use the spacer move the logo for top of my screen.
Here is a solution without using Spacer:
struct ContentView: View {
var body: some View {
VStack {
VStack {
Text("Image")
Text("Some text")
}.frame(minHeight: 0, maxHeight: .infinity)
HStack {
Text("Bottom")
}
}
}
}
I know you asked for no spacer, but this code shows you can use them without your logo going to the top of the screen. Alternatively you can use ".position(CGPoint(...))"
struct ContentView: View {
var body: some View {
VStack {
Spacer()
VStack (alignment: .center) {
Image("your-image").resizable().frame(width: 90, height: 95)
Text("TCheck time").font(.title).foregroundColor(.gray)
}.padding()
Spacer()
HStack (alignment: .bottom) {
Text("2020 SplitWay").font(.subheadline).foregroundColor(.gray)
}
}
}
}