Home > Back-end >  Align Views by constraints
Align Views by constraints

Time:10-14

I would like to build a view similar to this

✓    **Title Text**
     Description Text

Where the icon an the title text have the same horizontal center ant the title text and the description text have the same alginment at the left.

Since i could not find any possiblity in SwiftUI to set constraints I am a little bit stuck.

The best solution i could come up was this

    HStack(alignment: .top, spacing: Constants.Stacks.defaultHorizontalSpacing) {
        
        challengeTask.status.getIconImage()
        
        VStack(alignment: .leading, spacing: Constants.Stacks.defaultVerticalSpacing) {
            Text(challengeTask.title)
                .titleText()
             
            Text(challengeTask.description)
                .multilineTextAlignment(.leading)
                .descriptionText()
            
            Spacer()
        }
    }

But this does not align the icon horiztontally with the title text

CodePudding user response:

Using hidden

struct ContentView: View {
    var body: some View {
        VStack {
            HStack {
                iconImage()
                Text("Title").font(.title.bold())
            }
            HStack {
                iconImage()
                Text("Content").multilineTextAlignment(.leading)
            }
        }
    }
    private func iconImage() -> some View {
        // Change it to your image
        Image(systemName: "checkmark")
    }
}

Using GeometryReader

struct ContentView: View {
    @State private var iconSize: CGFloat = .zero
    
    var body: some View {
        VStack {
            HStack(spacing: 10) {
                Image(systemName: "checkmark")
                    .readSize { iconSize = $0.width }
                Text("Title")
                    .font(.title.bold())
            }
            Text("Content")
                .padding(.leading, iconSize   10)
        }
    }
}

fileprivate struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
}

extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        modifier(ReadSize(onChange))
    }
}

fileprivate struct ReadSize: ViewModifier {
    let onChange: (CGSize) -> Void
    
    init(_ onChange: @escaping (CGSize) -> Void) {
        self.onChange = onChange
    }
    func body(content: Content) -> some View {
        content.background(
            GeometryReader { geometryProxy in
                Color.clear.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
    private struct SizePreferenceKey: PreferenceKey {
        static var defaultValue: CGSize = .zero
        static func reduce(value: inout CGSize, nextValue: () -> CGSize) { }
    }
}

Hope this help!

CodePudding user response:

I think the right way to deal with this is using a custom alignment as described here.

In your case you have 2 possibilities depending on how you want to implement the UI hierarchy:

  • Align image center with title center
  • Align description leading with title leading

Playground for both versions:

import SwiftUI
import PlaygroundSupport

extension HorizontalAlignment {
  private struct TitleAndDescriptionAlignment: AlignmentID {
    static func defaultValue(in context: ViewDimensions) -> CGFloat {
      context[HorizontalAlignment.center]
    }
  }

  static let titleAndDescriptionAlignmentGuide = HorizontalAlignment(
    TitleAndDescriptionAlignment.self
  )
}

extension VerticalAlignment {
  private struct ImageAndTitleCenterAlignment: AlignmentID {
    static func defaultValue(in context: ViewDimensions) -> CGFloat {
      context[VerticalAlignment.center]
    }
  }

  static let imageAndTitleCenterAlignmentGuide = VerticalAlignment(
    ImageAndTitleCenterAlignment.self
  )
}

struct ContentView: View {
  var body: some View {
    NavigationView {
      VStack {

        VStack(alignment: .titleAndDescriptionAlignmentGuide) {
          HStack {
            Image(systemName: "rosette")
            Text("Title text")
              .font(.largeTitle)
              .alignmentGuide(.titleAndDescriptionAlignmentGuide) { context in
                context[.leading]
              }
          }
          Text("Description text")
            .alignmentGuide(.titleAndDescriptionAlignmentGuide) { context in
              context[.leading]
            }
        }

        HStack(alignment: .imageAndTitleCenterAlignmentGuide) {
          Image(systemName: "rosette")
            .alignmentGuide(.imageAndTitleCenterAlignmentGuide) { context in
              context[VerticalAlignment.center]
            }
          VStack(alignment: .leading) {
            Text("Title text")
              .font(.largeTitle)
              .alignmentGuide(.imageAndTitleCenterAlignmentGuide) { context in
                context[VerticalAlignment.center]
              }
            Text("Description text")
          }
        }
      }
    }
  }
}

PlaygroundPage.current.setLiveView(ContentView())
  • Related