How come ZStack is not working with device height in SwiftUI? - ios

So I was working on a project and whenever I make a ZStack to get a background color, the subsequent VStack is basically somehow attached to the ZStack. I thought ZStack were similar to objects being stacked on top of each other.
However, the closer I make the bottomHeightMultiplier var closer to 100% the more the Text in the Vstack gets pushed off the screen. I was just trying to create a background view with the top 20% of any device screen being white and the bottom 80% of the screen being green.
Unfortunately the Text("Enter bill total") just ends up getting pushed off the screen. If I put the Spacer() below the Text, it gets pushed to the top and beyond the safe area. Putting it above the Text pushes it to bottom and beyond the safe area.
import SwiftUI
struct CalculatorScreen: View {
var screenWidth = UIScreen.main.bounds.width
var screenHeight = UIScreen.main.bounds.height
var topHeightMultiplier: CGFloat = 0.20
var bottomHeightMultiplier: CGFloat = 0.80
var body: some View {
ZStack {
VStack {
Color.white
.frame(minHeight: screenHeight*topHeightMultiplier)
Color.green
.frame(minHeight: screenHeight*bottomHeightMultiplier)
}
VStack {
Spacer()
Text("Enter bill total")
.foregroundColor(.black)
}
}
}
}

This will do the trick. What really is happening is that when using UIScreen.bounds, the safe areas create a problem, making the view longer than the screen long is. That means that the inner VStack pushes on the outer ZStack and thus makes the ZStack also longer than the device is. Thus, no the objects within the ZStack are completely independent, however the size of the parent changes when that of a child changes, implicitly affecting other child objects in the ZStack. The second VStack will be as long as the ZStack due to the Spacer(), making it look like it has been pushed off the screen.
Additionally, the VStack for the colors had a standard padding (now added spacing: 0), creating a white line between the colors, eating up space as well which is not taken into account when calculating the height of the colors. My tip is to always use colors you can see when building blocks, that way you immediately spot safe area issues and unwanted paddings.
struct CalculatorScreen: View {
var topHeightMultiplier: CGFloat = 0.20
var bottomHeightMultiplier: CGFloat = 0.80
var body: some View {
GeometryReader { geometry in
ZStack {
VStack(spacing: 0) {
Color.purple
.frame(minHeight: geometry.size.height*topHeightMultiplier)
Color.green
.frame(minHeight: geometry.size.height*bottomHeightMultiplier)
}
.ignoresSafeArea()
VStack {
Spacer()
Text("Enter bill total")
.foregroundColor(.black)
}
}
}
}
}

Related

Why Does SwiftUI ScrollView Return To Top When GeometryReader Is Introduced?

The code shown below works as expected: one can scroll through the numbers as the list remains in the scroll position it had when the scrolling stopped. However, if the GeometryReader lines are uncommented the list of numbers always returns to the top when scrolling is stopped. I’m thinking the ScrollView thinks the list has scrolled past the end of the list and is bouncing back to the top. Is this expected behavior? If so, is there a way to adjust the code so the ScrollView retains its position in the list when scrolling stops?
I am aware that in the given code the GeometryReader is not used for any purpose. This is code I was using to try to figure out where the problem lay, and it starts happening as soon as the GeometryReader is introduced so I saw no need to clutter the demo code with unnecessary stuff. In fact, the problem remains even when the GeometryReader is put to use.
In the actual project on which I’m working the ScrollView contains another view which is where the GeometryReader is located so I’m not able to rearrange where the GeometryReader is in relation to the ScrollView.
I’m using Xcode 13.3.1 and Swift 5.6.
import SwiftUI
struct ContentView: View {
let colors: [Color] = [.yellow, .purple, .orange,
.blue, .pink, .green, .red]
let items = 1...50
let config = [
GridItem(.fixed(80))
]
let aspectRatio: CGFloat = 2/3
var body: some View {
VStack {
Spacer()
ScrollView {
// GeometryReader { geometry in
LazyVGrid(columns: config, spacing: 20) {
ForEach(items, id:\.self) { item in
Image(systemName: "\(item).square.fill")
.font(.largeTitle)
}
}
// }
}
.frame(maxHeight: 200)
Spacer()
}
}
}

SwiftUI - TabView automatically changes selection when size changes

