SwiftUI - Color 혼합하기
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 간단한 SwiftUI의 새로운 기능을 알아보려 합니다 🙋🏻
바로 SwiftUI에서 색상을 적용시키는 Color를 사용할때 새로 나온 mix 메서드를 이용해 컬러를 혼합하여 새로운 컬러를 도출해내보는것이죠!
정말 간단히 바로 알아볼까요?
Color mix
우선 기본적으로 Color 타입을 간단히 짚고 넘어가볼께요.
Color는 SwiftUI에서 색상을 표현하는 구조체입니다.
@frozen
struct Color
표현하는 방식으로는 에셋 카탈로그에서 색상을 로드하거나, RGB 값을 넣거나 색조, 채도, 밝기 등을 지정하여 편하게 표현할 수 있어요.
let aqua = Color("aqua")
let skyBlue = Color(red: 0.4627, green: 0.8392, blue: 1.0)
let lemonYellow = Color(hue: 0.1639, saturation: 1, brightness: 1)
let steelGray = Color(white: 0.4745)
그리고 UIColor나 NSColor를 이용해 Color도 뽑아낼 수 있죠.
#if os(iOS)
let linkColor = Color(uiColor: .link)
#elseif os(macOS)
let linkColor = Color(nsColor: .linkColor)
#endif
가장 기본적으로는 이미 Color에 정의된 색상들을 아래와 같이 이미지나 뷰에 적용하여 사용하죠.
Image(systemName: "leaf.fill")
.foregroundStyle(Color.green)
또한, 컬러 자체가 뷰 인스턴스로 취급되기에 뷰 계층에 바로 추가할 수도 있어요.
ZStack {
skyBlue
Image(systemName: "sun.max.fill")
.foregroundStyle(lemonYellow)
}
.frame(width: 200, height: 100)
자 이정도가 정말 간단한 Color에 대한 개념이였어요.
그럼 mix에 대해 살펴볼까요?
mix
두 색상을 지정된 양만큼 혼합하여 새로운 색상을 반환시켜주는 메서드입니다.
func mix(
with rhs: Color,
by fraction: Double,
in colorSpace: Gradient.ColorSpace = .perceptual
) -> Color
여기서 rhs는 혼합할 컬러를 나타냅니다.
그리고 fraction은 혼합할 정도 즉, 수치를 나타냅니다.
0.5로 준다면 양쪽 반반으로 혼합됨을 의미하죠.
마지막으로 colorSpace는 색상을 혼합하는데 사용되는 색상 공간을 의미합니다.
ColorSpace 타입에는 device와 perceptual 값이 존재해요.
device는 장치 색상 공간에서 그라디언트 색상을 보간해주는것이고,
perceptual은 지각적 색상 공간에서 그라데이션 색상을 보간해주는 차이가 있어요.
device는 RGB나 CMYK와 같은 디바이스에 종속적인 색상 공간에서 직접 색상을 보간하기에 단순한 직선적인 보간을 수행해요.
그렇기에 사람들이 볼때 약간 부자연스러운 중간 색상이 생길수도 있죠.
반면, perceptual은 지각적이라는 즉, 사람들의 시각 체계가 색상을 인식하는 방식을 고려한 방식으로 색상을 보간합니다.
그렇기에 더 자연스럽고 특히 서로 다른 밝기나 채도를 가진 색상끼리의 혼합일때 자연스럽죠.
예를들어, device로 빨강과 파랑을 믹스한다고 치면 이렇습니다.
수치를 반으로 동일하게 섞어 보라색이 나오지만 약간 탁한 보라색이 생길 수 있어요.
반면, perceptual로 동일하게 믹스해보면 이렇습니다.
조금 더 보기에 자연스럽고 선명한 중간 색상을 만들죠.
보통은 디자인이나 UI에서는 지각적인 보간을 선호하기에 기본값도 perceptual로 설정되있다고 느껴지네요 😃
그래서 이제 이 mix 메서드를 사용해볼까요?
import SwiftUI
struct ContentView: View {
var color: Color = .red.mix(
with: .blue,
by: 0.5
)
var body: some View {
Rectangle()
.frame(width: 300, height: 300)
.foregroundStyle(color)
}
}
아주 간단히 이렇게 사용해볼 수 있습니다.
기준이 될 색상에다가 mix 메서드로 혼합하고 싶은 색상을 넣어주고 혼합 정도를 넣어주면 됩니다.
만약 여러 색상 혼합이 필요하다?
let color = Color.red
.mix(with: .blue, by: 0.3)
.mix(with: .green, by: 0.2)
요렇게 체이닝하여 표현해줘도 충분하죠.
아주 간단하죠?
사실 그러면 여기서 하나 의문이 들거에요.
그라데이션도 아니고, 단순 색상 혼합인데 그럼 기존 Color 인스턴스 생성 시 RGB 값을 그 색상으로 넣으면 되는거 아닌가??
// mix 사용
let purple = Color.red.mix(with: .blue, by: 0.5)
// RGB 직접 계산
let purple = Color(red: 0.5, green: 0, blue: 0.5)
요런식으로?
맞아요 😁
둘다 결과는 같지만 mix 메서드의 장점도 있죠.
1️⃣ RGB 값을 직접 계산하는것보다 좀 더 자연스러운 혼합으로 더 정교한 색상 보간이 가능해집니다.
2️⃣ 내부적으로 더 복잡한 색상 공간 변환과 보간 알고리즘을 사용합니다.
3️⃣ mix 메서드 네이밍만 봐도 색상을 혼합했다는 의미기에 의도 표현이 확실해지고 가독성이 좋아집니다.
4️⃣ 애니메이션 개발 시 색상 변화를 위해 fraction 비율만 변화시켜 더 편리한 동적 색상 변경이 용이합니다.
여기서 저는 4번이 끌리는 이유입니다.
이렇게도 구현해볼 수 있어요.
struct ContentView: View {
@State private var dragAmount: CGFloat = 0
var dragColor: Color {
Color.green.mix(
with: .yellow,
by: min(max(dragAmount / 100, 0), 1)
)
}
var body: some View {
Rectangle()
.frame(width: 300, height: 300)
.foregroundStyle(dragColor)
.animation(.spring, value: dragAmount)
.gesture(
DragGesture()
.onChanged { gesture in
dragAmount = gesture.translation.width
}
.onEnded { _ in
dragAmount = 0
}
)
}
}
해당 뷰를 드래그 할때 자연스럽게 혼합되면서 색상이 변환되는 애니메이션을 구현할 수 있죠.
이런 이유들이 납득된다면 RGB 값으로 직접 지정하는것보다 mix 메서드를 사용하는게 더 권장될 수 있을것 같습니다 😄
마무리
사소하지만, 재밌는 mix 메서드에 대해 알아봤습니다.
사실 현업에서 개발할때는 에셋 카탈로그에 컬러들을 정의해두고 각 프로젝트마다 디자인 시스템에 의한 컬러들을 많이 사용하기에 이렇게까지 코드로 혼합해서 쓸일이 많이 있을까? 싶은 생각이 들었는데, 애니메이션과 같은것들을 염두한다면 충분히 사용할 수 있겠더라구요.