티스토리 뷰

iOS

[삽질 기록] GradationView 만들기

Zedd0202 2023. 8. 27. 16:13
반응형

 

그냥 삽질 기록..

내가 해결한 방법이 정답이 아닐 수 있음.

삽질은 엄청 했지만 그냥 짧게(???) 좋은 경험 한 것 같아서 남겨보려고 한다.

위와같은 뷰를 만들었어야 했는데, 딱 봤을 때 응 그냥 그라데이션이야~하고 CAGradientLayer이용하면 되지 않을까?? 싶었다.

대충 예전에 쓴 iOS ) CAGradientLayer 글을 보면서 작업을 시작했음.

나는 View는 UIKit으로 만드는게 편해서 UIKit으로 만들기 시작했다!

 

딴 이야기지만 중간에 코드 그냥 이미지로 넣은거 보고

진짜 또라인가?? 싶었다.

아니 이걸 왜 이미지로 넣으세요. 하~~~~

이미지로 넣어서 미안합니다. 

 

[첫번째 시도]

내가 원하는거 / CAGradientLayer 삽질 중 하나

CAGradientLayer만들고 이것저것 해보는데,

나는 첫번째 그림처럼 경계선이 진짜 다 풀어져있는 그런걸 원했지만 두번째 그림처럼 경계선이 너무 자기주장이 심한것이다. ㅋㅋ

iOS ) CAGradientLayer 에서 radial(방사형) 예제가 있길래 

이거야!! 싶어서 적용했다. 

내가 원하는거 / radial type 적용한거

나쁘진 않은데, 이것도 뭔가 경계선이 자기주장을 하는 느낌?

주변 백그라운드와 이질감 없이 어우러지는걸 원했고, ㅅ

사실 내가 만들려는건 구형이 아니라 사각형에 radius가 50정도 먹여져있는 그런 형태여서 (첫번째 이미지에서 거의 티는 안나지만;;)

이 방법은 패스하기로 했다.  

 

[두번째 시도]

이때부터 이거 그냥 뚝딱할 수 있는게 아닌가..? 싶어서 찾아보는데, 

이런 스택오버플로우 글을 발견. 코드를 보니 

let maskLayer = CAGradientLayer()
maskLayer.frame = yourImageView.bounds
maskLayer.shadowRadius = 5
maskLayer.shadowPath = CGPath(roundedRect: YourImagView.bounds.insetBy(dx: 5, dy: 5), cornerWidth: 10, cornerHeight: 10, transform: nil)
maskLayer.shadowOpacity = 1
maskLayer.shadowOffset = CGSize.zero
maskLayer.shadowColor = UIColor.white.cgColor        
yourImageView.layer.mask = maskLayer

shadow로 뭔가 좀 해보려고 한 것 같은데, shadow로는 내가 원하는게(경계선 자기주장 X) 절대 안나올 것 같아서 얘도 패스..

 

[세번째 시도]

그냥 뚝딱할 수 있을 줄 알았는데, 내 예상과 달라서 당황하다가 갓PT에게 물어봄

import UIKit

class BlurredBorderView: UIView {
    private let blurRadius: CGFloat = 10.0
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        // Draw the content of the view
        let contentRect = CGRect(x: blurRadius, y: blurRadius, width: bounds.width - 2 * blurRadius, height: bounds.height - 2 * blurRadius)
        context.saveGState()
        context.clip(to: contentRect)
        self.layer.render(in: context)
        context.restoreGState()
        
        // Apply blur effect to the border
        if let blurFilter = CIFilter(name: "CIGaussianBlur") {
            let borderRect = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
            blurFilter.setValue(CIImage(image: snapshotImage(view: self)), forKey: kCIInputImageKey)
            blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)
            if let outputImage = blurFilter.outputImage {
                let blurredImage = UIImage(ciImage: outputImage)
                blurredImage.draw(in: borderRect)
            }
        }
    }
    
    private func snapshotImage(view: UIView) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
        defer { UIGraphicsEndImageContext() }
        
        if let context = UIGraphicsGetCurrentContext() {
            view.layer.render(in: context)
            let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
            return snapshotImage
        }
        
        return nil
    }
}

당황.....

당연히 복붙했을 때 잘 안됐고.. 어디서부터 왜 안되는지, 뭐부터 봐야하는지 감이 안와서 이 방법도 포기했다.

그리고 내심 아니 이게 이렇게 어렵게 짜야 구현할 수 있는거야? 그럴리 없어...;;; 하는 마음도 같이 있었다.

 

[네번째 시도]

구글에 

이런게 보이길래 헉 이거다@!!!! 하고 들어갔는데, 제목에서 볼 수 있다시피 SwiftUI로 만든것이었다.

위에서 말했듯이 나는 UIKit으로 View를 만드는게 익숙해서 그냥 아묻따 UIKit으로 시작했는데.. SwiftUI로 할 생각은 못했던 것이다.

 

블로그에 나와있는 코드(오른쪽 사진)는 은근 긴데, 나는 내 버전으로 간소화 했다.

(background modifier는 iOS 15부터 사용가능해서 내 경우엔 쓸 수 없었다.)

struct ContentView: View {
        
    var body: some View {
        Rectangle()
            .frame(width: 230, height: 162)
            .padding()
            .blur(radius: 50, opaque: false)
    }
}

결과! 

다행히 이 결과는 마음에 들었고, 이걸 이제 UIView로 전환해야했다.

 

[SwiftUI -> UIKit View]

UIHostingController(rootView: ContentView()).view

UIHostingController를 이용하여 가져오면 된다고 한다. 

 

그래서..대충 아래와 같은 방법으로 쓰면 된다.

struct ContentView: View {
        
    var body: some View {
        Rectangle()
            .padding()
            .blur(radius: 50, opaque: false)
    }
}

// ViewController
self.view.backgroundColor = .systemBlue
guard let backgroundView = UIHostingController(rootView: ContentView()).view else { return }

self.view.addSubview(backgroundView)
backgroundView.snp.makeConstraints {
    $0.size.equalTo(CGSize(width: 230, height: 162))
    $0.center.equalToSuperview()
}

그 결과..

진짜 이번엔 될 줄 알았어서 그런지 살짝 당황

확인해보니 HostingView의 background가 systemBackground로 자동으로 들어간듯했다. 

self.view.backgroundColor = .systemBlue
guard let backgroundView = UIHostingController(rootView: ContentView()).view else { return }

backgroundView.backgroundColor = .clear // ✅
....

backgroundColor를 clear로 줘서 해결!

 


삽질하면서 느낀 장단점들 

 

[장점] 

UIKit에서 힘들게 해야하는 이런류의 작업들을 SwiftUI로 간단하게 할 수 있는 것 같아보인다. (UIKit에서 하는 간단한 방법이 있다면 댓글로 알려주세요)

앞으로 무조건 UIKit으로 만들기보다는, 만들어야하는 View에 따라 적절한 선택을 할 수 있을 것 같다는 생각이 들었다!! 

 

[단점]

SwiftUI에서 나오고 deprecate된것도 많고, 특정 OS버전 이상에서만 쓸 수 있는 modifier들이 많아서 적절히 변환해주는 작업이 필요하다. 

(간단한 뷰여서 그런지 다른 단점을 느낄 새가 없었던 것 같기도.. )

반응형