I have a paging style TabView in a HStack with another view. I'm adding a selection binding to the TabView to programmatically control selection.
When a tab is tapped the second view in the HStack disappears and the TabView resizes to fill the space.
However, if this is done after scrolling through my TabView's contents what happens is the selection changes many times as the size changes, causing the TabView to erratically scroll through many pages, rather than stay on the same selection and resize.
To reproduce:
Create a new project
Replace ContentView with the following code
Scroll through ~20 pages
Tap on the rectangle (blue or red)
The selection will change by itself.
Tested on iOS 14.5
struct ContentView: View {
#State var showSidebar = true
#State var selection = 0
var body: some View {
HStack {
Rectangle()
.frame(width: showSidebar ? 100 : 0)
.frame(maxHeight: .infinity)
TabView(selection: $selection) {
ForEach(0..<100, id: \.self) { i in
Rectangle()
.foregroundColor(i % 2 == 0 ? .red : .blue)
.padding()
.onTapGesture {
withAnimation {
showSidebar.toggle()
}
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}

How to Increase size of one side of a frame in swiftUI? (iMessage Drag to reveal time effect)

I have been trying to get this effect in SwiftUi, iMessage like time-showing when a message is swiped in TableView
After searching and searching, I figured I would increase the width of my scrollView.
when I do this, it increases on both sides and offsets the recipients chat bubbles off screen to the left as well. Is there a way to only increase the width on one side of the .frame() of the ScrollView to hide just the time?
^Main Question^
Also, I tried offsetting the HStack off screen to the left (to hide time only, right element of HStack) which contains the time and the message bubble, although when I do this, the text is clipped and is not visible when I use .gesture(DragGesture). Is there a better way I can achieve this in swiftUI?
Quick Skeleton of my actual code below.
import SwiftUI
struct ContentView: View {
#State private var draggedOffset = CGSize.zero
var body: some View {
VStack{
ScrollView {
ForEach(0 ..< 20) { item in
VStack {
HStack {
Spacer()
Text("(Message text)")
.padding(30)
.clipShape(Rectangle())
.background(Color.blue)
.padding()
Text("TI:ME ")
.offset(x: 30)
}
}
}
}
.offset(x: draggedOffset.width)
.gesture(DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = CGSize.zero
}
)
Spacer()
Text("Message Text Field")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
So I figured out that due to the ScrollView in my project being rotated twice, I needed to use a negative x offset for it to work. Although still wondering about the frame.

Issues with SwiftUI List NavigationLink frame(height: CGFloat)

I am trying to display a custom view for my List Row, it is an HStack with an image, a title Text and another Text containing a short paragraph. I am having issues with the height of the NavigationLink item. I have tried setting the frame to a specific height but it does not display optimally on different devices (the small resolution difference between the iPhone XR and iPhone XS is enough to make me have to adjust the height manually).
Is there a way to have the frame height be set automatically? I have tried many things such as setting the frame height on my PlanetRow View, adding padding, setting the lineLimit to: 0, 3, 5, ... Whatever I do, if I don't set the height for the NavigationLink View, the PlanetRow extends past the NavigationLink View (visible beyond the List separators).
Here's the code for my views:
struct PlanetRow : View {
var planet: Planet
var body: some View {
HStack {
Image("Mars")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(planet.name)
.font(.largeTitle)
Text(planet.overview)
.font(.caption)
}
.lineLimit(nil)
}
}
}
struct PlanetList : View {
var body: some View {
NavigationView {
List(planetData.identified(by: \.id)) { planet in
NavigationLink(destination: PlanetDetail(planet: planet)) {
PlanetRow(planet: planet)
}
.frame(height: 150)
}
.navigationBarTitle(Text("Planets"))
}
}
}

SegmentedControl breaks when using ForEach

So I am trying out SwiftUI and unfortunately it still has some issues but I dont know if its on my side or its just buggy. I am trying to use a SegmentedControl alongside a ForEach which displays images but labels of SegmentedControl is gone.
This is my ContentView:
struct ContentView : View {
#State var filter = 0
var body: some View {
NavigationView {
SegmentedControl(selection: $filter) {
Text("Tag1").tag(0)
Text("Tag2").tag(1)
}.padding(.bottom)
ForEach(1...3) { spot in
ZStack {
Image("placeholder")
.frame(height: 200)
.clipped()
Text("Swift")
.font(.largeTitle)
.foregroundColor(.white)
}.padding(.bottom, -8)
}.navigationBarTitle(Text("Explore"))
}
}
}
I dont see why it should break but it does. Am I using it wrongly or is this just a bug?
Your SegmentedControl is stretched and the reason is your image. The SegmentedControl's width is stretched to the size of the image and you're setting a custom frame to the image without making it resizable. That's why your labels are out of bounds. Just add .resizable() to your image, this will fix your image's size and your labels will also appear:
Image("placeholder")
.resizable()
.frame(height: 200)
.clipped()

Resources