layoutSubviews(), draw() 는 언제 사용할까? 뷰를 업데이트 하고싶을 때 원리.

Doyeong Kim·2023년 7월 7일
0

Swift

목록 보기
2/9

문제:

  • 컬렉션뷰 셀 안에 있는 gradient 뷰가 첫 시점에 안나오고 스크롤을 한번 다녀와야지만 적용이 되는 문제.

위 문제의 원인이 뭘까..🤔

테이블뷰 셀 안에 컬렉션뷰 셀에서 화면 reload 하는 시점이 애매해서 gradient 뷰의 프레임을 제대로 못받아오는 것 같다.

해결방법!
레이아웃 업데이트를 시켜주자! 어떻게??

  • layoutSubviews()
  • draw()
  • layoutIfNeeded()
  • setNeedsLayout()
  • setNeedsDisplay()
  • displayIfNeeded()

다 비슷한거 아녀..? 너무 헷갈려...

일단 첫번째로 override 함수인 layoutSubviews()를 호출해서 그 안에 gradientLayer의 프레임을 gradientView의 크기로 줘봤지만.. 무용지물 ㅠㅠ

override func layoutSubviews() {
	super.layoutSubviews()
    	gradientLayer.frame = gradientView.bounds
    }
}

그래서 어 혹시 layoutIfNeeded()를 해주면 되려나? 싶어서 gradientView 오토레이아웃 잡은 후에 넣었더니 바로 적용이 된다..! 띠용

그럼 draw override 함수를 써봐도 되려나 싶어서 해봤더니 이것도 바로 잡힘!

override func draw(_ rect: CGRect) {
	gradientLayer.frame = gradientView.bounds
}

draw 함수에서는 super를 호출할 필요가 없다고 한다. 다른 커스텀 뷰를 상속받을때만 호출해주면 됨. 난 그냥 collectionViewCell 안에서 호출하는거니 super 호출 X.

그래도 헷갈려.. 더 알아보자

1. setNeedsDisplay

이 메서드는 drawRect 메서드랑 같이 사용됨. 이 메서드는 내부적으로 drawRect를 호출하면서 뷰를 다시 그림.

뷰의 컨텐츠가 바꼈을 때 이 메서드로 뷰가 변경됐다라는 걸 알린후에 drawRect 호출해서 뷰를 다시 그리는 것.

뷰가 계속 움직이거나 업데이트 되어야할 때 등등 속성의 변경 사항을 반영하려면 setNeedsDisplay를 호출하면 됨.

주의사항으로는,
drawRect 함수를 절대 직접 호출하면 안된다!! setNeedsDisplay로 다음 업데이트 요청하면 됨.

2. setNeedsLayout

이 메서드는 아무런 작업 수행 X. 시스템에 뷰 업데이트가 필요하다고 trigger 하는 역할. 그 전 뷰 레이아웃을 무효화 시키고 다음 업데이트 주기때 한번에 업데이트 시키기 위함.
퍼포먼스에 도움이 된다.

import UIKit

class CircleView: UIView {
    private var center: CGPoint = .zero
    private var radius: CGFloat = 0.0
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        // center와 radius에 기반하여 원을 그리기 위해 사용자 정의 그리기 수행
        let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
        path.stroke()
    }
    
    func updateCircle(center: CGPoint, radius: CGFloat) {
        self.center = center
        self.radius = radius
        
        setNeedsLayout() // 레이아웃 업데이트 트리거
        setNeedsDisplay() // 다시 그리기 트리거
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 필요한 경우 추가적인 레이아웃 조정 수행
    }
}

3. layoutIfNeeded

이 메서드는 뷰 업데이트를 바로 적용하고 싶을때 사용하면 됨. 위에 setNeedsDisplay나 setNeedsLayout 같은 경우는 다음 업데이트 주기를 기다리는 거라면 이 메서드는 그냥 지금 바로! 느낌.

정리해보자면,

  • drawRect 함수는 직접 호출 X. 커스텀 뷰 만들어서 setNeedsDisplay 메서드 호출해서 사용.
  • layoutSubviews 함수도 직접 호출 X. layoutIfNeeded나 setNeedsLayout 호출.

결론은,

내 앱에서는 뷰가 계속 바껴야 하는 건 아니고, 그냥 처음에 셀 세팅될 때 gradient만 바로 적용이 되면 되기 때문에 drawRect 메서드를 호출할 필요도 없고 layoutSubviews 메서드도 셀이 재사용될때마다 계속 호출이 되기때문에 그럴 필요가 없다고 판단했다.

그래서 init 시점에서 뷰를 다 그려준 후에 gradientLayer의 프레임을 잡아주기 전에 layoutIfNeeded() 메서드만 호출 해 바로 레이아웃 업데이트 완료를 해주었다.

override init(frame: CGRect) {
	super.init(frame: frame)
	configure()
	makeConstraints()
	setGradientLayer()
}
    
private func setGradientLayer() {
	gradientLayer = CAGradientLayer()
    gradientView.layoutIfNeeded()
    gradientLayer.frame = gradientView.bounds
    gradientLayer.colors = [UIColor(red: 78, green: 175, blue: 168).cgColor, MacaColors.turquoise600.cgColor]
    gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.2)
    gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.8)
    gradientView.layer.addSublayer(gradientLayer)
}

결과!!

이제 바로 나온다!

출처:
https://stackoverflow.com/questions/10818319/when-do-i-need-to-call-setneedsdisplay-in-ios
https://zeddios.tistory.com/359

profile
신비로운 iOS 세계로 당신을 초대합니다.

0개의 댓글