SwiftUI - symbolEffect
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 SwiftUI의 symbolEffect에 대해 알아보겠습니다 🙋🏻
symbolEffect
symbolEffect는 SF Symbol 즉, System Font Symbol의 스타일을 변경하고자할 때 사용하는 뷰 모디파이어 입니다.
여기서 SF Symbol은 기본적으로 iOS에서 제공되는 시스템 아이콘이라고 보시면 됩니다 😃
해당 뷰 모디파이어는 iOS 17.0 이상에서 사용할 수 있으며, 정의는 아래와 같습니다.
nonisolated
func symbolEffect<T, U>(
_ effect: T,
options: SymbolEffectOptions = .default,
value: U
) -> some View where T : DiscreteSymbolEffect, T : SymbolEffect, U : Equatable
일반적인 뷰 모디파이어처럼, 적용하고자하는 뷰에 붙여주는것인데요.
적용할 효과를 effect 파라미터로 주며 옵션도 설정할 수 있고 해당 애니메이션 효과를 트리거할 value 값도 지정할 수 있습니다.
여기서 effect와 value는 필수 값이라고 보면 됩니다!
쉽게 말해 value가 변함에 따라 트리거가되어 해당 적용된 SF Symbol이 애니메이션을 보여주는 그런 그림이라고 보면 됩니다.
당연히, effect는 뷰에 추가할 심볼 이펙트이며, SwiftUI의 Image에 적용됩니다.
즉, Image 컴포넌트 사용 시 systemName으로 SF Symbol을 지정해줌으로 적용할 수 있겠죠?
만약 별도의 커스텀한 이미지를 에셋에 담아 사용한다면 SF Symbol이 아니기에 아무런 반응이 없습니다.
(이건 또 아래에서 실습해보면서 같이 알아보시죠ㅎㅎ)
이렇게 value가 변할때마다 트리거가되어 애니메이션 효과를 줄 수 도 있지만, 아래와 같이 isActive를 가진 이니셜라이저를 통해 해당 효과의 활성화를 컨트롤하도록 생성할 수도 있어요.
nonisolated
func symbolEffect<T>(
_ effect: T,
options: SymbolEffectOptions = .default,
isActive: Bool = true
) -> some View where T : IndefiniteSymbolEffect, T : SymbolEffect
기본값은 true로 항상 활성화를 시켜둘 수도 있고, 상위 뷰에서 프로퍼티를 두어 바인딩시켜 적용시킬 수도 있겠죠?
또한, 하나 더 전체에 해당 symbolEffect를 적용하였더 하더라도, 특정 이미지에선 해당 effect를 사용하고 싶지 않을 수 있습니다.
그럴땐, symbolEffectsRemoved 뷰 모디파이어를 사용하면 됩니다.
nonisolated
func symbolEffectsRemoved(_ isEnabled: Bool = true) -> some View
이렇게 적용한다면 해당 이미지 뷰에서는 효과가 제외됩니다.
물론, 여기서도 isEnabled 인자로 컨트롤할 수도 있죠!
위 초기화 메서드에서 effect 파라미터를 볼때 DiscreteSymbolEffect와 IndefiniteSymbolEffect 타입인걸 볼 수 있었는데요.
둘다 프로토콜로 DiscreteSymbolEffect는 일시적인 애니메이션을 수행하도록 하는 효과 프로토콜이며, IndefiniteSymbolEffect는 비활성화 되거나 제거될때까지 심볼에 지속적으로 영향을 미치는 애니메이션 프로토콜입니다.
이에 적절히 사용할 수 있으면 됩니다!
또 중요한 부분이 options 인자인데요.
SymbolEffectOptions 타입으로 총 5가지 분류를 할 수 있어요.
1️⃣ default - 기본 적용되는 효과
2️⃣ repeating - 무한 반복 효과
3️⃣ repeating(Int?) - 몇 회 반복할지에 대한 적용 효과
4️⃣ nonRepeating - 반복하지 않고 1회만 적용
5️⃣ speed(Double) - 효과 빠르기를 적용
이렇게 원하는것을 선택하여 구현할 수 있죠.
그럼 이제 본격적으로 간단하게 만들어볼까요?
import SwiftUI
struct ContentView: View {
@State private var counter: Int = 0
var body: some View {
VStack {
Button(
action: { counter += 1 },
label: {
Text("Tap Tap!")
}
)
Image(systemName: "folder.fill.badge.person.crop")
.resizable()
.frame(width: 150, height: 100)
}
.symbolEffect(.bounce, value: counter)
}
}
기본적으로 이런 구성으로 SF Symbol에 적용할 수 있습니다.
그럼 이제, 버튼을 클릭하면 바운스 효과를 가지며 애니메이션 되겠죠?
적용이 아주 간단하죠?
symbolEffect를 중첩으로도 다양하게 effect 및 options을 주어 사용할 수도 있어요.
import SwiftUI
struct ContentView: View {
@State private var counter: Int = 0
var body: some View {
VStack {
Button(
action: { counter += 1 },
label: {
Text("Tap Tap!")
}
)
Image(systemName: "folder.fill.badge.person.crop")
.resizable()
.frame(width: 150, height: 100)
}
.symbolEffect(.bounce, value: counter)
.symbolEffect(.pulse, options: .repeating, value: counter)
}
}
bounce는 한번만, 그리고 Pulse 효과는 무한으로 반복됩니다.
지금은 VStack에 전체 적용했지만, 만약 해당 VStack에 여러 심볼이 존재하고 같이 효과를 적용했을때 특정 심볼은 효과를 적용하지 않도록 할 수 있다고 했죠?
한번 적용해볼까요?
import SwiftUI
struct ContentView: View {
@State private var counter: Int = 0
var body: some View {
VStack {
Button(
action: { counter += 1 },
label: {
Text("Tap Tap!")
}
)
Image(systemName: "square.and.arrow.up")
.resizable()
.frame(width: 150, height: 100)
.symbolEffectsRemoved()
Image(systemName: "folder.fill.badge.person.crop")
.resizable()
.frame(width: 150, height: 100)
}
.symbolEffect(.bounce, value: counter)
.symbolEffect(.pulse, options: .repeating, value: counter)
}
}
이제 위 이미지는 적용되지 않아야겠죠?
이렇게 자유롭게 애니메이션이 트랜지션되는것도 컨트롤할 수 있네요ㅎㅎ
주의할점은 다시 한번 말하지만, 기본 내장된 SF Symbol에 적용되기에 직접 에셋에 추가한 이미지는 적용되지 않습니다!
마무리
사실 SF Symbol을 사용하는것보다 주로 커스텀한 이미지를 가지고 아이콘을 활용하기에 이 symbolEffect를 활용하는 경우가 잘 있을지 모르겠지만, 이걸 토대로 이미지도 이렇게 애니메이션 될 수 있는걸 한번 구현해봐도 재밌겠다 싶었습니다 😃