Danny의 iOS 컨닝페이퍼
article thumbnail

[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을 사용한 시스템 이미지 같은 경우, imagetintColor로 접근하면 변경이 가능하지만,

 

사용자 이미지 같은 경우에는 따로 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. 크기가 변형된 이미지에 withRenderingModealwaysTemplate으로 바꿔주기

 

적용이 되긴 하는데 코드가 너무 지저분하네요.

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 이용하기) 를 참고해 주세요.

 

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!