[TIL #5] 23 / 03 / 23
하... Tint...
오늘은 이미지를 불러와 버튼을 생성하는데,
이미지가 작아서 크기 변경이 필요했고, tint 컬러도 변경하려고 했습니다.
적용이 않돼서 한참 동안 싸우다가,
공부 겸 까먹지 않기 위해 이렇게 글을 쓰게 됐네요.
UIGraphicsImageRenderer
간단 소개를 하자면, iOS 10부터 추가된 클래스로,
Core Graphic을 통해 쉽게 이미지를 그릴 수 있는 API를 제공합니다.
자세한 내용은 공식문서 를 참고하세요.
UIGraphicsImageRenderer를 통해 출력 이미지를 렌더링을 할 수 있습니다.
공식문서에 자세히 나와있지만, 간단히 사용해 봅시다.
UIGraphicsImageRenderer 객체 생성
그런데 UIImage
타입이 아니죠? 우리가 사용해야 하는 건 이미지 타입인데...
이미지로 변환하려면 추가 작업이 필요합니다.
// 이미지에서 사용될 크기를 정해줍니다. (여기선 영역만 만들어주므로, 이미지 렌더링 시 draw를 해줘야합니다.)
let renderer = UIGraphicsImageRenderer(size: CGSize)
이미지 렌더링
UIGraphicsImageRenderer의 image
메서드를 호출하게 되면,
클로저 내부 작업을 통해 이미지를 그리고 UIImage
를 리턴하게 됩니다.
다른 그리는 작업을 추가할 수 있습니다. (ex. 테두리, 색, 모양 등)
let image = renderer.image { context in
// 이미지를 다시 크기에 맞게 그려줍니다!
draw(in: CGRect(origin: .zero, size: CGSize))
}
익스텐션과 함께 사용하면 이와 같이 사용이 가능하겠죠?
extension UIImage {
func resized(to size: CGSize) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { _ in
draw(in: CGRect(origin: .zero, size: size))
}
}
}
참고. iOS 13 이전 버전에서는 이렇게 직접 그림을 그려야 했다고 하네요.
저도 자세한 내용은 몰라서 코드만 첨부하겠습니다.
extension UIImage {
/// 불러온 이미지 사이즈 변경
///
/// From : https://leechamin.tistory.com/558
private func resized(to size: CGSize) -> UIImage? {
// 비트맵 생성
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
// 비트맵 그래픽 배경에 이미지 다시 그리기
self.draw(in: CGRect(origin: CGPoint.zero, size: size))
// 현재 비트맵 그래픽 배경에서 이미지 가져오기
guard let resizedImage = UIGraphicsGetImageFromCurrentImageContext() else {
return nil
}
// 비트맵 환경 제거
UIGraphicsEndImageContext()
// 크기가 조정된 이미지 반환
return resizedImage
}
}
Tint Color를 변경해 보자.
기본 SF Symbols을 사용한 시스템 이미지 같은 경우, image
의 tintColor
로 접근하면 변경이 가능하지만,
사용자 이미지 같은 경우에는 따로 Assets에서 Render를 설정을 해줘야 합니다.
왜냐하면, 이미지라서 어떤 부분에 색상을 처리할지 알 수가 없기 때문이죠. (템플릿 이미지로 처리)
직접 인스펙터를 드리지 않고, 코드로 설정하는 방법을 소개하겠습니다.
일단 UIImage
의 메서드 중에 이와 같이 withRenderingMode
라는 메서드가 존재합니다.
동작은 현재 이미지를 복제한 뒤, 선택한 렌더링에 따라 새로운 이미지로 반환한다고 합니다.
그냥 간단히 말해서, 이미지가 그려지는 방법을 결정하는 것 같네요.
func withRenderingMode(_ renderingMode: UIImage.RenderingMode) -> UIImage
그럼 어떠한 방법이 있나 살펴봅시다.
RenderingMode
에는 3가지 렌더링 중 선택이 가능합니다.
automatic
기본값으로, 시스템이 적절한 렌더링을 자동으로 선택됩니다.
alwaysOriginal
이미지를 원본 그대로, 즉 갖고 있는 원래 색상 그대로를 표현합니다.
alwaysTemplate
이미지가 템플릿 이미지로 처리되어, 적용된 tintColor에 따라 이미지의 색상이 변경됩니다.
(템플릿 이미지 : 불투명한 부분을 제외한 나머지 이미지 부분을 하나로 묶어서 렌더링 시킨다.)
이제 이미지에 TInt Color를 적용할 수 있겠죠?
바로 사용방법을 봅시다.
이와 같이 UIImage
에서 withRenderingMode
로 접근하여 사용하면 됩니다.
let image = UIImage(named: "image")?.withRenderingMode(.alwaysTemplate)
이번에도 간편하게 재사용하기 위해 익스텐션으로 만들어 봅시다.
짠!
extension UIImage {
static func imageWithRenderingModeAlwaysTemplate(named: String) -> UIImage? {
let image = UIImage(named: named)?.withRenderingMode(.alwaysTemplate)
return image
}
프로젝트 중 문제점
이미지 Tint Color도 변경하고 싶고, Size도 변경하고 싶은데...
위에서 만들어준 코드를 아래와 같이 사용하게 되면,
일단 크기는 변경이 되지만, Tint Color는 안 먹고, 이미지의 원래 색상(검정)이 나타나는 문제가 생겼습니다.
func createCurrentLocationButton(size: CGFloat) -> UIButton {
let button = UIButton(type: .custom)
// 이미지의 렌더링 모드 선택
let image = UIImage.imageWithRenderingModeAlwaysTemplate(named: "currentLocation")
// UIGraphicsImageRenderer를 통한 이미지의 크기 변환
let resizedImage = image?.resized(to: CGSize(width: size, height: size))
button.setImage(resizedImage, for: .normal)
button.tintColor = HexCode.selected.color
}
여기 문제는 아마도 이미지 크기를 변경할 때,
resized
에서 draw
를 통해 이미지를 새로 그려주게 되므로 적용이 안 되는 걸로 보이는데요.
그래서 해결방법을 두 가지 만들어 봤습니다.
1. 크기가 변형된 이미지에 withRenderingMode
를 alwaysTemplate
으로 바꿔주기
적용이 되긴 하는데 코드가 너무 지저분하네요.
func createCurrentLocationButton(size: CGFloat) -> UIButton {
let button = UIButton(type: .custom)
let image = UIImage.imageWithRenderingModeAlwaysTemplate(named: "currentLocation")
// 렌더링 모드 변경
let resizedImage = image?.resized(to: CGSize(width: size, height: size)).withRenderingMode(.alwaysTemplate)
button.setImage(resizedImage, for: .normal)
button.backgroundColor = HexCode.unselected.color
}
또, 여기서 이상한 게, 이렇게 객체를 받아서 설정을 해주면 적용이 안되더라고요...
(이유 아시는 분 댓글을 부탁드립니다. 😓)
let resizedImage = image?.resized(to: CGSize(width: size, height: size))
resizedImage.withRenderingMode(.alwaysTemplate)
2. 만들어둔 resized
메서드에 Tint Color까지 적용하여 렌더링 하기
일단 파라미터로 Tint Color를 받을 수 있게 추가해 준 뒤,
이미지 렌더링 부분에서 모든 이미지 작업(크기, 렌더링 모드, 틴트색)을 처리해 주기
extension UIImage {
func resized(to size: CGSize, tintColor: UIColor) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { _ in
// 적용할 tint 색상 설정
tintColor.setFill()
// 렌더링 모드 변경 후 이미지 그리기
withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: size))
}
}
}
이렇게 만들어주면, 나름 깔끔하게 사용이 가능해졌네요.
func createCurrentLocationButton(size: CGFloat) -> UIButton {
let button = UIButton(type: .custom)
let image = UIImage(named: "currentLocation")
let resizedImage = image?.resized(to: CGSize(width: size, height: size),
tintColor: HexCode.selected.color)
button.setImage(resizedImage, for: .normal)
}
마무리
이렇게 오늘도 간단히 이미지의 틴트 컬러 및 크기 조절 방법을 배워봤습니다.
왜? 객채로 받아서 렌더링 모드를 변경하면, 적용이 안되는지는 도저히 이해가 안 가네요...
(일단은 패스)
여기서 알아야 할 건!
1. 커스텀 이미지에 틴트색을 적용하려면 렌더링 모드를 템플릿으로 바꿔야 한다
2. 각종 출력 이미지의 처리는 UIGraphicsImageRenderer
에서 image
의 클로저를 통해 처리가 가능하다.
참고로 틴트색을 HexCode를 통해 변경하고 싶으면,
저번에 정리해 둔 [iOS/Swift] UIColor (RGB대신 HEX Color 이용하기) 를 참고해 주세요.
'프로젝트' 카테고리의 다른 글
[Swift/TIL #7] MKAnnotationView를 직접 만들어 보자 (0) | 2023.03.28 |
---|---|
[Swift/TIL #6] MapKit을 사용해 보자 (4) | 2023.03.25 |
[Swift/TIL #4] Pop 버튼 애니메이션 (원 반경에 따라 버튼 생성 해보자) (2) | 2023.03.22 |
[Swift/TIL #3] 탭바 아이템을 버튼으로 교체 with CGAffineTransform(회전) (0) | 2023.03.19 |
[Swift/TIL #2] CAShapeLayer 이용한 탭바 구현 (0) | 2023.03.17 |