Created
January 23, 2026 06:44
-
-
Save avii-7/aecf078a350d2eee36ff9279a37f5354 to your computer and use it in GitHub Desktop.
LegendView just like HTML Legend and FieldSet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // LegentView.swift | |
| // DifferentSmallTests | |
| // | |
| // Created by Arun's Macbook on 22/01/26. | |
| // | |
| // Support only one line for Legend Text right now. | |
| import SwiftUI | |
| private struct LegendShape: Shape { | |
| private struct ShapePoints { | |
| let x1: CGFloat // Representing left x starting point | |
| let x2: CGFloat // Representing right x starting point | |
| let midY: CGFloat // Will be used to move border downwards so that text looks vertically center | |
| } | |
| let text: String | |
| let font: UIFont | |
| let horizontalSpacing: CGFloat | |
| let cornerRadius: CGFloat | |
| func path(in rect: CGRect) -> Path { | |
| var path = Path() | |
| let shapePoints = getLine(rect: rect) | |
| path.move(to: CGPoint(x: shapePoints.x1, y: shapePoints.midY)) | |
| path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: shapePoints.midY)) | |
| path.addQuadCurve( | |
| to: CGPoint( | |
| x: rect.minX, | |
| y: shapePoints.midY + cornerRadius | |
| ), | |
| control: CGPoint(x: rect.minX , y: shapePoints.midY) | |
| ) | |
| path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - cornerRadius)) | |
| path.addQuadCurve( | |
| to: CGPoint( | |
| x: rect.minX + cornerRadius, | |
| y: rect.maxY | |
| ), | |
| control: CGPoint(x: rect.minX , y: rect.maxY) | |
| ) | |
| path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY)) | |
| path.addQuadCurve( | |
| to: CGPoint( | |
| x: rect.maxX, | |
| y: rect.maxY - cornerRadius | |
| ), | |
| control: CGPoint(x: rect.maxX , y: rect.maxY) | |
| ) | |
| path.addLine(to: CGPoint(x: rect.maxX, y: shapePoints.midY + cornerRadius)) | |
| path.addQuadCurve( | |
| to: CGPoint( | |
| x: rect.maxX - cornerRadius, | |
| y: shapePoints.midY | |
| ), | |
| control: CGPoint(x: rect.maxX , y: shapePoints.midY) | |
| ) | |
| path.addLine(to: CGPoint(x: shapePoints.x2, y: shapePoints.midY)) | |
| return path | |
| } | |
| private func getLine(rect: CGRect) -> ShapePoints { | |
| let rectSize = rect.size | |
| let textSize = text.size(with: rectSize, font: font) | |
| var x = (rectSize.width - textSize.width) / 2 - horizontalSpacing | |
| var x1 = x + horizontalSpacing + textSize.width + horizontalSpacing | |
| let minimumWidth = 5.0 | |
| x = max(x , rect.minX + cornerRadius + minimumWidth) | |
| x1 = min(x1, rect.maxX - cornerRadius - minimumWidth) | |
| return ShapePoints( | |
| x1: x, | |
| x2: x1, | |
| midY: textSize.height / 2 | |
| ) | |
| } | |
| } | |
| struct LegentView<LegendText: View, Content: View>: View { | |
| struct Properties { | |
| let text: String | |
| let font: UIFont | |
| var horizontalSpacing: CGFloat = 10 | |
| var cornerRadius: CGFloat = 20 | |
| var lineWidth: CGFloat = 2.0 | |
| } | |
| let properties: Properties | |
| var configure: (Text) -> LegendText | |
| let innerContent: () -> Content | |
| @State private var size: CGSize = .zero | |
| var body: some View { | |
| ZStack(alignment: .top) { | |
| LegendShape( | |
| text: properties.text, | |
| font: properties.font, | |
| horizontalSpacing: properties.horizontalSpacing, | |
| cornerRadius: properties.cornerRadius | |
| ) | |
| .stroke(style: StrokeStyle(lineWidth: properties.lineWidth, lineCap: .round, lineJoin: .round)) | |
| .frame(height: size.height + properties.text.size(font: properties.font).height) | |
| VStack(spacing: .zero) { | |
| configure( | |
| Text(properties.text) | |
| .font(Font(properties.font)) | |
| ) | |
| innerContent() | |
| .padding(.vertical, 4) | |
| .onGeometryChange(for: CGSize.self) { proxy in | |
| proxy.size | |
| } action: { newValue in | |
| self.size = newValue | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #Preview { | |
| LegentView( | |
| properties: .init( | |
| text: "Premium Benefits", | |
| font: .boldSystemFont(ofSize: 20), | |
| horizontalSpacing: 10, cornerRadius: 20 | |
| ), configure: { text in | |
| text.foregroundStyle(.black) | |
| .lineLimit(1) | |
| }, | |
| innerContent: { | |
| VStack { | |
| Text("You are awesome buddy 😄") | |
| .font(.caption) | |
| Divider() | |
| HStack { | |
| Text("Get 10% off on all purchases") | |
| .font(.caption) | |
| Image(systemName: "star") | |
| } | |
| .padding(.horizontal) | |
| Divider() | |
| HStack { | |
| Text("Get 10% off on all purchases") | |
| .font(.caption) | |
| Image(systemName: "star") | |
| } | |
| .padding(.horizontal) | |
| } | |
| }) | |
| .padding() | |
| } | |
| extension String { | |
| func size(with rectSize: CGSize, font: UIFont) -> CGSize { | |
| let constraintRect = rectSize | |
| let boundingBox = self.boundingRect( | |
| with: constraintRect, | |
| options: [.usesLineFragmentOrigin, .usesFontLeading], | |
| attributes: [.font: font], | |
| context: nil | |
| ) | |
| let size = CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height)) | |
| return size | |
| } | |
| func size(font: UIFont) -> CGSize { | |
| let constraintRect = CGSize( | |
| width: Double.greatestFiniteMagnitude, | |
| height: Double.greatestFiniteMagnitude | |
| ) | |
| let boundingBox = self.boundingRect( | |
| with: constraintRect, | |
| options: [.usesLineFragmentOrigin, .usesFontLeading], | |
| attributes: [.font: font], | |
| context: nil | |
| ) | |
| let size = CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height)) | |
| return size | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment