I have a few VStacks in my SwiftUI ContentView and I'm wondering how calculate remaning vertical space at the bottom of screen. Reason is to draw chart there and I need to know y-axis scale.
Thanks.
Here is a demo of possible approach - use GeometryReader that consumes all available space and gives metrics
struct DemoView: View {
#State private var height = CGFloat.zero
var body: some View {
VStack {
View1()
View2()
// GeometryReader consumes all remaining space here
GeometryReader { gp in
// use gp.size here
}
}
}
}
Related
Pretty simple question. I have a custom geometry-based shape that I want to utilize the current (i.e. default) font size to determine calculations for its rendering. For instance, if the font size is 12, I want the corner radius to be 1/4 that side, or three. But I'm not sure how to get the current font's metrics. Is it possible?
The reasoning is my control has a text component. If someone applies a font to it and changes the size as a result, I want to update my geometry to match that new size. So is it possible?
You can't get the typography of the font but you can get the View size or use ScaledMetric
import SwiftUI
struct FontSizeView: View {
//Use the View's size
#State var textSize: CGSize = .zero
//Apple's solution to adjusting views using the font size as a reference
#ScaledMetric var subViewSize: CGFloat = 48
var body: some View {
VStack{
Text("Hello, World!")
.modifier(ViewSizeViewModifier(size: $textSize))
Circle()
.frame(width: subViewSize, height: subViewSize)
}
}
}
struct ViewSizeViewModifier: ViewModifier{
#Binding var size: CGSize
func body(content: Content) -> some View {
content
.background(
GeometryReader{ proxy in
Color.clear
.onAppear(){
//initial value
size = proxy.size
}.onChange(of: proxy.size) { newValue in
//Updates with changes to orientation and such.
size = newValue
}
}
)
}
}
I have a VStack with some content at the top of my app, then a ScrollView on the bottom, with these views being seperated with a Divider. Is there any way to offset the scrollView such that it starts slightly tucked under the Divider and the top view?
Here is an example image of what I want:
The numbers are in a ScrollView and the top content is simply Color.white in this example.
If I apply a simply y offset, though, I get this:
The number is vertically shifted up, but not "tucked" under.
Is there an easy way to get the "tucked" result? I'm sure I could use a ZStack or something, but that seems like a lot of work, especially because I don't know how large the top content will be.
Example Code:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
Color.white.frame(height: 100)
Divider()
ScrollView {
ForEach(0..<20) { number in
Text("\(number)")
}
}
.offset(y: -8)
}
}
}
I assume you just need padding for scroll view, like
ScrollView {
ForEach(0..<20) { number in
Text("\(number)")
}
}
.padding(.top, -8) // << here !!
.clipped()
I want to make a view "swipeable", however this view only covers a small share of the whole screen. Doing:
var body: some View {
ZStack {
Text("ABC")
.gesture(DragGesture().onChanged(onChanged))
}
}
func onChanged(value: DragGesture.Value) {
print(value.startLocation)
print("onChanged", value.location)
}
Gives me relative values where the user has swiped - however, without knowing the exact location of the view, I'm unable to know where the user's touch is on the screen.
In other words, when the Text is in the center of the screen, going all the way to the right edge gives 200px. But let's say the Text moved to the left, then it would be almost 400. I tried putting another gesture on the ZStack to save the initial value, however it appears you can't have multiple gestures within each other.
How can I make the listener translate the values so the left edge is 0 and the right edge is displayWidth in px, regardless of where the Text is?
You can make use of coordinate spaces, and use that with DragGesture.
Code:
struct ContentView: View {
#State private var location: CGPoint = .zero
var body: some View {
VStack(spacing: 30) {
Text("ABC")
.background(Color.red)
.gesture(
DragGesture(coordinateSpace: .named("screen")).onChanged(onChanged)
)
Text("Location: (\(location.x), \(location.y))")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.coordinateSpace(name: "screen")
}
private func onChanged(value: DragGesture.Value) {
location = value.location
}
}
I am trying to use the whole iPhone area for my app.
I have this HStack at the top, used to create a custom toolbar.
var body: some View {
VStack (spacing:0) {
MyTopbar()
// other controls
Spacer()
}
.edgesIgnoringSafeArea(.top)
This appears like this on new devices with a notch and old devices without a notch. The notch cuts my menu.
I can solve that by adding a spacer with a frame height before MyTopbar() on the vertical stack but first of all this seems to be a very awful solution. First I have to guess a height for that spacer. Then I have to detect if the device has a notch or not (?).
Is there a better way?
You can think of it as layers (content that respects safe area and content that doesn't).
Something like this perhaps:
struct ContentView: View {
var body: some View {
ZStack {
Color.blue.ignoresSafeArea() // Whatever view fills the whole screen
VStack (spacing:0) {
MyTopbar()
// other controls
Spacer()
}
}
}
}
A possible solution to add clear color with safe area height. No need for much calculation.
var body: some View {
VStack (spacing:0) {
Color.clear.frame(height: Color.clear.frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
MyTopbar()
// other controls
Spacer()
}
.edgesIgnoringSafeArea(.top)
Im trying to calculate the screen safe area size in a SwiftUI app launch so I can derive component sizes from the safe area rectangle for iOS devices of different screen sizes.
UIScreen.main.bounds - I can use this at the start but it gives me the total screen and not the safe area
GeometryReader - using this I can get the CGSize of the safe area but I cant find a way to send this anywhere - tried using Notifications and simple functions both of which caused errors
Finally I tried using the .onPreferenceSet event in the initial view then within that closure set a CGSize variable in a reference file, but doing that, for some reason makes the first view initialise twice. Does anyone know a good way to get the edge insets or the safe area size at app startup?
More simpler solution :
UIApplication.shared.windows.first { $0.isKeyWindow }?.safeAreaInsets.bottom
or shorter:
UIApplication.shared.windows.first?.safeAreaInsets.top
Have you tried this?
You can use EnvironmentObject to send the safe area insets anywhere in your code after initializing it in your initial View.
This works for me.
class GlobalModel: ObservableObject {
//Safe Area size
#Published var safeArea: (top: CGFloat, bottom: CGFloat)
init() {
self.safeArea = (0, 0)
}
}
Inside SceneDelegate.
let globalModel = GlobalModel()
let contentView = ContentView().environmentObject(globalModel)
Inside your initial view.
struct ContentView: View {
#EnvironmentObject var globalModel: GlobalModel
var body: some View {
ZStack {
GeometryReader { geo in
Color.clear
.edgesIgnoringSafeArea(.all)
.onAppear {
self.globalModel.safeArea = (geo.safeAreaInsets.top, geo.safeAreaInsets.bottom)
}
}
SomeView()
}
}
}