← 모든 글

SwiftUI의 Liquid Glass: iOS 26에 Return을 출시하며 얻은 세 가지 패턴

Apple의 Liquid Glass는 한 줄짜리 SwiftUI API입니다: .glassEffect().1 제 명상 타이머인 Return은 iOS, macOS, tvOS에 걸쳐 이를 아홉 번 사용합니다.2 그중 한 곳에서는 이 modifier를 커스텀 Shape에 적용하여 타이머 숫자 자체를, 글리프 하나하나 단위로 liquid glass로 바꿉니다.

iPhone의 Return — 커스텀 Shape를 통해 타이머 숫자 자체가 Liquid Glass로 렌더링됩니다(아래 패턴 1에서 다룸); 시간 선택기는 Liquid Glass HUD입니다

흥미로운 질문은 이 한 줄짜리를 넘어설 때 무슨 일이 일어나는가입니다. Apple의 Human Interface Guidelines는 엄격한 레이어링 규칙을 제시합니다: Liquid Glass는 기능적 레이어(컨트롤, 내비게이션, 일시적 UI)에 속하며 콘텐츠 레이어에는 결코 들어가지 않습니다.3 Return의 아홉 가지 사용 중 대부분은 교과서적인 기능적 레이어 적용입니다: 선택기, 버튼, 컨트롤 스트립, 일시정지 상태 배지가 그렇습니다. 흥미로운 사용은 규칙을 깨지 않으면서 구부리는 세 가지입니다.

이 글에서는 제가 출시한 세 가지 패턴과 그것들이 지킨 규칙, 저를 사로잡았던 함정들, 그리고 제가 의도적으로 사용하지 않은 API 표면을 차례로 살펴봅니다.

Apple의 Human Interface Guidelines는 Liquid Glass를 세 가지 머티리얼 변형으로 정의합니다. regular 변형은 그 아래에 놓인 무엇에든 자신의 외관을 적응시키고(왼쪽 예시는 어두운 배경, 오른쪽 예시는 밝은 배경), clear 변형은 훨씬 약한 필터링으로 아래의 디테일이 비쳐 보이게 합니다:3

어두운 배경 위의 Liquid Glass regular 변형 — Apple HIG 참고 자료

밝은 배경 위의 Liquid Glass regular 변형 — Apple HIG 참고 자료

Liquid Glass clear 변형 — Apple HIG 참고 자료, clear 변형이 아래 콘텐츠의 시각적 디테일을 어떻게 비쳐 보이게 하는지 보여줌

참고 이미지는 Apple의 Materials HIG 페이지에서 가져왔습니다.3 두 변형 모두 SwiftUI에서 .glassEffect()가 만들어내는 것이며, 둘 사이의 선택은 modifier에 전달하는 단일 인자로 결정됩니다.

요약(TL;DR)

  • iOS 26은 Liquid Glass를 .glassEffect(_:in:)로 제공합니다. 기본 변형은 .regular이고 기본 모양은 Capsule입니다.1
  • Return은 한 줄짜리를 넘어서는 세 가지 패턴을 사용합니다: 커스텀 Shape 위의 glass(Core Text 글리프 패스를 통한 타이머 텍스트), 거울 패턴(뒤집고 마스킹한 복사본으로 아래에 반사를 만듦), 그리고 기능적 레이어 HUD 오버레이.
  • Apple의 HIG 규칙: 기능적 레이어에는 Liquid Glass를, 콘텐츠 레이어에는 표준 머티리얼을.3
  • 저는 의도적으로 GlassEffectContainer를 사용하지 않았습니다. 변형(morphing) API는 Return에서 쓸 곳이 없으며(어떤 glass 요소도 다른 요소로 애니메이션되지 않음), 렌더링 성능 격차도 벤치마크하지 않았습니다. 이는 측정되지 않은 트레이드오프이지 권장 사항이 아닙니다.1
  • 함정들: 평평한 배경 위의 glass는 평평하게 보입니다. 떨림 없는 숫자 렌더링에는 고정 너비 셀이 필요합니다. tvOS의 HStack은 레이아웃 방향 환경 값을 무시합니다. 모핑 애니메이션에서는 reduce-motion을 반드시 존중해야 합니다.

인접한 Apple UI 패턴에 대해서는 SwiftUI 레이아웃 프로토콜 심층 분석, SF Pro 타이포그래피 시스템, Symbol Effects 어휘를 읽어 보세요.

한 줄짜리 API와 레이어링 규칙

Apple은 WWDC 2025에서 주요 디자인 기둥으로 소개한 Liquid Glass를 작은 표면적으로 제공합니다:113

Text("Hello, World!")
    .font(.title)
    .padding()
    .glassEffect()                              // default: .regular variant, Capsule shape

Text("Hello, World!")
    .glassEffect(in: .rect(cornerRadius: 16))   // custom shape

Text("Hello, World!")
    .glassEffect(.regular.tint(.orange).interactive())  // tint + touch reactivity

세 가지 손잡이: 변형(.regular 또는 .clear), 모양(임의의 Shape), 그리고 GlassEffectStyle 체인(tint, interactive)입니다. 이것이 단일 뷰에 대한 API의 전부입니다. 여러 뷰의 렌더링은 별도의 GlassEffectContainer가 처리하는데, 이는 뒤에서 다루겠습니다.

HIG는 API보다 더 엄격합니다. Apple의 Human Interface Guidelines는 모든 iOS 26+ 인터페이스에 두 개의 레이어를 정의합니다:3

  1. 콘텐츠 레이어: 사용자가 소비하는 문서, 목록, 사진, 미디어입니다. 여기에는 표준 머티리얼(기존의 .regularMaterial, .thinMaterial 등)을 사용합니다.
  2. 기능적 레이어: 컨트롤, 내비게이션, 탭 바, 사이드바, 일시적 오버레이입니다. 여기에는 Liquid Glass를 사용합니다.

Apple의 구체적인 지침은 이렇습니다: “콘텐츠 레이어에 Liquid Glass를 사용하지 마세요. Liquid Glass는 인터랙티브 요소와 콘텐츠를 명확하게 구분해 줄 때 가장 잘 작동하며, 이를 콘텐츠 레이어에 포함하면 불필요한 복잡성과 혼란스러운 시각적 계층 구조로 이어질 수 있습니다.”3

이 규칙은 실제 앱에 적용해 보기 전까지는 제약적으로 들립니다. Return은 명상 타이머입니다. 그 콘텐츠 레이어는 호흡 이미지와 모든 것 뒤에서 실행되는 반복 영상입니다. 그 기능적 레이어는 시간 선택기, 시작/일시정지/정지 버튼 스택, 보조 설정 버튼 행, 그리고 (tvOS에서는) 일시정지 상태 배지입니다. Return의 아홉 가지 glass 사용 중 여덟 가지는 교과서적인 기능적 레이어 적용입니다: iOS 및 macOS 코드 경로용 세 가지 시간 선택기 변형, 시작/일시정지 토글, 정지 버튼, 설정 버튼 행, tvOS 일시정지 표시기, 그리고 하나 더 있는 일시적 컨트롤 오버레이입니다.4

아홉 번째는 의도적인 경계 사례(타이머 숫자 자체에 적용한 Liquid Glass)이며, 다음 섹션에서 이를 자세히 살펴봅니다.

패턴 1: 커스텀 Shape 위의 Glass

Return의 타이머 숫자는 glass 배경 위에 그려진 텍스트가 아닙니다. glass가 바로 텍스트입니다. .glassEffect(.clear, in:)는 임의의 Shape를 받으며,9 ShapePath를 생성하는 프로토콜입니다.10 그래서 요령은 이렇습니다: Core Text를 사용해 타이머 문자열을 글리프 패스로 변환한 다음,11 그 패스(Shape로서의)를 .glassEffect에 전달하는 것입니다.56

import SwiftUI
@preconcurrency import CoreText

struct GlassTextShape: Shape {
    let text: String
    let font: CTFont

    func path(in rect: CGRect) -> Path {
        guard !text.isEmpty else { return Path() }
        let combinedPath = CGMutablePath()

        let attrString = NSAttributedString(string: text, attributes: [.font: font])
        let line = CTLineCreateWithAttributedString(attrString)
        guard let runs = CTLineGetGlyphRuns(line) as? [CTRun], !runs.isEmpty else {
            return Path()
        }

        for run in runs {
            let glyphCount = CTRunGetGlyphCount(run)
            guard glyphCount > 0 else { continue }
            var glyphs = [CGGlyph](repeating: 0, count: glyphCount)
            var positions = [CGPoint](repeating: .zero, count: glyphCount)
            let range = CFRange(location: 0, length: glyphCount)
            CTRunGetGlyphs(run, range, &glyphs)
            CTRunGetPositions(run, range, &positions)

            for i in 0..<glyphCount {
                guard let glyphPath = CTFontCreatePathForGlyph(font, glyphs[i], nil) else { continue }
                let transform = CGAffineTransform(translationX: positions[i].x, y: positions[i].y)
                combinedPath.addPath(glyphPath, transform: transform)
            }
        }

        // Core Text y-axis is flipped vs SwiftUI; flip then re-bound and center.
        var swiftPath = Path(combinedPath).applying(CGAffineTransform(scaleX: 1, y: -1))
        let flippedBounds = swiftPath.boundingRect
        let offsetX = rect.midX - flippedBounds.midX
        let offsetY = rect.midY - flippedBounds.midY
        return swiftPath.applying(CGAffineTransform(translationX: offsetX, y: offsetY))
    }
}

Return/Return/GlassTextShape.swift에서 가져온 실제 프로덕션 코드입니다.5 path(in:) 함수는 Core Text를 사용해 문자열을 배치하고, 각 CTRun을 순회하며, 각 글리프의 CGPath를 추출하여 하나의 CGMutablePath로 합칩니다. 합친 이후에 두 가지 직관적이지 않은 단계가 따라옵니다: Core Text의 좌표계는 원점을 왼쪽 아래에 두는 반면 SwiftUI의 Path는 왼쪽 위에 두므로, 패스를 CGAffineTransform(scaleX: 1, y: -1)로 뒤집어야 합니다. 그러면 뒤집힌 패스의 boundingRect는 음수 y 값을 가지므로, 이동(translation)으로 SwiftUI가 Shape에 건네주는 rect 안에 다시 중앙 정렬합니다. 두 변환 중 하나라도 건너뛰면 글리프가 거꾸로 또는 화면 밖에 렌더링됩니다.

그러면 적용은 한 줄입니다:

Rectangle()
    .fill(.clear)
    .glassEffect(.clear, in: textShape)
    .frame(width: cellWidth, height: cellHeight)

clear Rectangle은 히트 타겟 자리 표시자이며, 실제 시각 요소는 textShape가 만들어내는 모양입니다. 글리프 패스 모양을 사용하면 Liquid Glass 머티리얼이 글리프 윤곽선만 채웁니다. 그 결과: 타이머의 각 숫자가 별개의 liquid glass 형태가 되어, 그 뒤에서 실행되는 애니메이션이 무엇이든 그것을 굴절시킵니다.6

HIG의 미묘함. Apple이 명시한 규칙은 기능적 레이어에는 Liquid Glass를, 콘텐츠 레이어에는 표준 머티리얼을이며, 하나의 명시적 예외가 있습니다: 콘텐츠 레이어의 일시적 인터랙티브 컨트롤(슬라이더, 토글)은 활성화될 때 Liquid Glass를 취할 수 있다는 것입니다.3 Return의 타이머 숫자는 컨트롤이 아니라 상태 표시입니다: Timer.publish(every: 1, ...)로부터 초당 한 번 갱신되며 탭 제스처가 없습니다(그 아래의 시작/일시정지 버튼이 상태를 토글하는 것입니다). 따라서 여기에 Liquid Glass를 적용하는 것은 의도적인 경계 사례로, 문자 그대로의 인터랙티브함보다는 의도 면에서 “일시적 인터랙티브 컨트롤”에 더 가깝습니다. 숫자는 사용자가 세션 내내 바라보는 시각적 초점이기 때문입니다. 저는 규칙을 깨는 것이 아니라 구부리는 것입니다. HIG를 엄격하게 읽는 리뷰어라면 이것이 표준 머티리얼이어야 한다고 주장할 수 있습니다. 저는 타이머가 진행 표시기와 같은 부류에 속하는 경과 시간 컨트롤 표면이라고 주장합니다. Apple의 문서는 이 사례를 직접 판단해 주지 않습니다.

Text + 배경이 아니라 커스텀 Shape인가. glass 배경 위에 렌더링된 Text는 glass 위의 텍스트로 읽힙니다. glass 자체로 렌더링된 Text는 다른 시각적 범주로 읽힙니다. 사용자는 그 숫자를 기능적 전경으로, 구체적으로는 바라보기 위한 것이 아니라 통해서 보기 위해 존재하는 일시적 요소로 인식합니다.

패턴 2: 거울 패턴

Return은 타이머 아래에 그 반사를 보여주며, 점차 사라지게 합니다. 실제 프로덕션 코드:6

Mac의 Return — Liquid Glass HUD 오버레이로서의 시간 선택기, 위쪽에 타이머 텍스트의 glass 처리가 보임

VStack(spacing: 0) {
    GlassTimerText(text: displayTime, fontSize: fontSize)
        .accessibilityLabel("Time remaining: \(accessibleDescription)")

    if showReflection {
        GlassTimerText(text: displayTime, fontSize: fontSize)
            .scaleEffect(x: 1, y: -1)
            .mask(
                LinearGradient(
                    stops: [
                        .init(color: .white.opacity(0.2), location: 0),
                        .init(color: .clear, location: 0.6)
                    ],
                    startPoint: .top,
                    endPoint: .bottom
                )
            )
            .offset(y: -8)
            .accessibilityHidden(true)
    }
}

세 가지 변환이 거울을 구성하며, 모두 표준 SwiftUI 프리미티브입니다:14

  1. scaleEffect(x: 1, y: -1)은 두 번째 복사본을 위아래로 뒤집습니다.
  2. .mask(LinearGradient(...))은 반사를 상단의 20% 불투명도에서 60% 지점의 완전 투명으로 페이드합니다.
  3. .offset(y: -8)은 반사를 8포인트 위로 끌어올려, 눈에 띄는 이음매를 남기지 않고 원본에 맞닿게 합니다.

반사에 적용된 .accessibilityHidden(true) modifier는 결정적인 역할을 합니다. VoiceOver가 거울에 비친 시간을 두 번 읽어서는 안 됩니다. 원본의 accessibilityLabelaccessibilityAddTraits(.updatesFrequently)은 이미 위쪽의 메인 GlassTimerText 인스턴스에 붙어 있으며, 반사는 순전히 장식적입니다.

왜 이것이 특히 Liquid Glass에서 잘 작동하는가. 반사는 GlassTimerText로부터 glass 머티리얼을 상속받습니다. 원본이 놓인 어떤 배경(호흡하는 원의 그라데이션, 영상, 색조가 입혀진 장면)이든 두 복사본 모두를 통해 굴절됩니다. 거울에는 glass 전용 코드가 전혀 필요 없습니다. glass 머티리얼이 굴절을 공짜로 처리합니다. 전체 효과는 세 개의 modifier와 하나의 그라데이션입니다.

접근성 비용. reduce-motion 사용자는 여전히 거울을 보지만, 시간 갱신 사이의 glass 머티리얼 애니메이션은 @Environment(\.accessibilityReduceMotion)을 통해 다른 곳에서 억제됩니다.7 반사 자체는 정적이며, 숫자 전환 사이의 모핑만 애니메이션됩니다.

패턴 3: 일시적 컨트롤을 위한 Glass HUD 오버레이

Return의 나머지 여덟 가지 glass 사용은 교과서적인 기능적 레이어 적용입니다.4 각각은 동일한 패턴을 따릅니다:

Apple Watch의 Return — 작은 캔버스 크기에서의 기능적 레이어 Liquid Glass

durationPicker
    .frame(height: 50)
    .frame(maxWidth: 320)
    .glassEffect()
    .padding(.horizontal, 20)
    .transition(.opacity.combined(with: .scale(scale: 0.95)))

.transition(.opacity.combined(with: .scale(scale: 0.95)))이 결정적인 부분입니다. 일시적 컨트롤 위의 Liquid Glass는 그 컨트롤이 전환할 때에만 알맞게 느껴집니다. 화면에 영구적으로 머무는 정적 glass HUD는 크롬(chrome)으로 읽힙니다. 사용자가 탭할 때 페이드+스케일로 나타나고 시선을 거둘 때 다시 사라지는 glass HUD는 순간적인 컨트롤 표면으로 읽힙니다.

glassEffect에 대한 Apple의 문서는 이를 암묵적으로 언급합니다: 이 modifier는 “컨테이너로 보내 렌더링할 콘텐츠를 캡처”하고 “터치 및 포인터 상호작용에 실시간으로 반응”합니다.1 애니메이션 훅은 API에 들어 있지 않지만, 렌더링 파이프라인은 glass 요소가 움직인다고 가정합니다. 정적 glass 요소는 그 어포던스를 놓칩니다.

Return은 이 패턴을 시간 선택기(사용자가 탭하면 위로 슬라이드), 시작/일시정지 토글 버튼(항상 보이지만 누를 때 스케일됨), 정지 버튼(세션 중에만 보임), 설정 버튼 행(시간 선택기 아래의 가로 컨트롤 스트립), 그리고 tvOS 일시정지 상태 배지(Apple TV에서 세션이 일시정지된 경우에만 보임)에 사용합니다. 다섯 가지 맥락 모두 HIG의 기능적 레이어 규칙을 존중합니다.3

Apple의 tvOS 참고 자료: 영상 배경 위에 떠 있는 Liquid Glass 오버레이가 아래 미디어를 비쳐 보이게 함 — 정석적인 10피트 UI 사용 사례

Apple HIG 참고 자료: tvOS의 Liquid Glass 오버레이는 미디어 레이어 위에 놓여 그것을 비쳐 보이게 합니다.3 Return의 tvOS 구현은 동일한 모델을 따릅니다: 컨트롤이 반복되는 불/숲/물 배경 위에 떠 있습니다.

랩에서 얻은 toolbar 관련 메모. WWDC 2026 SwiftUI Group Lab 패널은 이것이 toolbar에서 어떻게 펼쳐지는지를 더 명확히 했습니다. toolbar에 비(非)glass 콘텐츠를 넣을 때 — 예시는 프로필 사진이었습니다 — 항목이 공유 glass 배경을 떨구고 사진을 직접 보여주도록 sharedBackgroundVisibility(.hidden)이 필요합니다.15 패널은 한 가지 개선점을 짚었습니다: 더 새로운 API는 toolbar 버튼의 콘텐츠 여백만 제거하는데, 이전에는 배경을 숨기는 것이 유일한 레버였고 그것은 배경과 여백을 함께 제거했습니다. toolbar 버튼 자체에 대해서는, 패널은 버튼에 .glassEffect를 직접 적용하기보다 두드러진 버튼 스타일(glassProminent)과 buttonBorderShape로 방향을 잡았습니다. 그 바탕에 깔린 HIG 입장은 이 글이 전제로 삼는 바로 그 레이어링 규칙과 같습니다: Liquid Glass는 콘텐츠 영역이 아니라 크롬에 속한다는 것인데, 그 아래로 콘텐츠가 스크롤되지 않는 glass 표면은 굴절시킬 것이 없기 때문입니다.153

GlassEffectContainer 문제

Apple은 앱이 여러 뷰에 .glassEffect()를 사용할 때마다 GlassEffectContainer를 권장하며, 그 이유는 두 가지입니다: 더 나은 렌더링 성능(glass 효과가 일괄 처리됨)과 전환 중에 모양을 서로 변형(morph)시킬 수 있는 능력입니다.1

저는 그것을 사용하지 않았습니다. 그 근거는 Apple의 지침에 대한 반박이 아니라 애플리케이션에 특화된 것입니다. Return에는 아홉 개의 glass 뷰가 있지만, 그 어느 것도 서로 변형될 필요가 없습니다.46 시간 선택기는 결코 시작 버튼으로 애니메이션되지 않습니다. 타이머 텍스트는 결코 설정 버튼 행으로 애니메이션되지 않습니다. 각 glass 요소는 독립적입니다. 변형 API는 발동할 사용 사례가 없을 것이고, 컨테이너의 간격 규칙은 오늘날 아무런 조율이 필요 없는 레이아웃을 제약할 것입니다.

렌더링 성능 논거는 측정 없이는 완전히 반박할 수 없습니다. Apple의 문서는 컨테이너 밖에서 “너무 많은” glass 효과가 성능을 저하시킬 수 있다고 경고합니다.1 Return의 아홉 개 뷰는 결코 한 번에 화면을 공유하지 않습니다(시간 선택기는 메뉴 상태에서만, 정지 버튼은 세션 중 일시정지될 때만 나타납니다). 임의의 프레임에서 저는 보이는 glass 요소를 서너 개로 셉니다. 이는 제가 테스트한 iOS, iPadOS, macOS, watchOS, tvOS 전 기기에서 매끄러웠지만, 컨테이너로 감싼 경우와 modifier만 쓴 경우를 비교하는 instruments 추적은 실행해 보지 않았습니다. 그래서 솔직하게 표현하자면: Return은 측정된 성능 동등성이 아니라 관찰된 양호한 사용자 경험에 근거하여 GlassEffectContainer를 건너뜁니다.

여기서 제가 끌어낸 규칙: GlassEffectContainer는 여러 glass 요소가 동시에 보이며 애니메이션되는 앱을 위한 것이다. Apple의 예시는 glassEffectUnion(id:namespace:)를 활용한 심볼 세트 렌더링입니다: 네 개의 날씨 심볼이 하나의 단위로 유연하게 합쳐지고 갈라집니다.1 그것은 교과서적인 사용 사례입니다. 미래의 Return 기능이 glass 요소를 변형하거나 컨테이너의 간격 규칙을 공유해야 한다면, 그때 컨테이너가 추가할 올바른 도구입니다. 오늘날의 앱에서는 아직 그 경우에 도달하지 않았습니다.

저를 사로잡았던 함정들

프로덕션에서 나온 실제 버그 세 가지:

Glass 숫자 떨림. SF Pro Rounded는 비례 렌더링에서 너비가 가변적인 숫자를 가집니다. 타이머가 카운트다운되면서 표시된 문자열의 길이가 바뀌었고, 둘러싼 HStack이 매초 다시 흐름을 잡으며 타이머 전체가 떨렸습니다. 해결책: 각 문자에 고정 너비 셀을 적용했습니다. 각 숫자는 fontSize * 0.6cellWidth를, 각 콜론은 fontSize * 0.3을 가지며, HStack은 안정적인 그리드가 됩니다.6

HStack(spacing: 0) {
    ForEach(Array(text.enumerated()), id: \.offset) { _, char in
        let isColon = char == ":"
        let cellWidth = isColon ? colonCellWidth : digitCellWidth
        GlassDigitCell(character: String(char), font: ctFont,
                       cellWidth: cellWidth, cellHeight: cellHeight)
    }
}

이 셀들은 Apple 표준이 아닙니다. 작은 고정 폰트 크기에서의 비례 너비 렌더링에 대한 우회책입니다. Apple의 SF Pro Rounded에 .monospacedDigit()을 쓰면 Text에서 동일한 문제를 해결하겠지만, 그 modifier는 커스텀 Shape 기반 glass 렌더러에서는 사용할 수 없습니다. 고정 셀 레이아웃이 그 대체물입니다.

tvOS 레이아웃 방향 재정의. 동일한 GlassTimerText가 iOS, iPadOS, macOS, tvOS에서 실행되었습니다. 특히 tvOS에서는 iOS 버전이 환경 내 재정의를 존중했음에도, HStack이 오른쪽에서 왼쪽(RTL) 언어 환경에서 거울처럼 뒤집혔습니다. 해결책: 레이아웃 방향을 환경 값과 명시적 flipsForRightToLeftLayoutDirection(false) modifier 둘 다로 고정하되, 이를 숫자 셀의 HStack에 직접 적용했습니다(부모 VStack은 반사 복사본이 상속받도록 환경 재정의를 별도로 적용합니다):6

HStack(spacing: 0) { ... }
    .flipsForRightToLeftLayoutDirection(false)
    .environment(\.layoutDirection, .leftToRight)

이유: tvOS의 HStack은 일부 버전에서 환경 수준의 재정의를 무시하는 것으로 보이며, flipsForRightToLeftLayoutDirection(false)이 더 안정적으로 존중되는 명시적 비(非)미러 계약입니다.12 만전을 기한 이중 안전장치입니다.

숫자 모핑에서의 reduce-motion. Liquid Glass는 기본적으로 표시된 문자열 사이의 모핑 전환을 애니메이션합니다. accessibilityReduceMotion을 켠 사용자는 그 모핑을 깜박임으로 보았습니다. 해결책:6

.animation(reduceMotion ? nil : .easeInOut(duration: 0.15), value: displayTime)

이 애니메이션 modifier는 @Environment(\.accessibilityReduceMotion)을 읽고 reduce-motion이 켜져 있을 때 전환을 완전히 비활성화합니다. Apple의 접근성 지침은 명확합니다: 모든 장식적 애니메이션은 사용자의 모션 환경설정을 존중해야 합니다.7

Liquid Glass를 사용하지 말아야 할 때

거절도 디자인의 일부입니다.

콘텐츠 레이어에 Liquid Glass를 넣지 마세요. Apple의 HIG는 명시적이며, 이 규칙을 무시하면 혼란스러운 계층 구조가 생깁니다: 사용자가 무엇이 인터랙티브이고 무엇이 콘텐츠인지 구분할 수 없게 됩니다.3 glass 효과가 목록 행이나 사진 카드를 장식하고 있다면, 그 디자인은 플랫폼과 싸우는 중입니다.

평평한 배경 위에 glass를 사용하지 마세요. Liquid Glass는 그 뒤에 있는 것을 굴절시킵니다. “그 뒤에 있는 것”이 단일한 단색이라면, 굴절은 구부릴 것이 없고 결과는 평평한 색조의 사각형으로 읽힙니다. glass를 다양한 콘텐츠(그라데이션, 이미지, 영상) 위에 두거나, 아니면 glass를 아예 사용하지 마세요. Return의 타이머 화면은 VideoBackgroundView를 통해 테마 기반 커버 이미지와 반복 영상을 배경으로 실행하는데,4 이는 바로 그 위의 glass 요소가 굴절시킬 질감을 언제나 갖도록 하기 위함입니다.

고빈도 콘텐츠 위의 glass는 신중히 다루세요. glass 머티리얼 렌더링은 GPU에 묶여 있고, glass 모양 변경 사이의 기본 모핑 애니메이션 자체도 하나의 애니메이션입니다. 초당 한 번 갱신되는 타이머는 제 테스트에서 괜찮습니다. 60Hz의 파형이나 오디오 비주얼라이저는 검증되지 않았고 모핑 애니메이션과 충돌할 가능성이 높습니다. 저는 상한선을 벤치마크하지 않았으니, 이를 측정된 임계값이 아니라 경험칙으로 받아들이세요. Apple의 문서도 하나를 공표하지 않습니다.

reduce-motion을 테스트하지 않고 glass를 출시하지 마세요. 모든 glass 애니메이션은 accessibilityReduceMotion에 따라 차단되어야 합니다.7 glass 모양 사이의 기본 모핑은 단순한 페이드가 아니라 운동성 효과입니다.

iOS 26+에 출시하는 앱에 Liquid Glass가 의미하는 것

논지는 작습니다. Liquid Glass는 앱이 이미 HIG 레이어링 규칙을 존중할 때에만 한 줄짜리 API입니다. 컨트롤을 기능적 레이어에, 콘텐츠를 콘텐츠 레이어에 두는 SwiftUI 앱은 .glassEffect() modifier로 Liquid Glass를 채택하고 기본적으로 네이티브하게 느껴질 수 있습니다.

두 레이어를 섞는 앱(목록 행 안의 컨트롤, 콘텐츠로 취급되는 내비게이션 바, 사진 카드 위의 장식적 크롬)은 Liquid Glass를 채택해도 잘못된 느낌을 줄 것입니다. 머티리얼은 옳지만, 그 아래의 아키텍처는 그렇지 않습니다.

커스텀 Shape 패턴(패턴 1)은 이 규칙을 깔끔하게 확장합니다. 기능적으로 컨트롤인 것은 무엇이든, 통상적인 의미에서 “컨트롤”처럼 보이지 않더라도 Liquid Glass를 취할 수 있습니다. 타이머는 컨트롤이고, 레벨 미터는 컨트롤이며, 진행 표시기는 컨트롤입니다. 그 각각 위의 Liquid Glass는 사양에 부합합니다.

이 글을 같은 앱의 데이터 레이어를 App Intents를 통해, 그리고 MCP 서버를 통해 출시한 제 이전 글들과 함께 읽어 보세요. 시각적 레이어는 동일한 스택의 세 번째 표면입니다: 시스템 AI를 위한 타입화된 엔티티, 교차 LLM 에이전트를 위한 파일 포맷, 그리고 기기 앞의 사람을 위한 Liquid Glass입니다.8

FAQ

iOS 26이 아닌 플랫폼에서 .glassEffect()를 사용할 수 있나요?

.glassEffect() modifier는 iOS 26+, iPadOS 26+, macOS 26+, watchOS 26+, tvOS 26+, visionOS 26+에서 동작합니다. 26 이전 플랫폼에는 .background(.regularMaterial) 등이 있어 frosted-glass 효과를 내지만, 새로운 Liquid Glass 굴절은 아닙니다.1

GlassEffectContainer는 시각 요소를 바꾸나요?

컨테이너로 감싼 glass 요소들은 간격 규칙이 겹침을 유발할 때 서로의 모양을 섞을 수 있습니다. 컨테이너가 없으면 각 .glassEffect()는 독립적입니다. glass 요소가 애니메이션 중에 유연하게 합쳐져야 하는 앱이라면 GlassEffectContainer가 올바른 도구입니다. 각 glass 요소가 별개로 유지되어야 하는 앱이라면 컨테이너는 부담입니다.1

Text.foregroundStyle(.thinMaterial)과 함께 직접 사용하지 않나요?

thinMaterial은 Liquid Glass가 아니라 표준 머티리얼입니다. 그 시각 요소는 frosted-glass 오버레이이지, Liquid Glass의 빛을 구부리는 굴절형 glass 효과가 아닙니다.3 구체적으로 새 머티리얼처럼 보여야 하는 텍스트에는 .glassEffect(.clear, in: customShape)가 지원되는 경로입니다.

마케팅용 Liquid Glass 스크린샷은 어떻게 캡처하나요?

glass 효과는 런타임에 GPU로 렌더링되므로, 스크린샷은 효과가 이미 적용된 상태로 시뮬레이터나 기기에서 촬영합니다. Apple의 공식 Liquid Glass 참고 이미지는 HIG 문서 페이지와 WWDC 2025 세션에서 나옵니다.3

GlassTextShape는 임의의 텍스트에 동작하나요, 아니면 숫자에만 동작하나요?

Core Text가 배치할 수 있는 모든 문자열에 동작합니다. Return은 숫자와 콜론에 사용하지만, 동일한 Shape가 글자, 심볼, 이모지(올바른 폰트로), 또는 혼합 문자열에도 동작합니다. 성능은 글리프 개수에 따라 좌우됩니다. glass로 렌더링한 긴 문단은 비용이 크겠지만, 여섯 글자짜리 타이머는 사소합니다.


세 가지 패턴, 하나의 규칙, 그리고 제가 의도적으로 건너뛴 하나의 API입니다. Liquid Glass는 iOS 26+ 앱의 세 번째 표면으로, 타입화된 엔티티와 공유 파일 포맷 위에 놓입니다. 한 줄짜리 API는 실재합니다. 그 아래의 HIG 규칙이 한 줄짜리를 작동하게 만드는 것입니다.

참고 자료


  1. Apple Developer, “Applying Liquid Glass to custom views”. glassEffect(_:in:) modifier, GlassEffectContainer, glassEffectUnion(id:namespace:), glassEffectID(_:in:), GlassEffectTransition에 대한 문서. 기본 변형 .regular, 기본 모양 Capsule

  2. 저자의 Return, 2026년 4월 21일 App Store에 출시된 명상 타이머 앱으로 iPhone, iPad, Mac, Apple Watch, Apple TV에서 사용 가능. iOS 26+ / macOS 26+에서 SwiftUI, SwiftData, HealthKit 사용. 

  3. Apple Developer, “Materials” Human Interface Guidelines. Liquid Glass에 대한 기능적 레이어 대 콘텐츠 레이어 규칙을 정의: “콘텐츠 레이어에 Liquid Glass를 사용하지 마세요.” regular 및 clear 변형과 그 의도된 용도를 나열. 

  4. Return/Return/ContentView.swift(일곱 개의 .glassEffect() 호출 지점), Return/Return/GlassTimerText.swift(GlassDigitCell의 한 호출 지점), Return/ReturnTV/TVContentView.swift(tvOS “Paused” 표시기의 한 호출 지점)의 프로덕션 코드. 총 아홉 개. 더불어 glass 요소가 통해서 굴절시키는 테마 기반 커버 이미지와 반복 영상을 렌더링하는 Return/Return/VideoBackgroundView.swift

  5. Return/Return/GlassTextShape.swift의 프로덕션 코드. Core Text를 감싼 Shape 준수 래퍼. 2025년 11월 26일 생성, 출시된 App Store v1.0에 포함. 

  6. Return/Return/GlassTimerText.swift의 프로덕션 코드. GlassDigitCell, GlassTimerText, GlassTimerDisplay 뷰. 고정 너비 셀 레이아웃, 거울 반사, reduce-motion 차단을 구현. 

  7. Apple Developer, “accessibilityReduceMotion” 환경 값. 앱은 사용자의 모션 환경설정을 존중해야 하며, Liquid Glass의 기본 모핑 애니메이션은 그 값에 따라 차단되어야 함. 

  8. 저자의 분석 App Intents Are Apple’s New API to Your AppTwo Agent Ecosystems, One Shopping List. 세 표면 모델: Apple Intelligence를 위한 App Intents, 교차 LLM 에이전트를 위한 MCP, 기기 앞의 사람을 위한 Liquid Glass. 

  9. Apple Developer, View“glassEffect(_:in:isEnabled:)”. in: 매개변수는 임의의 Shape 준수 타입을 받음. 기본 모양은 Capsule

  10. Apple Developer, “Shape” protocol. Shape는 주어진 사각형에 대해 Path를 생성하는 모든 타입. 커스텀 모양은 임의의 CGPath 데이터를 감쌀 수 있음. 

  11. Apple Developer, “Core Text Programming Guide”CTLineCreateWithAttributedString. Core Text는 속성 문자열을 글리프 런으로 배치하고 글리프별 패스를 추출하는 데 사용되는 하위 수준 텍스트 엔진. 

  12. Apple Developer, “flipsForRightToLeftLayoutDirection(_:)”. 둘러싼 \.layoutDirection 환경 값과 무관하게 View의 RTL 미러링을 명시적으로 재정의. 

  13. Apple, “WWDC 2025 Highlights” via Apple Newsroom. iOS 26, iPadOS 26, macOS 26, watchOS 26, tvOS 26, visionOS 26 전반의 통일 디자인 머티리얼로 Liquid Glass 발표. 세션: “Meet Liquid Glass” (WWDC 2025), “Build a SwiftUI app with Liquid Glass”

  14. Apple Developer, “LinearGradient”, “scaleEffect(x:y:anchor:)”, “mask(_:)”. 표준 SwiftUI 프리미티브로 모두 iOS 13부터 사용 가능. 

  15. Apple, WWDC 2026 SwiftUI Group Lab (session 2). WWDC 2026 SwiftUI Group Lab (session 2)의 로컬 전사 녹음에서 의역; Apple은 랩에 대한 공식 자막을 게시하지 않음. 비(非)glass 콘텐츠(프로필 사진)를 보여주기 위해 toolbar 항목의 공유 glass 배경을 숨기는 것, 배경과 여백을 함께가 아니라 toolbar 버튼의 콘텐츠 여백만 제거하는 더 새로운 API, 버튼에 .glassEffect를 적용하기보다 두드러진 glass 버튼 스타일과 buttonBorderShape를 선호하는 것, 그리고 콘텐츠가 아닌 크롬 레이어링 근거에 대한 출처. 뒷받침하는 심볼들은 CustomizableToolbarContentsharedBackgroundVisibility(_:)에 문서화되어 있으며 .hidden 같은 Visibility 값을 받고, buttonBorderShape(_:)도 마찬가지이다; glassProminent는 두드러진 Liquid Glass PrimitiveButtonStyle이다. 랩에서 언급된 구체적인 콘텐츠 여백 API는 Apple의 게시된 문서와 대조하여 확인할 수 없었으므로, 심볼로 단언하기보다 동작으로 설명한다. 

관련 게시물

iOS 26의 HealthKit + SwiftUI: 권한 요청, 샘플 타입, 그리고 두 개의 앱을 출시하며 얻은 크로스 플랫폼 패턴

Water(물 섭취량 추적, HKQuantitySample)와 Return(마음챙김 세션, HKCategorySample)에서 가져온 실제 프로덕션 패턴. 권한 UX, async 래퍼, watchOS 변형, 그리고 …

11 분 소요

SwiftUI Layout 프로토콜: sizeThatFits부터 placeSubviews까지 커스텀 레이아웃 구축하기

iOS 16의 Layout 프로토콜은 두 가지 메서드로 앱이 커스텀 레이아웃을 구축할 수 있게 합니다. sizeThatFits, placeSubviews, 제안 계약, 레이아웃 값, 그리고 캐싱.

9 분 소요

취향은 인프라다

에이전트가 출시되는 결과물의 더 많은 부분을 생성할수록, 품질의 상한선은 미적 판단을 시스템에 얼마나 잘 인코딩하느냐에 달려 있어요. 취향은 쿼리 가능한 형태가 될 때 확장돼요.

5 분 소요