Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: make layout relative to a central view

Tags:

layout

swiftui

Assume I build a view like this:

struct MyView: View {
   @State private var a: String
   @State private var b: String
   @State private var c: String

   var body: some View {
      VStack {
        HStack {
          Text(a)

          // this is the central view
          Text(b).font(.headline)
        }

        Text(c)
      }
   }
}

I would like the central text view (the one displaying b) to be the anchor of the layout. That is, no matter how other text values change, I would like the central text to always stay in the centre of MyView (the centre of the text element and the centre of MyView should stay identical) and the other text elements should be laid out around the central one.

How to I achieve this? I tried to look at alignment guides, but I just don't seem to understand how to use them properly.

like image 964
MrMobster Avatar asked Feb 03 '26 10:02

MrMobster


1 Answers

After spending some time to learn how alignment works in detail, I managed to arrive at a solution that only uses stacks and custom alignments, with minimal alignment guides and without needing to save any intermediate state. It's purely declarative, so I am supposed this is how SwiftUI designers intended it. I still think that there might have been a better design for it, but one can work with it.

struct ContentView: View {
    @State var a: String = "AAAAA"
    @State var b: String = "BBBB"
    @State var c: String = "CCCCCC"

    var body: some View {
         VStack {
            ZStack(alignment: .mid) {
                // create vertical and horizontal 
                // space to align to
                HStack { Spacer() }
                VStack { Spacer() }

                VStack(alignment: .midX) {
                    Text(self.a)

                    HStack(alignment: .center) {
                        Text(self.c)


                        Text(self.b)
                            .font(.title)
                            .border(Color.blue)
                            .alignmentGuide(.midX) { d in
                                (d[.leading] + d[.trailing])/2
                            }
                            .alignmentGuide(.midY) { d in
                                (d[.top] + d[.bottom])/2
                            }
                    }
                }
            }
            .layoutPriority(1.0)
            .overlay(CrossHair().stroke(Color.pink, lineWidth: 2))

            TextField("", text: self.$b).textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

fileprivate extension HorizontalAlignment {
    enum MidX : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return (d[.leading] + d[.trailing])/2
        }
    }

    static let midX = HorizontalAlignment(MidX.self)
}

fileprivate extension VerticalAlignment {
    enum MidY : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return (d[.top] + d[.bottom])/2
        }
    }

    static let midY = VerticalAlignment(MidY.self)
}

fileprivate extension Alignment {
    static let mid = Alignment(horizontal: .midX, vertical: .midY)
like image 64
MrMobster Avatar answered Feb 04 '26 23:02

MrMobster



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!