Building a Versatile Avatar Component in SwiftUI

M.Abbas
7 min readDec 26, 2024

--

Avatars are a ubiquitous part of modern UI design, commonly used to represent users or entities. In this post, we’ll dissect a SwiftUI based avatar component that supports customization of size, state, and content type. By the end, you’ll have a clear understanding of its architecture and how to leverage it in your own projects.

Component Overview

This avatar component is built with modularity and flexibility in mind. It includes three main elements:

  1. AvatarType: Determines the type of content displayed (icon, initials, or an image).
  2. AvatarSize: Configures predefined sizes for the avatar and its fonts or badges.
  3. DLAvatarState: Represents the state of the avatar, such as online or offline, and adjusts the badge appearance accordingly.

Breaking Down the Code

1. Defining the Avatar Types

The AvatarType enum specifies the types of content you can show in the avatar:

public enum AvatarType {
case icon(icon: String)
case initials(text: String)
case image(image: String)
}
  • icon: Displays a system or custom icon.
  • initials: Shows user initials.
  • image: Uses an image resource for the avatar.

This abstraction allows for versatile content representation.

2. Configuring Sizes with AvatarSize

The AvatarSize enum encapsulates predefined avatar sizes, making it easy to maintain consistency across the UI:

enum AvatarSize {
case xs, sm, md, lg

var size: CGFloat {
switch self {
case .xs: 32
case .sm: 40
case .md: 48
case .lg: 56
}
}

var fontSize: CGFloat {
switch self {
case .xs: return 12
case .sm: return 14
case .md: return 18
case .lg: return 20
}
}

var badgeSize: CGFloat {
switch self {
case .xs: return 12
case .sm: return 14
case .md: return 16
case .lg: return 18
}
}

var badgeTextSize: CGFloat {
switch self {
case .xs, .sm: return 8
case .md, .lg: return 12
}
}

var offset: CGFloat {
switch self {
case .xs: return 1
default: return 3
}
}
}

Each size has associated properties:

  • size: The overall dimension of the avatar.
  • fontSize: Font size for initials.
  • badgeSize: Size of the status badge.
  • badgeTextSize: Text size inside the badge.
  • offset: Adjusts badge position for better alignment.

This configuration simplifies size management and ensures scalability.

3. Handling States with DLAvatarState

The DLAvatarState enum adds a layer of interactivity by enabling state-based badges:

public enum DLAvatarState {
case normal, online, offline

var bgColor: Color {
switch self {
case .online: return .green
case .offline: return .green.opacity(0.5)
default: return .clear
}
}
}
  • bgColor: Determines the badge background color:
  • .online: Green.
  • .offline: Faded green.
  • .normal: Clear (no badge).

This ensures that the avatar’s state is visually distinguishable.

4. Building the Badge Component

The DLAvatarBadge view draws a circular badge to represent the user’s state:

struct DLAvatarBadge: View {

var ckAvatarSizing: AvatarSize
var state: AvatarState

public var body: some View {
Circle()
.fill(state.bgColor)
.frame(width: ckAvatarSizing.badgeSize, height: ckAvatarSizing.badgeSize)
.overlay {
Circle()
.stroke(.white, lineWidth: 2)
}
}
}

The badge size and appearance are dynamically tied to the AvatarSize and AvatarState.

5. Putting It All Together in the Avatar View

Finally, the Avatar view combines all the components:

