Reputation: 10466
Given an HStack like the following:
HStack{
Text("View1")
Text("Centre")
Text("View2")
Text("View3")
}
How can I force the 'Centre' view to be in the centre?
Upvotes: 11
Views: 6643
Reputation: 812
This works for me (tested with Swift 5.9)
HStack{
HStack {
Text("View1")
Spacer()
}
.border(Color.red)
Text("Centre")
HStack {
Spacer()
Text("View2")
Text("View3")
}
.border(Color.red)
}
To make it more generic and support wider range of use case, I create a custom View
struct CustomView<Leading, Center, Trailing>: View where Leading: View, Center: View, Trailing: View {
private let leading: () -> Leading
private let center: () -> Center
private let trailing: () -> Trailing
init(
@ViewBuilder leading: @escaping () -> Leading,
@ViewBuilder center: @escaping () -> Center,
@ViewBuilder trailing: @escaping () -> Trailing
) {
self.leading = leading
self.center = center
self.trailing = trailing
}
var body: some View {
HStack {
HStack {
leading()
Spacer()
}
center()
HStack {
Spacer()
trailing()
}
}
}
}
// Usage
CustomView(
leading: {
Button(action: {}, label: { Text("My button") })
},
center: {
Text("Center View")
},
trailing: {
HStack {
Image(systemName: "info")
Image(systemName: "magnifyingglass")
VStack {
Image(systemName: "globe")
Text("View")
}
}
}
)
Upvotes: 3
Reputation: 2335
My answer extends the accepted answer's idea (and takes Confused Vorlon's hint into account) to make the HStack
spread to the entire width of the available space and its centre element fixed in the centre:
HStack(spacing: .zero) {
Spacer()
.overlay {
HStack(spacing: .zero) {
Text("View 1")
Spacer()
}
}
Text("Centre")
Spacer()
.overlay {
HStack(spacing: .zero) {
Spacer()
Text("View 2")
}
}
}
This solution helped me, for example, while implementing a custom pagination UI:
Without this trick, it looked like this:
Upvotes: -1
Reputation: 258441
Here is possible simple approach. Tested with Xcode 11.4 / iOS 13.4
struct DemoHStackOneInCenter: View {
var body: some View {
HStack{
Spacer().overlay(Text("View1"))
Text("Centre")
Spacer().overlay(
HStack {
Text("View2")
Text("View3")
}
)
}
}
}
The solution with additional alignments for left/right side views was provided in Position view relative to a another centered view
Upvotes: 23
Reputation: 889
Asperis answer is already pretty interesting and inspired me for following approach:
Instead of using Spacers with overlays, you could use containers left and right next to the to-be-centered element with their width set to .infinity to stretch them out just like Spacers would.
HStack {
// Fills the left side
VStack {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 120, height: 200)
}.frame(maxWidth: .infinity)
// Centered
Rectangle()
.foregroundColor(Color.red)
.frame(width: 50, height: 150)
// Fills the right side
VStack {
HStack {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 25, height: 100)
Rectangle()
.foregroundColor(Color.red)
.frame(width: 25, height: 100)
}
}.frame(maxWidth: .infinity)
}.border(Color.green, width: 3)
I've put it in a ZStack to overlay a centered Text for demonstration:
Using containers has the advantage, that the height would also translates to the parent to size it up if the left/right section is higher than the centered one (demonstrated in screenshot).
Upvotes: 2
Reputation: 10466
the answer takes a handful of steps
For the view which has to fill the VStack, I use a Geometry Reader. This automatically expands to take the size of the parent without otherwise disturbing the layout.
import SwiftUI
//Custom Alignment Guide
extension HorizontalAlignment {
enum SubCenter: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[HorizontalAlignment.center]
}
}
static let subCentre = HorizontalAlignment(SubCenter.self)
}
struct CentreSubviewOfHStack: View {
var body: some View {
//VStack Alignment set to the custom alignment
VStack(alignment: .subCentre) {
HStack{
Text("View1")
//Centre view aligned
Text("Centre")
.alignmentGuide(.subCentre) { d in d.width/2 }
Text("View2")
Text("View3")
}
//Geometry reader automatically fills the parent
//this is aligned with the custom guide
GeometryReader { geometry in
EmptyView()
}
.alignmentGuide(.subCentre) { d in d.width/2 }
}
}
}
struct CentreSubviewOfHStack_Previews: PreviewProvider {
static var previews: some View {
CentreSubviewOfHStack()
.previewLayout(CGSize.init(x: 250, y: 100))
}
}
Edit: Note - this answer assumes that you can set a fixed height and width of the containing VStack. That stops the GeometryReader from 'pushing' too far out
In a different situation, I replaced the GeometryReader with a rectangle:
//rectangle fills the width, then provides a centre for things to align to
Rectangle()
.frame(height:0)
.frame(idealWidth:.infinity)
.alignmentGuide(.colonCentre) { d in d.width/2 }
Note - this will still expand to maximum width unless constrained!
Upvotes: 8