How to control height and padding of GeometryReader - swiftui

I have two Text Fields side-by-side. The first field should take up 25% of the width and the second field 75%. I also have a view modifier that adds 24px of horizontal padding.
Two issues
The second Text Filed does not respect the Horizontal padding on the right side. How do I get it to respect the padding?
The whole Geometry Reader is a square, its vertical size seams to match the horizontal. How do I get it to just conform to the height of the TextFields.
GeometryReader { geometry in HStack(spacing: 20) {
TextField("Country...", text: $country)
.textFieldStyle(TextFieldDarkFull())
.frame(
width: geometry.size.width * 0.25
)
TextField("Phone...", text: $phone)
.textFieldStyle(TextFieldDarkFull())
.frame(
width: geometry.size.width * 0.75
)
}
}
.modifier(HorizontalPadding24())

remove second TextField frame limitation (ie. modifier width: geometry.size.width * 0.75). So you fix first text field and let second one consume all remaining space dynamically.
it depends of your entire layout but you can try to fix vertical size of block, like
GeometryReader { geometry in HStack(spacing: 20) {
// content here
}
.fixedSize(horizontal: false, vertical: true) // << here !!

Related

Adjust Image Size of Image to Parent View Size

I just want to simply adjust the image size of image to the parent's view size.
Images in swiftUI are miss behaved children that simply will not adjust to their parent...
I wanna be able to call ImageCard("image").frame(decide the size of the image)
struct ImageCard: View {
let backgoundImage: String?
var body: some View {
ZStack{
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFit() // for filling image on ImageView
.cornerRadius(5)
.shadow(color: .gray, radius: 6, x: 0, y: 3)
}
}
}
If I understood correctly the intention was to fill proportionally, so
ZStack{
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !! // for filling image on ImageView
but in that case it can spread out of bounds, so it needs to be clipped in place of .frame applied, so either
ImageCard("image")
.frame(decide the size of the image)
.clipped() // << here !!
or, better, as already described inject dimension inside card and apply it there, like
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !!
.frame(decide the size of the image)
.clipped() // << here !!
.cornerRadius(5)
.shadow(color: .gray, radius: 6, x: 0, y: 3)
}
I wrote a package just for jmages, You give it the max height & width without needing the Image's exact dimensions & it'll maximize the size without being stretched: https://github.com/NoeOnJupiter/SImages
Usage:
DownsampledImage(.wrapped(UIImage(named: backgroundImage)))
.resizable(.wrapped(true))
.frame(width: width, height: height)
Plus this will downsample your image to the size it's displayed in, your memory usage will be much lower since it depends on the resolution of the Image.
Note: If you wanna bound the image to the whole View, use UIScreen.main.bounds for the frame.
You can simply include two variables for width and height to make your ImageCard() become adjustable at any time since you mentioned that you wanted:
I wanna be able to call ImageCard("image").frame(decide the size of the image)
You don't need ZStack or scaleToFit() because you wanted to decide the size whenever the ImageCard() is called. Code is below the image.
struct DemoView: View {
var body: some View {
ImageCard(backgroundImage: "Swift", width: 300, height: 500)
}
}
struct ImageCard: View {
let backgroundImage: String?
let width: CGFloat
let height: CGFloat
var body: some View {
Image(backgroundImage ?? "")
.resizable()
.frame(width: width, height: height)
}
}

SwiftUI - view expand from the bottom of frame

I'd like to display a number of values as a continuous value from 0 to 1. I'd like them to grow from the bottom up, from 0 displaying no value, to 1 displaying a full height.
However, I'm unable to make it "grow from the bottom". I'm not sure what a better term for this is - it's a pretty simple vertical gauge, like a gas gauge in a car. I'm able to make it grow from the middle, but can't seem to find a way to make it grow from the bottom. I've played with mask and clipShape and overlay - but it must be possible to do this with just a simple View, and calculations on its height. I'd specifically like to able to show overlapping gauges, as the view below demonstrates.
My ContentView.swift is as follows:
import SwiftUI
struct ContentView: View {
// binding values with some defaults to show blue over red
#State var redPct: CGFloat = 0.75
#State var bluePct: CGFloat = 0.25
let DISP_PCT = 0.8 // quick hack - the top "gauge" takes this much so the sliders display below
var body: some View {
GeometryReader { geom in
VStack {
ZStack {
// neutral background
Rectangle()
.fill(Color.gray)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT)
// the first gauge value display
Rectangle()
.fill(Color.red)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)
// the second gauge value, on top of the first
Rectangle()
.fill(Color.blue)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
}
HStack {
Slider(value: self.$redPct, in: 0...1)
Text("Red: \(self.redPct, specifier: "%.2f")")
}
HStack {
Slider(value: self.$bluePct, in: 0...1)
Text("Red: \(self.bluePct, specifier: "%.2f")")
}
}
}
}
}
As you play with the sliders, the red/blue views grows "out" from the middle. I would like them to grow "up" from the bottom of its containing view.
I feel like this is poorly worded - if any clarification is needed, please don't hesitate to ask!
Any help would be greatly appreciated!
You can't have them all in the same stacks. The easiest way to do this is to have your gray rectangle be your case view, and then overlay the others on top in VStacks with Spacers like this:
// neutral background
Rectangle()
.fill(Color.gray)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT)
.overlay (
ZStack {
VStack {
Spacer()
// the first gauge value display
Rectangle()
.fill(Color.red)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)
}
VStack {
Spacer()
// the second gauge value, on top of the first
Rectangle()
.fill(Color.blue)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
}
}
)
The overlay contains them, and the spacers push your rectangles down to the bottom of the stacks.

