iOS 앱의 사용자 경험을 높이는 모듈화된 토스트 메시지 시스템 개발 여정 ✨
들어가며: 왜 토스트 메시지 시스템을 개발하게 되었나요? 🌉
안녕하세요! 오늘은 제가 개발한 ToastMessageKit에 대한 이야기를 나눠볼게요.
교육 앱을 개발하면서 사용자에게 다양한 피드백을 제공해야 하는 상황이 많았어요.
레벨업, 미션 성공, 연속 정답, 에러 알림 등 여러 상황에서 일관된 디자인과 사용자 경험을 제공하면서도 개발 효율성을 높일 수 있는 방법이 필요했죠.
어떤 문제점들이 있었나요?
기존에는 토스트 메시지마다 개별적인 뷰와 로직을 구현하다 보니 다음과 같은 문제점이 발생했어요:
- 코드 중복 📝: 비슷한 기능의 토스트 메시지마다 중복 코드를 작성해야 했어요
- 디자인 불일치 🎨: 개발자마다 다른 방식으로 구현하여 UI 일관성이 떨어졌어요
- 유지보수 어려움 🔧: 새로운 토스트 유형 추가 시 많은 코드 변경이 필요했죠
- 협업 비효율 👥: 기획자, 디자이너, 개발자 간 의사소통이 복잡했어요
이러한 문제를 해결하기 위해 재사용 가능하고 확장성 있는 토스트 메시지 시스템인 ToastMessageKit을 개발하게 되었답니다.
어떤 설계 원칙을 세웠나요? 🧩
ToastMessageKit은 다음 핵심 원칙을 기반으로 설계했어요:
- 모듈화와 재사용성 🧱: 각 컴포넌트가 독립적으로 작동하도록 설계했어요
- 확장 가능한 구조 📈: 새로운 토스트 유형이나 기능을 쉽게 추가할 수 있는 구조로 만들었어요
- 선언적 사용법 📋: 복잡한 내부 구현을 숨기고 간결한 API를 제공했어요
- 일관된 사용자 경험 🌟: 모든 토스트 메시지가 통일된 디자인 원칙을 준수하도록 했어요
ToastMessageKit의 의존 관계는 어떻게 되나요?
ToastMessageKit의 컴포넌트 간 관계와 데이터 흐름을 시각화한 다이어그램이에요:
이 다이어그램을 통해 알 수 있듯이, ToastCoordinator가 중앙에서 전체 시스템을 관리하고,
Factory 패턴을 통해 다양한 토스트 유형을 생성하며, Components와 Abilities를 조합하여 유연하게 기능을 확장할 수 있어요.
이러한 구조는 관심사 분리와 단일 책임 원칙을 잘 구현하고 있으며, 새로운 기능 추가 시 기존 코드 수정 없이도 확장이 가능하답니다.
어떤 디자인 패턴을 적용했나요?
이를 위해 두 가지 주요 패턴을 적용했어요:
1. Factory 패턴은 어떻게 활용했나요?
토스트 뷰 생성을 담당하는 팩토리 패턴을 도입해 각 토스트 유형별로 독립적인 팩토리를 구현했어요. 이를 통해 토스트의 생성 로직과 표현 로직을 분리할 수 있었죠.
// 토스트 메시지 생성을 위한 열거형 팩토리
public enum ToastViewMaker {
case noteExpansion(isMaxSize: Bool)
case levelUp(level: Int)
case missionSuccess(missionName: String)
case error(title: String, description: String)
case consecutiveCorrect(count: Int, studentName: String?)
case consecutiveWrong(count: Int)
public func makeFactory() -> ToastViewFactory {
switch self {
case .noteExpansion(let isMaxSize):
return isMaxSize ? NoteExpansionMaxSizeFactory() : NoteExpansionMinSizeFactory()
case .levelUp(let level):
return LevelUpFactory(level: level)
// 다른 케이스들...
}
}
}
2. Ability 패턴은 무엇인가요?
토스트 메시지의 동작과 기능을 모듈화하기 위해 'Ability' 패턴을 도입했어요. 각 Ability는 특정 기능(자동 사라짐, 애니메이션, 우선순위 등)을 캡슐화하여 토스트에 주입할 수 있도록 설계했답니다.
public protocol ToastAbility {
func apply(to view: ToastView)
}
// 자동으로 사라지는 기능
public struct AutoDisappearAbility: ToastAbility {
private let duration: TimeInterval
public func apply(to view: ToastView) {
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
view.dismiss()
}
}
}
// 컨페티 애니메이션 기능
public struct ConfettiAbility: ToastAbility {
public func apply(to view: ToastView) {
// 컨페티 애니메이션 로직
}
}
💡 핵심 포인트: 기능과 동작을 모듈화함으로써 필요한 기능만 조합하여 다양한 토스트 유형을 쉽게 만들 수 있어요!
주요 컴포넌트는 어떻게 구현했나요? 🛠️
ToastMessageKit의 핵심 컴포넌트는 다음과 같아요:
1. ToastCoordinator는 어떤 역할을 하나요?
토스트 메시지를 전역적으로 관리하는 싱글톤 코디네이터예요. 여러 토스트 메시지의 표시 순서, 중복 방지, 윈도우 레벨 관리 등을 담당하죠.
public class ToastCoordinator {
public static let shared = ToastCoordinator()
// 앱 시작 시 초기화
public func initializeOverlays(for scene: UIWindowScene) {
// 토스트 메시지용 오버레이 윈도우 설정
}
// 토스트 메시지 표시
public func showToast(for maker: ToastViewMaker) {
let factory = maker.makeFactory()
let toastView = factory.make()
displayToast(toastView)
}
// 직접 팩토리 객체로 토스트 표시
public func showToast(for factory: ToastViewFactory) {
let toastView = factory.make()
displayToast(toastView)
}
}
2. PassthroughWindow는 어떤 특별한 점이 있나요?
토스트 메시지가 표시될 때 아래 UI와의 상호작용을 방해하지 않도록 설계한 특수 윈도우예요.
class PassthroughWindow: UIWindow {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let view = super.hitTest(point, with: event), view != self {
return view
}
return nil // 윈도우 자체는 터치 이벤트 무시
}
}
3. 컴포넌트 구조는 어떻게 되어 있나요?
토스트 뷰의 UI 요소를 담당하는 컴포넌트들이에요:
// 아이콘 컴포넌트
public struct IconComponent {
let image: UIImage
public func applyTo(view: ToastView) {
// 아이콘 설정 로직
}
}
// 메시지 컴포넌트
public struct MessageComponent {
let title: String
let description: String?
public func applyTo(view: ToastView) {
// 메시지 설정 로직
}
}
협업은 어떻게 이루어졌나요? 👥
ToastMessageKit 개발에서 가장 중요한 부분 중 하나는 기획자, 디자이너와의 협업이었어요.
기존에는 토스트 메시지 요구사항이 나올 때마다 디자인, 기획, 개발 간 많은 조율이 필요했고, 구현까지 시간이 많이 소요되었죠.
어떻게 협업 효율성을 높였나요?
이를 개선하기 위해 다음과 같은 과정을 거쳤어요:
- 공통 언어 정립 📚: 모든 토스트 메시지를 '컴포넌트'와 '기능'으로 구분하는 공통 언어를 만들었어요
- 디자인 시스템 연계 🎨: 디자인 시스템의 색상, 타이포그래피, 간격 등을 ToastMessageKit에 연동했어요
- 문서화 📋: 가이드 문서를 작성하여 모든 팀원이 참조할 수 있도록 했어요
- 예제 카탈로그 📖: 모든 토스트 메시지 유형과 기능을 보여주는 예제 앱을 제작했어요
새로운 요구사항은 어떻게 처리했나요?
이러한 과정을 통해 새로운 토스트 메시지 요구사항이 나오면:
- 기획자가 어떤 '기능'(Ability)을 가져야 하는지 명시해요
- 디자이너가 어떤 '컴포넌트'로 구성되는지 설계해요
- 개발자가 기존 컴포넌트와 기능으로 조합 가능한지 검토 후 구현해요
이와 같은 체계적인 프로세스로 의사소통 비용을 크게 줄일 수 있었답니다.
Example 앱은 어떤 역할을 했나요? 📱
ToastMessageKit의 개발 과정에서 가장 큰 도전 중 하나는 기획자와 디자이너에게 추상적인 컴포넌트와 기능의 개념을 효과적으로 설명하고, 실제 구현이 의도한 대로 작동하는지 빠르게 확인받는 것이었어요.
이 문제를 해결하기 위해 독립적인 Example 앱을 개발했답니다.
Example 앱은 어떤 목적으로 만들었나요?
ToastMessageKit-Example 앱은 다음과 같은 목적으로 설계되었어요:
- 카탈로그 기능 📖: 모든 토스트 메시지 유형을 한 곳에서 시각적으로 확인할 수 있어요
- 실시간 커스터마이징 ⚡: 다양한 속성을 실시간으로 조정하며 결과를 확인할 수 있어요
- 디자인 시스템 통합 테스트 🧪: 디자인 시스템의 색상, 폰트 등이 제대로 적용되는지 검증할 수 있어요
- 개발-디자인-기획 간 소통 도구 💬: 공통 레퍼런스로 활용할 수 있어요
Example 앱의 구조는 어떻게 되어 있나요?
ToastMessageKit에서 제공하는 모든 토스트 메시지 유형을 표 형태로 정리하여 보여줘요. 각 유형별로:
- 토스트 메시지 이름
- 간략한 설명
- "표시" 버튼을 통해 즉시 확인 가능
- 필요한 속성(예: 레벨, 미션명 등) 입력 가능
struct CatalogView: View {
var body: some View {
List {
Section("기본 토스트") {
ToastCatalogItem(
title: "레벨업",
description: "사용자 레벨 상승 시 표시",
onTap: { ToastCoordinator.shared.showToast(for: .levelUp(level: 10)) }
)
ToastCatalogItem(
title: "미션 성공",
description: "미션 완료 시 표시",
onTap: { ToastCoordinator.shared.showToast(for: .missionSuccess(missionName: "기초 수학")) }
)
// 다른 토스트 타입들...
}
Section("커스텀 토스트") {
// 커스텀 토스트 타입들...
}
}
.navigationTitle("토스트 카탈로그")
}
}
커스터마이저는 어떤 역할을 하나요?
실시간 커스터마이저는 토스트 메시지의 다양한 속성을 UI 컨트롤을 통해 조정하고 바로 결과를 확인할 수 있는 화면이에요:
- 메시지 제목, 설명 텍스트 입력
- 아이콘 선택기
- 알림 레벨 선택기 (일반, 경고, 오류 등)
- 사라지는 시간 슬라이더
- 애니메이션 효과 선택기 (페이드, 슬라이드 등)
- 기능(Ability) 체크박스 목록
이 화면을 통해 디자이너는 UI 속성을, 기획자는 기능적 속성을 실시간으로 조정하며 최적의 조합을 찾을 수 있었어요.
협업 과정은 어떻게 진행되었나요?
Example 앱을 활용한 협업 과정은 다음과 같이 진행되었어요:
- 요구사항 정의 단계 📝:
- 기획자가 필요한 토스트 메시지 유형과 기능을 정의해요
- 디자이너가 시각적 디자인 작업을 진행해요
- 개발자가 초기 구현 계획을 수립해요
- 프로토타이핑 단계 🔄:
- 개발자가 Example 앱에 새로운 토스트 유형 또는 기능을 추가해요
- 팀 내에서 Example 앱을 배포해요 (TestFlight 또는 내부 배포)
- 주간 피드백 세션에서 Example 앱을 직접 조작하며 피드백을 수집해요
- 피드백 반영 단계 👂:
- 수집된 피드백을 ToastMessageKit에 반영해요
- Example 앱을 업데이트하고 재배포해요
- 변경사항에 대한 즉각적인 확인이 가능해요
- 확정 및 문서화 단계 📚:
- 최종 토스트 메시지 스펙을 확정해요
- 사용 가이드 및 예제 코드를 문서화해요
- 메인 앱에 통합해요
Example 앱을 통해 어떤 효과를 얻었나요?
Example 앱을 통한 빠른 피드백 사이클 구축으로 다음과 같은 효과를 얻을 수 있었어요:
- 피드백 사이클 단축 ⏱️: 변경 사항에 대한 피드백 수집 시간이 1주에서 1-2일로 단축됐어요
- 오해 감소 🧠: 추상적인 개념에 대한 오해가 감소하고 모든 팀원이 동일한 이해를 공유할 수 있었어요
- 실험 용이성 🧪: "이런 기능은 어떨까요?"라는 아이디어를 즉시 구현하고 테스트할 수 있었어요
- 수정 비용 감소 💰: 메인 앱 개발 전에 대부분의 UI/UX 이슈를 해결할 수 있었어요
- 문서화 효율 📑: Example 앱 자체가 실행 가능한 문서 역할을 했고, 신규 입사자 온보딩에도 효과적이었어요
팀에서 예상하지 못했던 큰 장점은 신규 기능 기획 회의에서 Example 앱이 브레인스토밍 도구로 활용된 것이었어요.
회의 중 나온 아이디어를 그 자리에서 Example 앱에 적용해보며 실시간으로 가능성을 검증할 수 있었답니다!
어떤 성과를 얻었나요? 📊
ToastMessageKit 도입으로 얻은 주요 성과는 다음과 같아요:
기술적으로 어떤 개선이 있었나요?
- 코드 중복 70% 이상 감소 ✂️: 동일한 기능을 여러 번 구현할 필요가 없어졌어요
- 새로운 토스트 유형 개발 시간 80% 단축 ⏱️: 3일에서 0.5일로 단축됐어요
- 버그 발생률 감소 🐞: 중앙화된 관리로 인해 토스트 관련 버그가 90% 감소했어요
- 유지보수성 향상 🔧: 명확한 관심사 분리로 코드 이해 및 수정이 용이해졌어요
비즈니스적으로는 어떤 이점이 있었나요?
- 개발 생산성 향상 📈: 개발자가 비즈니스 로직에 더 집중할 수 있게 됐어요
- 일관된 사용자 경험 🌟: 모든 알림이 일관된 디자인과 동작 방식을 제공해요
- 의사소통 비용 40% 절감 💬: 기획-디자인-개발 간 명확한 언어로 소통할 수 있게 됐어요
- 제품 품질 향상 🏆: 안정적이고 시각적으로 일관된 피드백 시스템으로 사용자 만족도가 증가했어요
실제로 어떻게 사용하나요? 🛠️
ToastMessageKit의 사용법은 매우 직관적이에요:
// 레벨업 토스트 메시지 표시
ToastCoordinator.shared.showToast(for: .levelUp(level: 10))
// 미션 성공 토스트 메시지 표시
ToastCoordinator.shared.showToast(for: .missionSuccess(missionName: "수학 문제 3개 연속 정답"))
// 커스텀 토스트 메시지 생성
struct CustomToastFactory: ToastViewFactory {
func make() -> ToastView {
return BaseToastFactory(
components: [
IconComponent(image: UIImage(named: "customIcon")),
MessageComponent(title: "맞춤 알림", description: "원하는 메시지를 표시합니다.")
],
abilities: [
AlertLevelAbility(level: .normal),
AutoDisappearAbility(messageLength: description.count),
DeepLinkAbility(url: URL(string: "myapp://settings")!)
]
).make()
}
}
// 커스텀 토스트 표시
ToastCoordinator.shared.showToast(for: CustomToastFactory())
💡 핵심 포인트: 간결한 API로 복잡한 기능을 쉽게 사용할 수 있어요!
어떤 교훈을 얻었나요? 🧠
ToastMessageKit 개발을 통해 얻은 가장 큰 교훈은 "적절한 추상화의 중요성"이었어요.
너무 많은 추상화는 복잡성을 증가시키고, 너무 적은 추상화는 재사용성을 저해해요.
토스트 메시지 기능을 컴포넌트와 기능으로 분리한 접근 방식이 최적의 균형점을 찾는 데 도움이 되었답니다.
앞으로 어떤 발전 방향을 계획하고 있나요?
향후 발전 방향으로는:
- 더 많은 Ability 추가 🧩: 진동 피드백, 소리 효과, 접근성 기능 등을 추가할 계획이에요
- 테마 지원 강화 🌓: 다크 모드/라이트 모드 전환 지원을 개선할 예정이에요
- 애니메이션 커스터마이징 ✨: 더 다양한 애니메이션 옵션을 제공할 계획이에요
- 통계 및 분석 기능 📊: 어떤 토스트 메시지가 많이 사용되는지 추적하는 기능을 고려하고 있어요
마치며 🎁
ToastMessageKit은 단순한 UI 컴포넌트 라이브러리를 넘어, 기획자-디자이너-개발자 간 효과적인 협업을 가능하게 하는 도구로 자리매김했어요.
복잡한 UI 요소를 모듈화하고 재사용 가능한 형태로 만들어 개발 효율성을 높이는 접근 방식은 다른 UI 요소 개발에도 적용할 수 있는 좋은 사례가 되었답니다.
개발자로서 가장 보람찼던 순간은 새로운 토스트 메시지 요구사항이 나왔을 때, 기존 코드를 수정하지 않고도 몇 줄의 코드만으로 요구사항을 충족시킬 수 있었던 때였어요.
"한 번 작성하고, 어디서나 사용한다(Write once, use anywhere)"는 철학이 실제로 구현된 경험이었답니다.
여러분의 프로젝트에서도 이러한 접근 방식이 도움이 되길 바라요.
복잡한 UI 요소를 모듈화하고 재사용 가능하게 만드는 것은 초기에는 더 많은 노력이 필요할 수 있지만, 장기적으로는 개발 생산성과 코드 품질을 크게 향상시킬 수 있어요! 😊