How spacing is calculated in HStack - ios

In SwiftUI when a horizontal array of Circles are made like this:
HStack(spacing : 4) {
Foreach(0..<5) { index in
Circle()
}
}
How the (horizontal) spacing value of 4 is applied? Is it between the centre of two circles or from their edges instead?

Building on top of #Asperi's comment: the spacing is applied in the same way it's applied to Text or Button standard views, which is between the frames of the views.
If you click on an element in the preview (this doesn't work when on live preview mode), you can see the frame of an element outlined in blue. The spacing is applied between the edges of the frames of each view.

Related

How do I vertically center an icon with a multiline text label's first line of text?

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)

Context menu preview not with rounded corners in SwiftUI

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 :

How to stop GeometryReader changing value when keyboard opens

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)

SwiftUI: Change Popover Arrow Color

In SwiftUI, how does one change the color of the arrow that connects a popover to its anchor point?
When working with the underlying UIPopoverController outside of SwiftUI, I believe it's done by changing the backgroundColor property, but I don't see a way to access that here. Even setting background as the very last modifier only changes the view within the popover; not the popover itself.
For example, adding the following code to a view:
#State private var showDetailedView: Bool = false
// ...
.popover(isPresented: self.$showDetailedView) {
Text("Hello!")
.padding()
.background(Color.red)
}
.onTapGesture {
self.showDetailedView = true
}
...results in an arrow that's still the default background color (this example taken from native macOS in Dark Mode):
...and like this on iOS (running via Catalyst), which is even worse!
Here is a pure SwiftUI solution using GeometryReader and two .frame calls. The key idea is to make a background larger than the size of your presented view. Since SwiftUI does not clip contents at this moment, this will override the default background on the popover arrow.
Do notice that this only works with a solid background. In Catalyst, a solid background is already painted so transparent content would reveal the ugly black as you have posted. We might have to resort to things like UIViewRepresentable for such case.
Consider the following example that changes the color of an arrow on the top edge:
.background(GeometryReader { geometry in
Color
.white
.frame(width: geometry.size.width,
height: geometry.size.height + 100)
.frame(width: geometry.size.width,
height: geometry.size.height,
alignment: .bottom)
})
Explanation:
The first inner frame creates a white rectangle that is 100px higher than your presented view.
The second outer frame creates a new frame that is of the same size as your presented view. this is achieved through the GeometryReader.
The alignment: argument in the second outer frame makes sure that these two frames align on the bottom.
Since the outer frame is as large as the GeometryReader, it fills the whole background of your presented view.
The "overflowed" content overrides the default black arrow color.
To make this work with arbitrary arrow edge, you might want to change the inner frame, increasing both the width and height. As for the alignment for outer frame, using the default argument of .center should work.

Why is this view centered horizontally when its aligned to a vertical axis

Okay so I am new to the Swift programmming language and IOS platform however I have a strong background in mobile development on other platforms such as Android/WP/Xamarin so I decided to just dive in and learn the layout system starting with Auto Layout.
Here I am trying to center a view horizontally and align it to the bottom of the screen using the Auto Layout API via PureLayout. When I set the view to align to the horizontal axis of it's superview this is result I get.
Code
override func updateViewConstraints() {
if(!didSetupConstraints)
{
blackView.autoPinToBottomLayoutGuideOfViewController(self, withInset: 10)
blackView.autoSetDimensionsToSize(CGSize(width:50,height: 50))
blackView.autoAlignAxisToSuperviewAxis(.Horizontal)
didSetupConstraints=true
}
super.updateViewConstraints()
}
Result
However when I set the view to align to the vertical axis I get the desired end result
Code
override func updateViewConstraints() {
if(!didSetupConstraints)
{
blackView.autoPinToBottomLayoutGuideOfViewController(self, withInset: 10)
blackView.autoSetDimensionsToSize(CGSize(width:50,height: 50))
blackView.autoAlignAxisToSuperviewAxis(.Vertical)
didSetupConstraints=true
}
super.updateViewConstraints()
}
Result
Even though I got the result that I wanted, I did not feel good about proceeding without properly understanding why it got aligned in the center horizontally by setting the view to align to it's superview's vertical axis. So can someone explain to me the whole notion of aligning to axes and how they work in Auto Layout. Thanks in advance.
Being centered horizontally and aligning to the vertical axis are the same thing.
The vertical axis is a imaginary vertical line that is centered between the views left and right edge. So if a view is aligned with it, the view will be positioned anywhere along that vertical line, causing the view to be entered horizontally.
The source for Pure Layout also has a definition and shows how they map to regular auto layout if that helps.
/** A vertical line equidistant from the view's left and right edges. */
ALAxisVertical = NSLayoutAttributeCenterX,
/** A horizontal line equidistant from the view's top and bottom edges. */
ALAxisHorizontal = NSLayoutAttributeCenterY,

Resources