Skip to content

Instantly share code, notes, and snippets.

@zats
Last active October 22, 2023 22:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zats/003add3de72a8f6e502a78ff0cbe1957 to your computer and use it in GitHub Desktop.
Save zats/003add3de72a8f6e502a78ff0cbe1957 to your computer and use it in GitHub Desktop.
Square image container

Image in a square container

It took me way too long to figure out how to achieve following:

image

Key points to call out:

I wanted API to look something like

DocumentIcon(icon: .systemImage("creditcard"))
  .font(.largeTitle) // optional font 

I want to make sure that created system image respects externally passed font or defaults to .body Additionally it maintains 5pt padding

It took multiple attempts of .aspectRatio(1, contentMode: …) and GeometryReader { geo in … } to finally arrive at this solution

Implementation

struct SquareContentView<Content: View>: View {
    let content: Content
    @State private var sideLength: CGFloat? = nil

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        VStack {
            content
        }
        .background(
            GeometryReader { geometry in
                Color.clear.onAppear {
                    let dimension = max(geometry.size.width, geometry.size.height)
                    sideLength = dimension
                }
            }
        )
        .frame(width: sideLength, height: sideLength)
    }
}

Consumption

The callsite component looks like so:

struct DocumentIcon: View {
    var title: String
    @State var icon: Document.Icon
    @Environment(\.font) private var maybeFont
    
    private var font: Font {
        maybeFont ?? .body
    }

    var body: some View {
        SquareContentView {
            Text("X") // we use X to make sure icons are the same size regardless of icon dimension
                .disabled(true)
                .foregroundColor(.clear)
                .overlay {
                    Group {
                        switch icon {
                        case .letter:
                            Text(String(title.first ?? "?"))
                        case .systemImage(let systemName):
                            Image(systemName: systemName)
                        }
                    }
                    .font(font)
                    .foregroundColor(.white)
                }
            .padding(font.scaledValue(10))
        }
        .background(.orange)
        .cornerRadius(font.scaledValue(5))
    }
}

Hope it'll save someone time ✌️

TODO

In the future I'd love to make following components dependant on the font set externally

  • padding between image and the bounds of the component
  • corner radius of the component
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment