리퀴드 글라스 디자인 시스템 (iOS 26)은(는) 무엇을 하나요?
Apple의 Liquid Glass를 구현하기 위한 패턴입니다. Liquid Glass는 뒤에 있는 콘텐츠를 블러 처리하고 주변 콘텐츠의 색상과 빛을 반사하며 터치 및 포인터 상호작용에 반응하는 동적 재질입니다. SwiftUI, UIKit 및 WidgetKit 통합을 다룹니다.
활성화 시점
- iOS 26+ 앱을 새로운 디자인 언어로 구축하거나 업데이트할 때
- 유리 스타일의 버튼, 카드, 툴바 또는 컨테이너를 구현할 때
- 유리 요소 간의 모핑 전환을 만들 때
- 위젯에 Liquid Glass 효과를 적용할 때
- 기존 블러/재질 효과를 새로운 Liquid Glass API로 마이그레이션할 때
핵심 패턴 — SwiftUI
기본 유리 효과
모든 뷰에 Liquid Glass를 추가하는 가장 간단한 방법:
Text("Hello, World!")
.font(.title)
.padding()
.glassEffect() // 기본값: regular 변형, 캡슐 모양
모양 및 색조 커스터마이징
Text("Hello, World!")
.font(.title)
.padding()
.glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0))
주요 커스터마이징 옵션:
.regular— 표준 유리 효과.tint(Color)— 두드러지게 하기 위해 색상 색조 추가.interactive()— 터치 및 포인터 상호작용에 반응- 모양:
.capsule(기본값),.rect(cornerRadius:),.circle
유리 버튼 스타일
Button("Click Me") { /* action */ }
.buttonStyle(.glass)
Button("Important") { /* action */ }
.buttonStyle(.glassProminent)
여러 요소를 위한 GlassEffectContainer
여러 유리 뷰는 항상 컨테이너로 감싸서 성능 최적화와 모핑을 활성화하세요:
GlassEffectContainer(spacing: 40.0) {
HStack(spacing: 40.0) {
Image(systemName: "scribble.variable")
.frame(width: 80.0, height: 80.0)
.font(.system(size: 36))
.glassEffect()
Image(systemName: "eraser.fill")
.frame(width: 80.0, height: 80.0)
.font(.system(size: 36))
.glassEffect()
}
}
spacing 매개변수는 병합 거리를 제어합니다. 요소가 가까울수록 유리 모양이 함께 혼합됩니다.
유리 효과 결합
glassEffectUnion을 사용하여 여러 뷰를 하나의 유리 모양으로 결합합니다:
@Namespace private var namespace
GlassEffectContainer(spacing: 20.0) {
HStack(spacing: 20.0) {
ForEach(symbolSet.indices, id: \.self) { item in
Image(systemName: symbolSet[item])
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectUnion(id: item < 2 ? "group1" : "group2", namespace: namespace)
}
}
}
모핑 전환
유리 요소가 나타나거나 사라질 때 부드러운 모핑을 만듭니다:
@State private var isExpanded = false
@Namespace private var namespace
GlassEffectContainer(spacing: 40.0) {
HStack(spacing: 40.0) {
Image(systemName: "scribble.variable")
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectID("pencil", in: namespace)
if isExpanded {
Image(systemName: "eraser.fill")
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectID("eraser", in: namespace)
}
}
}
Button("Toggle") {
withAnimation { isExpanded.toggle() }
}
.buttonStyle(.glass)
사이드바 아래로 수평 스크롤 확장
수평 스크롤 콘텐츠가 사이드바 또는 인스펙터 아래로 확장되도록 하려면 ScrollView 콘텐츠가 컨테이너의 앞쪽/뒤쪽 가장자리에 닿도록 하세요. 레이아웃이 가장자리까지 확장되면 시스템이 자동으로 사이드바 아래 스크롤 동작을 처리합니다. 추가 수정자는 필요하지 않습니다.
핵심 패턴 — UIKit
기본 UIGlassEffect
let glassEffect = UIGlassEffect()
glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3)
glassEffect.isInteractive = true
let visualEffectView = UIVisualEffectView(effect: glassEffect)
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
visualEffectView.layer.cornerRadius = 20
visualEffectView.clipsToBounds = true
view.addSubview(visualEffectView)
NSLayoutConstraint.activate([
visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
visualEffectView.widthAnchor.constraint(equalToConstant: 200),
visualEffectView.heightAnchor.constraint(equalToConstant: 120)
])
// contentView에 콘텐츠 추가
let label = UILabel()
label.text = "Liquid Glass"
label.translatesAutoresizingMaskIntoConstraints = false
visualEffectView.contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor)
])
여러 요소를 위한 UIGlassContainerEffect
let containerEffect = UIGlassContainerEffect()
containerEffect.spacing = 40.0
let containerView = UIVisualEffectView(effect: containerEffect)
let firstGlass = UIVisualEffectView(effect: UIGlassEffect())
let secondGlass = UIVisualEffectView(effect: UIGlassEffect())
containerView.contentView.addSubview(firstGlass)
containerView.contentView.addSubview(secondGlass)
스크롤 가장자리 효과
scrollView.topEdgeEffect.style = .automatic
scrollView.bottomEdgeEffect.style = .hard
scrollView.leftEdgeEffect.isHidden = true
툴바 유리 통합
let favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction))
favoriteButton.hidesSharedBackground = true // 공유 유리 배경에서 제외
핵심 패턴 — WidgetKit
렌더링 모드 감지
struct MyWidgetView: View {
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
if renderingMode == .accented {
// 강조 모드: 흰색 강조, 테마 유리 배경
} else {
// 전체 색상 모드: 표준 모양
}
}
}
시각적 계층을 위한 Accent Groups
HStack {
VStack(alignment: .leading) {
Text("Title")
.widgetAccentable() // Accent 그룹
Text("Subtitle")
// 기본 그룹 (기본값)
}
Image(systemName: "star.fill")
.widgetAccentable() // Accent 그룹
}
강조 모드에서 이미지 렌더링
Image("myImage")
.widgetAccentedRenderingMode(.monochrome)
컨테이너 배경
VStack { /* content */ }
.containerBackground(for: .widget) {
Color.blue.opacity(0.2)
}
주요 디자인 결정
| 결정 | 근거 |
|---|---|
| GlassEffectContainer 래핑 | 성능 최적화, 유리 요소 간 모핑 활성화 |
spacing 매개변수 | 병합 거리 제어 — 요소가 혼합되기 위해 얼마나 가까워야 하는지 미세 조정 |
@Namespace + glassEffectID | 뷰 계층 변경 시 부드러운 모핑 전환 활성화 |
interactive() 수정자 | 터치/포인터 반응을 명시적으로 선택 — 모든 유리가 반응할 필요는 없음 |
| UIKit의 UIGlassContainerEffect | SwiftUI와 동일한 컨테이너 패턴으로 일관성 유지 |
| 위젯의 강조 렌더링 모드 | 사용자가 강조 홈 화면을 선택하면 시스템이 색조 유리를 적용 |
모범 사례
- 항상 GlassEffectContainer를 사용하여 여러 형제 뷰에 유리를 적용할 때 — 모핑을 활성화하고 렌더링 성능을 향상시킵니다
- 다른 모양 수정자(frame, font, padding) 후에
.glassEffect()를 적용하세요 - 사용자 상호작용에 반응하는 요소(버튼, 토글 가능 항목)에만
.interactive()를 사용하세요 - 컨테이너에서 간격을 신중하게 선택하여 유리 효과가 병합되는 시점을 제어하세요
- 뷰 계층을 변경할 때
withAnimation을 사용하여 부드러운 모핑 전환을 활성화하세요 - 다양한 모양(라이트 모드, 다크 모드, 강조/색조 모드)에서 테스트하세요
- 접근성 대비를 확인하세요 — 유리 위의 텍스트는 읽기 쉬워야 합니다
피해야 할 안티 패턴
- GlassEffectContainer 없이 여러 개의 독립적인
.glassEffect()뷰 사용 - 너무 많은 유리 효과 중첩 — 성능과 시각적 선명도 저하
- 모든 뷰에 유리 적용 — 대화형 요소, 툴바, 카드용으로 예약
- UIKit에서 코너 반경을 사용할 때
clipsToBounds = true를 잊는 것 - 위젯에서 강조 렌더링 모드를 무시하는 것 — 색조 홈 화면 모양이 깨짐
- 유리 뒤에 불투명 배경을 사용하는 것 — 반투명 효과가 무효화됨
사용 시점
- 새로운 iOS 26 디자인의 네비게이션 바, 툴바, 탭 바
- 플로팅 액션 버튼 및 카드 스타일 컨테이너
- 시각적 깊이와 터치 피드백이 필요한 대화형 컨트롤
- 시스템의 Liquid Glass 모양과 통합되어야 하는 위젯
- 관련 UI 상태 간의 모핑 전환