SwiftUI: Bottom aligning text and images of different frame sizes

I am attempting to bottom align an imageIcon and 2 texts of different font sizes, but for some reason, the text with the larger font does not seem to align properly.
HStack(alignment: .bottom) {
Image(uiImage: UIImage(named: "baseline_accessible_black_24pt") ?? UIImage())
.resizable()
.frame(width: 24, height: 24)
.background(Color.green)
Text("2")
.font(.system(size: 56))
.fontWeight(.bold)
.background(Color.blue)
Text("mins")
.font(.body)
.background(Color.pink)
}
.background(Color.yellow)
Results:
Notice that the "2" has empty spaces at the bottom. How do I adjust the insets such that all UI components are aligned at the bottom?
You can align the images and text with the following VerticalAlignment. Just change the HStack alignment from .bottom:
HStack(alignment: .lastTextBaseline) {
/* ... */
}
Result (using temporary image):
With backgrounds
Without backgrounds
The Texts still have some space underneath, because of letters like j which extend below the text baseline. Without the background colors, you wouldn't notice.

GeometryReader inside a ScrollView collapses the GeometryReader, regardless of child sizes

I'm trying to make a sub view which contains a drawing which I want the size to be proportional to a fraction of the parent container.
This works fine except when put inside a scroll view somewhere up the hierarchy.
As seen from the screenshots when inside of the stack the loudest as expected. But when you put inside a scroll view the size of the geometry reader collapses to near zero and its children overflow its boundaries. As though it's rendering its children on a different Z index. In fact if you remove the wrapping VStack from the geometry render contents it doesn't fact layout all its children as though it were wrapped in a ZStack.
EDIT:
To be clear: the ScrollView is not owned by the component. The component should not be aware if it's in a ScrollView or not - it's just a poor helpless component, being thrown around. That's why I put the ScrollView into the preview code, not the component.
I've tried all sorts of combinations of fixed sizes and men and mags and ideal. The only work around I could find was this hacky solution - the edited part of the question near the bottom where the width reported by the geometry reader is captured through a workaround into a state variable and then re-used to set the frame size of a sibling view.
Note, I'm a complete beginner. There seems to be some interaction between the scroll view and the geometry reader that is beyond my current understanding.
Seems to be a confusing topic.
import SwiftUI
struct ScrollViewGeoReader: View {
var body: some View {
GeometryReader{ g in
VStack{
let width = g.size.width
Circle().frame(width: width/3, height: width/3, alignment: .center)
Text("inside geo")
Text("inside geo")
Text("inside geo \(width)")
Text("inside geo")
Text("inside geo")
}
.border(Color.green, width: 2)
}
.border(Color.red, width: 3)
}
}
struct ScrollViewGeoReader_Previews: PreviewProvider {
static var previews: some View {
VStack {
// VStack {
ScrollView {
ScrollViewGeoReader()
Text("Next scrollview item")
}
.border(Color.blue, width: 2)
}
}
}
ZStack (expected layout):
ScrollView (see how the red frame of the geometry read it has collapsed to size 10):
EDIT:
Also note that the same problem occurs with or without the circle, which is also a problem I have. So it's not chicken / egg as far as the width capturing is concerned, at least in terms of Circles. I would have though the Text components know their own size, and would tell the GR.
vs
Here is scratchy (w/o subviews separation) possible solution for layout. If/when internal subviews separated the geometry width can be injected by constructor arguments.
Tested with Xcode 12.
struct TestScrollViewWithGeometry: View {
var body: some View {
GeometryReader{ g in
ScrollView {
VStack{
let width = g.size.width
Circle().frame(width: width/3, height: width/3, alignment: .center)
Text("inside geo")
Text("inside geo")
Text("inside geo \(width)")
Text("inside geo")
Text("inside geo")
}
.border(Color.green, width: 2)
Text("Next scrollview item")
}
.border(Color.blue, width: 2)
}
.border(Color.red, width: 3)
}
}

How to expand frame by dx,dy in SwiftUI

For some reason the Text() view that I create sometimes doesn't fit the string correctly in its frame and instead draws the ellipsis -- while I am not explicitly defining the frame of the view. So I would like to "expand" the frame by a dx/dy amount in the x/y directions -- is there a view modifier that I can use for that? ex. Text(" Jx3 ").expand(dx: +4, dy: 0) Or maybe I'm not supposed to use extra space in the string, but then the background shape is too narrow.
viewForPoints(displayHistory[index].displayPoints)
.overlay(
Text(" \(displayHistory[index].abbreviation) ")
.foregroundColor(.gray)
.font(FONTO)
.offset(x: +8, y: +14)
.background(
Capsule()
.fill(Color.white)
.offset(x: +8, y: +14)
)
)
Try to use fixed size, as
.overlay(
Text(" \(displayHistory[index].abbreviation) ")
.fixedSize()
)
Tested & works with some replicated code with Xcode 11.2 / iOS 13.2
Note: overlay uses same frame as its owner, so probably it worth considering ZStack for your case instead.