iOS

[Swift] 글래스모피즘 구현하기 (UIVisualEffectView, UIViewPropertyAnimator)

스츠흐 2024. 6. 3. 01:39

안녕하세요!

 

저는 새해가 되면 올해의 디자인 트렌드를 찾아보곤 합니다.

작년에는 문득 '내가 만들 수 있겠는데?'라는 생각이 들어서 글래스모피즘(Glassmorphism)과 뉴모피즘(neumorphism) 뷰를 개발하고 라이브러리 형태로 배포했었습니다.

 

올해 2024에도 디자인 트렌드를 찾아봤을 때, 글래스모피즘에 대한 언급이 지속적으로 이어지고 있더군요!

그래서 제가 만들었던 글래스모피즘뷰에 대해서 설명하는 글을 써보려고 합니다 :)

 

(Swift 언어로 작성되었고, UIKit에서 사용할 수 있는 글래스모피즘뷰 라이브러리입니다)

 

https://github.com/Chaehui-Seo/CHGlassmorphismView

 

GitHub - Chaehui-Seo/CHGlassmorphismView: Assistant for Glassmorphism UI in iOS

Assistant for Glassmorphism UI in iOS. Contribute to Chaehui-Seo/CHGlassmorphismView development by creating an account on GitHub.

github.com


 

0. 글래스모피즘이란?

글래스모피즘(Glassmorphism)은 쉽게 말해서 반투명한 재질의 유리 효과를 입힌 UI 스타일을 의미합니다. 뒷배경이 흐릿하게 비치고, 이를 통해 해당 UI요소가 배경으로부터 떠있는 듯한 느낌을 준다는 특징이 있습니다.

이름이 생소하게 느껴질 수 있지만, 글래스모피즘이 적용된 디자인은 많은 이들에게 익숙하게 느껴질 것입니다. iOS7 이후에 스큐어모피즘(실제 사물와 유사하게 디자인하는 기법) 대신 미니멀디자인을 채택하며 글래스모피즘을 이용한 디자인을 선보였습니다. Window Vista에서도 글래스모피즘을 적용했습니다.

글래스모피즘 UI

