PROJECT

SpeechBubble

개인 라이브러리 · UIKit 말풍선 뷰 · 오픈소스(MIT)

2024.04 · v0.1.0–0.1.4 배포

iOS 13+ · 서드파티 의존성 0 · SPM·CocoaPods

작은 말풍선 하나에도 결정이 쌓입니다. 드로잉을 부위별로 나누고 타입으로 오용을 막는 API를 설계해 배포까지 해 본 첫 라이브러리입니다.

설계 포인트

무엇을 결정했나

설계 01 · 드로잉

본체와 꼬리를 다른 기법으로 그린다

문제

말풍선은 본체(둥근 사각형)와 꼬리(삼각형)의 테두리가 한 획처럼 이어져 보여야 합니다. 점선 스타일까지 지원하려면 layer 테두리만으로는 부족합니다.

접근

드로잉을 부위별로 분리했습니다. 본체는 CAShapeLayer(둥근 사각 + 점선 대시), 꼬리는 draw(_:)의 UIBezierPath 삼각형. 두 뷰를 borderWidth만큼 겹치는 제약으로 합성해 테두리가 이어집니다.

핵심 결정

꼬리는 모서리 길이 × positionRatio로 기준점을 잡고 arrowSize만큼 벌린 3점을 방향별로 계산합니다. 4방향(top/bottom/left/right) 어디든 임의 비율 위치에 붙습니다.

ArrowView.swift · 꼬리 3점 계산·조립
// 모서리 길이 x 비율로 기준점
case .top:
    pointTip   = CGPoint(x: position, y: rect.height - arrowSize)
    pointLeft  = CGPoint(x: position - arrowSize, y: rect.height)
    pointRight = CGPoint(x: position + arrowSize, y: rect.height)

let path = UIBezierPath()
path.move(to: arrowPoints.left)
path.addLine(to: arrowPoints.tip)
path.addLine(to: arrowPoints.right)
if style.contains(.fill) { filledColor.setFill(); path.fill() }

설계 02 · API

OptionSet 스타일과 15개 반응형 프로퍼티

문제

스타일(채움·실선·점선)은 서로 조합되고, 꼬리 위치(4방향)는 배타적입니다. 호출부가 헷갈리지 않게 타입이 오용을 막아야 합니다.

접근

조합은 OptionSet(.fill / .line / .dottedLine), 배타는 enum(top/bottom/left/right)으로 성격에 맞는 타입을 골랐습니다. 필수 파라미터는 text·style·arrowPosition 3개뿐입니다.

핵심 결정

공개 프로퍼티 15개 전부 didSet으로 런타임에 반응합니다. 값을 바꾸면 서브뷰 전파와 재레이아웃·재드로우까지 이어집니다. SPM과 CocoaPods 이중 배포, 서드파티 의존성 0.

SpeechBubbleStyleOptions.swift · 조합 가능한 스타일
public struct SpeechBubbleStyleOptions: OptionSet {
    public static let fill       = Self(rawValue: 1 << 0)
    public static let line       = Self(rawValue: 1 << 1)
    public static let dottedLine = Self(rawValue: 1 << 2)
}

// 사용: 채움 + 점선 테두리
SpeechBubbleView(text: "안녕하세요",
                 style: [.fill, .dottedLine],
                 arrowPosition: .top)

회고 · 개선 로드맵

지금 다시 본다면 고칠 것들

그때

2024년 처음 배포한 라이브러리라 빠진 것들이 있습니다. 테스트는 템플릿 스텁에 머물렀고, 접근성 라벨과 SwiftUI 지원을 넣지 못했습니다.

지금 보이는 것

UIViewRepresentable 래퍼로 SwiftUI를 지원하고, 합성 뷰 레벨의 accessibilityLabel을 붙이고, 다크 모드 전환 시 traitCollectionDidChange에서 테두리 색을 재적용해야 합니다.

배운 것

공개 파라미터의 오타(cornerRadious)는 배포된 API라 깨지 않고 유지 중입니다. 공개 API 네이밍은 배포 전에 끝내야 한다는 것을 몸으로 배웠습니다.

개선 로드맵
// 1. UIViewRepresentable 래퍼 → SwiftUI 지원
// 2. 합성 뷰 레벨 accessibilityLabel
// 3. traitCollectionDidChange → 테두리 재드로우
// 4. 스텁 테스트 → 드로잉 스냅샷 테스트
// 5. (교훈) 공개 API 네이밍은 배포 전에 확정

기록

SPM과 CocoaPods로 배포했습니다

v0.1.4

SPM · CocoaPods 이중 배포

0.1.0부터 0.1.4까지 다섯 번의 릴리스. MIT 라이선스, 서드파티 의존성 없이 UIKit·Foundation만 사용합니다.