struct Avatar: View {

var type: AvatarType
var size: AvatarSize
var state: AvatarState
var customSize: CGFloat? = nil

public var body: some View {
ZStack {
switch type {
case .icon(let icon):
Image(icon)
.resizable()
.frame(width: 24, height: 24)
case .initials(let text):
Text(text)
.font(.system(size: size.fontSize, weight: .semibold))
.lineLimit(1)
case .image(let image):
Image(image)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
.frame(width: customSize ?? size.size, height: customSize ?? size.size)
.background(.white)
.clipShape(.circle)
.overlay {
Circle()
.stroke(Color.grey200)
}
.overlay(alignment: .bottomTrailing) {
if state != .normal {
DLAvatarBadge(
ckAvatarSizing: size,
state: state
)
.offset(x: size.offset, y: size.offset)
}
}
}
}

Key Features:

  • Content Display: The type property determines whether to show an icon, initials, or image.
  • Styling: The avatar is styled with a circular shape, a background, and a stroke for a polished look.
  • State Badge: If the state is not .normal, a badge is added at the bottom-right.

Usage Examples

Here’s how you can use the Avatar component:

Display Initials

Avatar(type: .initials(text: "Aa"), size: .md, state: .online)

Display an Image

Avatar(type: .image(image: "ic_ProfileImage"), size: .lg, state: .offline)

Custom Size with an Icon

We can provide a custom size to the avatar like below and will get the desired sized avatar.

Avatar(type: .icon(icon: "ic_User"), size: .sm, state: .normal, customSize: 50)

Avatar Group Component

An Avatar Group is a common UI element used in collaborative and social applications to visually represent a group of users. This SwiftUI-based component is designed to handle scenarios where multiple avatars need to be displayed together while maintaining a clean and intuitive layout.

In this article, we’ll break down the AvatarGroup component and explore how it handles different group sizes, offsets, and layouts.

Component Overview

The AvatarGroup is a SwiftUI component that:

  • Accepts a list of AvatarType objects representing individual avatars.
  • Dynamically displays one or two avatars, with a badge indicating additional group members if more are present.
  • Offers two predefined sizes: .xs (extra small) and .lg (large).

The AvatarGroupSize Enum

enum AvatarGroupSize {
case xs
case lg

var offset: CGFloat {
switch self {
case .xs: return 20
case .lg: return 32
}
}
}

This enum defines two sizes for the avatar group:

  • .xs: Small avatars with a smaller offset for tighter spacing.
  • .lg: Larger avatars with increased spacing for better visibility.

The offset property determines how far additional avatars or badges are positioned from the base avatar.

The AvatarGroup View

The AvatarGroup struct is the core of the component. It takes three parameters:

  • items: An array of AvatarType that defines the avatars to display.
  • size: An optional AvatarGroupSize, defaulting to .lg.

Here’s a closer look at the logic inside the component:

struct AvatarGroup: View {
let items: [AvatarType]
var size: AvatarGroupSize = .lg

public var body: some View {
let itemCount = items.count
if items.count == 0 {
EmptyView()
} else {
ZStack {
if itemCount > 0 {
Avatar(type: items[0], size: size == .lg ? .sm : .xs, state: .normal)
}

Group {
if itemCount > 2 {
Circle()
.fill(Color.grey200)
.frame(width: size == .lg ? 40 : 32, height: size == .lg ? 40 : 32)
.overlay {
Text("\(items.count - 1)")
.foregroundStyle(.black)
.font(.system(size: 12, weight: .semibold))
.lineLimit(1)
.padding(2)
}
.padding([.leading, .top], size.offset)
} else if itemCount > 1 {
Avatar(type: items[1], size: size == .lg ? .sm : .xs, state: .normal)
.padding([.leading, .top], size.offset)
}
}
}
}
}
}
  • Flexible Group Representation: The AvatarGroup displays up to two individual avatars or condenses excess avatars into a count badge for clarity and simplicity.
  • Configurable Sizes: Offers two size options, .lg and .xs, to adapt the component for different UI contexts, such as compact lists or profile headers.
  • Dynamic Avatar Display: Integrates the Avatar component to render each avatar type (e.g., icons, initials, or images) seamlessly within the group.
  • Count Badge for Overflow: Automatically displays a circular badge showing the remaining number of avatars when the group exceeds two members.

Usage Examples

Here’s how to use the AvatarGroup component:

1. Icons Avatar Group

AvatarGroup(
items: [
.icon(icon: "ic_User"),
.icon(icon: "ic_User")
],
size: .lg
)

2. Initials Avatar Group

AvatarGroup(
items: [
.initials(text: "Aa"),
.initials(text: "Aa")
],
size: .lg
)

3. Images Avatar Group

AvatarGroup(
items: [
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage")
],
size: .lg
)

4. Overflow Avatar Group

AvatarGroup(
items: [
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage"),
.image(image: "ic_ProfileImage")
],
size: .lg
)

Conclusion

The Avatar and AvatarGroup components are versatile and essential building blocks for modern user interfaces, particularly in collaborative and social applications. The Avatar component offers customizable options for representing users through icons, initials, or images, while seamlessly integrating status indicators like online/offline badges. The AvatarGroup extends this functionality, enabling the display of multiple avatars in a clean, dynamic layout that adapts to the number of members and available space. Together, these components enhance the user experience by providing visually consistent and intuitive elements for representing individuals and groups in any app.

You can get complete code from following link
https://gist.github.com/muhammadabbas001/5437eb59e7f9cccf02072a08609e6b88

--

--

M.Abbas
M.Abbas

No responses yet