iOS6(좌) iOS7(우)의 알림센터 (출처: https://www.idownloadblog.com/2013/06/14/close-look-notification-center/)
Window vista 스크린샷 (출처: https://en.wikipedia.org/wiki/Windows_Vista)
iOS6(좌) iOS7(우)의 알림센터
(출처는 이미지 링크 참조)
Window vista 스크린샷
(출처는 이미지 링크 참조)

 

 

 

1. 설계

UI 라이브러리를 만들 때, 모두의 니즈를 완벽하게 맞추기는 어렵습니다. UI가 배치되는 모든 상황을 알기 어렵기 때문입니다. 그래서 몇 가지 커스터마이징을 할 수 있도록 public method를 제공해주기로 하였습니다. 제가 생각했을 때, 커스텀이 필요할 거 같은 요소는 크게 아래 네 가지였습니다.

  1. 블러의 강도
  2. 테마(라이트/다크)
  3. 배경과의 거리 (그림자가 퍼진 정도로 표현)
  4. 모서리 둥글기 값

 

효과를 의도한대로 보여주기 위해서는 UI에 대한 컨트롤을 제가(라이브러리가) 가지고 있어야 했습니다. 사용자가 설정한 뷰의 속성(backgroundColor, cornerRadius 등등)이 UI효과를 어그러뜨리는 결과를 낳아서는 안되었기 때문입니다.

그래서 UIView 형태로 라이브러리를 제공하고, 아래와 같이 계층을 구성했습니다.

 

BackgroundView라는 커스텀 뷰를 첫 번째 subview로 위치시키고 배경색상을 clear하게 지정함으로써, 사용자가 설정한 뷰 속성에 영향을 받지 않을 수 있었습니다.

물론, 사용자가 억지로 다른 뷰를 첫 번째 subview로 삽입할 경우 문제가 발생할 수 있습니다. 자주 발생하지 않을 경우라고 생각하긴 했지만, 어찌 되었든 이 부분도 리드미에 명시해 두었습니다.

리드미에 명시한 Caution

 

 

 

2. UIVisualEffectView

공식문서: https://developer.apple.com/documentation/uikit/uivisualeffectview

 

글래스모피즘의 핵심은 '블러'에 있다고 생각했습니다.

일반 반투명 뷰(alpha값 조절한 뷰)와 글래스모피즘 뷰의 차이는 뒷배경이 어떻게 보이는지에 있습니다. 글래스모피즘 뷰는 블러 효과가 필수적이고, iOS에서 블러를 적용하기 위해 UIVisualEffectViewUIBlurEffect 효과를 주는 방식을 선택했습니다.

위에 소개한 계층 구조 중 두 번째 subview를  UIVisualEffectView로 만들었습니다.

테마를 변경하게 하기 위해서 라이트모드일 때는 UIVisualEffectView의 effect 스타일을 .light로, 다크모드일 때는 .dark로 설정하였습니다.

private var blurView = UIVisualEffectView()
// 라이트모드일 때
self.blurView.effect =  UIBlurEffect(style: .light)
// 다크모드일 때
self.blurView.effect = UIBlurEffect(style: .dark)

 

 

 

3. UIViewPropertyAnimator

공식문서: https://developer.apple.com/documentation/uikit/uiviewpropertyanimator

 

개인적으로 UIVisualEffectView의 최대 단점은 커스텀이 어렵다는 점이라고 생각했습니다. 정해진 타입 안에서 블러 효과 선택이 가능하긴 했지만, 블러 효과의 정도를 조절하기는 어려웠습니다.

이번 라이브러리를 만들면서도 이 부분을 어떻게 극복할지 고민을 하였습니다. 블러의 강도 조절은 무조건 제공되어야 하는 커스텀 요소라고 생각했기 때문에 해결 방법을 찾아보았고, UIViewPropertyAnimator를 발견하였습니다.

UIViewPropertyAnimator는 애니메이션의 끝 상태를 설정하고, 특정 지점으로 이동시킬 수 있는 애니메이션 방법입니다.

블러뷰의 상태를 조정하기 위해서, 블러 효과를 addAnimation한 상태에서 fractionComplete 값을 통해서 특정 지점으로 이동시키는 방식을 채택했습니다. 즉, 애니메이션 재생은 사용하지 않고, 특정 지점으로 이동시키는 용으로 UIViewPropertyAnimator를 사용하였습니다.

// MARK: - 초기 설정
// 초기에는 효과 blurEffect 적용되지 않은 nil인 상태
private var blurView = UIVisualEffectView()
// 실제 애니메이션을 재생하지 않기 때문에 설정값은 무관
private let animator = UIViewPropertyAnimator(duration: 0, curve: .linear) 
// default 블러 강도는 65%로 설정
private var animatorFractionComplete: CGFloat = 0.65

func initialize() {
    // 기타 다른 설정값 생략

    // default mode인 light 모드로 애니메이션 추가
    animator.addAnimations {
        self.blurView.effect =  UIBlurEffect(style: .light)
    }
    // 애니메이션 재생 없이 특정 지점(default는 65%)로 이동
    animator.fractionComplete = animatorFractionComplete
}


// MARK: - 블러 강도 재설정 시
// density = 설정하고자 하는 강도값 (0~1의 값)
public func setBlurDensity(with density: CGFloat) {
    self.animatorFractionComplete = density
    self.animator.fractionComplete = density
}


// MARK: - 테마 변경될 때
// theme = light, dark 중 선택
public func setTheme(to theme: CHTheme) {
    // 효과, 애니메이션 초기화
    self.blurView.effect = nil
    self.animator.stopAnimation(true)
    // 테마에 따라 blur style을 애니메이션에 추가
    self.animator.addAnimations {
        self.blurView.effect = theme == .light ?
                                UIBlurEffect(style: .light) // 라이트모드로 변경 시
                                UIBlurEffect(style: .dark) // 다크모드로 변경 시
    }
    // 특정 지점으로 이동시킴
    self.animator.fractionComplete = self.animatorFractionComplete
}

 

 

 

4. 마치며

위에 언급한 부분 외에 디자인적으로 개선하고 싶은 세부 요소들을 추가하여서 아래와 같은 결과물을 만들어낼 수 있었습니다.

커스터마이징 기능 포함한 샘플 프로젝트

 

샘플 프로젝트도 라이브러리에 포함되어 있으니 참고하여서 개발한다면 쉽게 연동하실 수 있을 것 같습니다 (위에 있지만, 다시 한번 링크)

라이브러리명의 프리픽스(CH)는 제 이니셜에서 따왔는데, glassmorphism이라는 단어 자체가 길고 복잡해서 가독성에 아쉬움이 남네요..

어찌되었든 이렇게 제 첫 번째 라이브러리 배포를 해보았고, 난생처음 모르는 개발자분들의 스타⭐️를 받을 수 있었습니다.

 

더 나은 방법 제안이나 궁금한 점은 언제든 댓글로 남겨주세요 :)

감사합니다!