skip to Main Content

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

2

Answers


  1. iOS 16

    struct ContentView: View {
        var body: some View {
            HStack(alignment: .firstTextBaseline) {
                //             ^^^^^^^^^^^^^^^^^
                Image(systemName: "checkmark")
                VStack {
                    Text("Title").font(.title.bold())
                    Text("Content").multilineTextAlignment(.leading)
                }
            }
        }
    }
    

    Using hidden

    struct ContentView: View {
        var body: some View {
            VStack {
                HStack {
                    iconImage()
                    Text("Title").font(.title.bold())
                }
                HStack {
                    iconImage().hidden()
                    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!

    Login or Signup to reply.
  2. 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())
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search