I have a view with a navigation bar, some other views with fixed height on the top and some other views with fixed height on the bottom (including a tab bar). What I'm trying to achieve is to constrain content to fix the remaining space in the center of the screen without using a scroll view.
The app needs to run on iPhone SE (1st generation) which is the model with the minimum available height. This content includes some multiline text and some buttons with multiline text labels so the idea is to scale the font and the buttons height.
I tried to compute the difference between the screen height and the top and bottom content, but some element (e.g. the padding) are dynamically computed by the system and I cannot assume to be fixed.
Here is the model of my view.
var body: some View {
VStack {
// Top content with fixed size
VStack {
Text("Here it's some multiline text")
Button(action: { }, label { Text("Some other multiline text label") }
// Other buttons
Button(action: { }, label: { Text("Last multiline text label") }
}
/*
What my frame height should be?
.frame(height: ?)
*/
// Bottom content with fixed size
}
}
Related
Problem
We want an icon that is centered vertically with the first line of text of an adjacent view, and we want it to be aligned regardless of text length, icon size or font size. In other words, we want the following result:
The desired result is above: the red icon is aligned with the center of the first line of textIn SwiftUI, the default vertical alignment options are:
.top
.center
.bottom
.firstTextBaseline
.lastTextBaseline
.center is what we want for a single line of text, but if we use it for multiline text we get:
And if we use .firstTextBaseline, we are close but it doesn't quite center the image (this is more obvious as the text size changes):
Solution
We need to use a combination of alignment guides and arithmetic. The solution in code is below:
struct ContentView: View {
var body: some View {
HStack(alignment: .firstTextBaseline) {
Rectangle()
.fill(.red)
.frame(width: 16, height: 16)
.alignmentGuide(.firstTextBaseline) { context in
context[VerticalAlignment.center]
}
Text(greeting)
.font(.title3)
.background(.blue)
.alignmentGuide(.firstTextBaseline) { context in
let remainingLine = (context.height - context[.lastTextBaseline])
let lineHeight = context[.firstTextBaseline] + remainingLine
let lineCenter = lineHeight / 2
return lineCenter
}
}
}
}
In the above example, the rectangle center is aligned with the center of the first line:
How it works
The Rectangle is a stand-in for an icon.
The .firstTextBaseline of alignmentGuide is actually only used as a key to match the against the HStack alignment. So it doesn't make a difference except that the HStack, Rectangle and Text all are using the same vertical alignment scheme.
The context[VerticalAlignment.center] of the Rectangle is returned to indicate that the center of the Rectangle will be be aligned with the other alignment guides of its view siblings (thus centering the Rectangle)
We need to do additional math for the Text to find where the center of the first line of text lies. The arithmetic uses the lastTextBaseline and the height of the entire Text to calculate the region below the text baseline. By adding this to the firstTextBaseline and dividing by 2, we determine where the center of the first line of text lies. The result of this calculation is returned as the alignment guide, for alignment with its siblings (in this case, the center of the Rectangle that we returned already)
When using a plain styled List in SwiftUI with more than one Text view inside a VStack as show below, the preview of the view when showing its context menu doesn't have corner radius. If you remove one of the Text views it will have corner radius. Also the rows that you need to scroll down to will also show corner radius most of the time. I've tried using the contentShape modifier with RoundedRectangle but doesn't fix it. How can I get it to show with corner radius all the time?
List {
ForEach(1...20, id: \.self) { _ in
VStack {
Text("Hello")
Text("World")
}.contextMenu {
Button {} label: { Text("Hello") }
}
}
}.listStyle(.plain)
I thinks you should fill a bug to Apple in this case.
After some try I have notice that if you define a frame for your Label , the view is correctly rounded.
I tryed to fixedSize the text without success :
Text("world").fixedSize()
The problem seems to come from _UIMorphingPlatterView, more precisely the _UIPlatterClippingView which use _UIPortalView.
If the frame is not fixed the clip is not apply correctly.
The debug view hierarchy give this :
I'm struggling to find documentation to support this but it seems as though the values of
GeometryReader.size.width & height change when the keyboard opens. This can be proven through something like:
var body: some View {
TabView {
GeometryReader { g in
Rectangle()
.frame(width:g.size.width/2,height:g.size.height/20)
TextField(...)
}
}
}
which shows the rectangle resizing when the keyboard opens by clicking on the textfield.
How would I prevent this from happening? I want to specify the frame size relative to screen size to support many screen sizes...
You don't need a geometry reader to know the screen's size. you can get screen's dimensions using UIScreen.main.bounds.width and UIScreen.main.bounds.height.
Note width always shows the horizontal-dimension's size, and height always shows the vertical one (incase of screen rotation)
Add this to the GeometryReader:
.ignoresSafeArea(.keyboard)
Please have a look at this image before reading the question
I'm currently trying to align the red view's vertical center (/centerY) to the bottom edge of the green view in a SwiftUI View.
I'm coming from UIKit where I would solve this with something like viewA.centerYAnchor.constraint(toEqual: viewB.bottomAnchor)
But how would you solve this the SwiftUI way? I have kind of the following hierachy:
VStack {
ZStack {
Image("someImage")
Text("Awesome Title") // <- align center to the Image's bottom edge
.frame(width: 200, height: 130)
}
Spacer()
}
I found the solution:
Set the ZStacks alignment to .bottom. Now the red view will be aligned to the green views bottom edge. Thanks to #Andrew. But this is not enough:
Set the red views .alignmentGuide to the following:
-> .alignmentGuide(.bottom) { d in d[.bottom] / 2 }
Explanation: Now the green view's bottom edge will be aligned to 50% of the red view's height! Awesome!
If you remove the frame from the text and add a bottom alignment to the ZStack, it will give you the desired effect.
VStack {
ZStack (alignment: .bottom) {
Image("someImage")
Text("Awesome Title") // <- align center to the Image's bottom edge
}
Spacer()
}
result:
i'm trying to code a simple login page on my app. I started using SwiftUI on my newlly updated Mac OS Catalina. The Apple documentation is still lacking a lot.
I need to center a VStack vertically on a Scrollview ocupying the whole page with a "limit" on it's width of 400.
Something like this:
ScrollView(.vertical) {
VStack {
Text("Hello World")
}
.frame(maxWidth: 400, alignment: .center)
}
It was easy with UIScrollView, just needed to set the ContentView to fill height and width and then centering a Vertical StackLayout inside the Content View but now with SwiftUI i just wonder..
The goal is something like this (Credit to the author)
If someone is wondering why i want everything inside a scrollview, it's beacause my form is quite big and i expect the user to use both landscape and portrait view so i really need the content to be scrollable, bear in mind also that in a Ipad the form doens't fill the whole screen that's why i want it centered vertically.
You can vertically center content in a scroll view by using GeometryReader to get the parent view's dimensions and setting the scroll view's content's minHeight to the parent's height.
When the content is too big to fit vertically it'll just scroll like normal.
For example:
var body: some View {
GeometryReader { geometry in // Get the geometry
ScrollView(.vertical) {
VStack {
Text("Form goes here")
.frame(maxWidth: 400) // Set your max width
}
.padding()
.background(Color.yellow)
.frame(width: geometry.size.width) // Make the scroll view full-width
.frame(minHeight: geometry.size.height) // Set the content’s min height to the parent
}
}
}
I've build a more generic view based on #Alex answer
/// Custom vertical scroll view with centered content vertically
///
struct VScrollView<Content>: View where Content: View {
#ViewBuilder let content: Content
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
content
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
}
You can use it anywhere in your app like this
var body: some View {
VScrollView {
VStack {
Text("YOUR TEXT HERE")
}
}
}