[iOS] UIView의 Life Cycle (생명주기)

2023. 3. 9. 22:42iOS/iOS

728x90
반응형

👉 시작

이제 스토리보드가 아닌 코드베이스 UI로 구현하는 것이 편해지고 익숙해졌다.

MVC패턴을 구현하면서 View 와 ViewController 를 분리하게 되면서 UIView 를 많이 다루게 되었다.

UIView를 UIViewController에 연결하면서 호출순서가 헷갈리기 시작했다.

UIView의 메서드들을 공부하면서 서로의 호출순서를 비교해보기로 했다.

 

또한 코드베이스 UI를 만들면 init(frame: ) 을 사용하는 데 required init(coder: )를 생성해야 에러가 사라지는 것을 볼 수 있다.

xcode에서 바로 에러를 잡아주어서 단순히 넘어갔지만 공부를 해보기로 했다.

먼저 공부를 하고 나서 사용했어야 했는데 순서가 잘못되었지만 그래도 지금이라도 궁금해져서 다행이다 ㅎㅎ

제대로 알고 사용해야지 ...

 

 


👉 UIView 

✔️ init(frame: )

  • View의 인스턴스를 만들기 위해 지정된 이니셜라이저이다.
  • 스토리보드나 xib 파일을 사용하지 않고 코드로 만들었을 때 사용해야하는 이니셜라이저이다.
  • CGRect frame 사각형으로 center와 bounds 지정해준다.

 

✔️ init(coder: )

  • 스토리보드나 xib파일은 활용하여 화면을 만들 때 컴파일러가 인식할 수 있게 코드로 변환해주는 과정에서 사용한다.
  • 코드로 작성했을 때는 사용하지 않지만 선언해주어야 한다.
    • 인터페이스 빌더를 사용하지 않을 경우에도 결국 UIView는 NSCoding 프로토콜을 채택하고 있기 때문에 구현해 줘야 한다.
      NSCoding 선언부에 실패가능한 이니셜라이저를 작성하도록 되어있기 때문이다.

 

✔️ willMove(toSuperView: )

  • 슈퍼뷰가 변경될 것이라고 알려주는 메소드이다.
  • 이 메소드는 기본적으로 기능을 구현하지 않는다. 슈퍼뷰가 변경될 때마다 추가 작업을 수행하도록 재정의할 수 있다.

 

✔️ didMoveToSuperview( )

  • 슈퍼뷰가 변경되었다는 것을 알려주는 메소드이다.

 

✔️ awakeFromNib( )

  • 인터페이스 빌더 또는 nib 파일에서 로드된 후 생성되는 메소드이다.

 

✔️ willMove(toWindow: )

  • window가 변경될 것이라고 알려주는 메소드이다.
  • 이 메소드는 기본적으로 기능을 구현하지 않는다. 윈도우가 변경될 때마다 추가 작업을 수행하도록 재정의할 수 있다.

 

✔️ didMoveToWindow( )

  • window가 변경되었다는 것을 알려주는 메소드이다.

 

✔️ updateConstraints( )

  • 뷰의 제약조건을 업데이트하는 메소드이다.
  • 제약조건을 제자리에서 변경하는 것이 너무 느리거나 뷰가 많은 중복 변경을 하는 경우에 사용한다.
  • 버튼 이벤트로 제약조건을 변경하는 경우에는 버튼 이벤트 메서드에서 직접 변경하는 것이 좋다.

 

✔️ layoutSubviews( )

  • 하위 뷰를 배치하는 메소드이다.
  • iOS 5.1 이하 버전에서는 아무 작업을 하지 않지만
    그 이상에서는 설정한 제약조건을 사용하여 하위 뷰의 크기와 위치를 결정한다.
  • 해당 메소드는 직접 호출하면 안된다.
    레이아웃을 강제로 업데이트하려면 setNeedsLayout() 을 사용하고 (다음 사이클에 적용)
    즉시 업데이트하려면 layoutIfNeeded() 를 사용해야 한다.
  • 모든 하위 뷰의 메소드도 호출이 되기 때문에 뷰가 쌓였을 때 호출하면 큰 부하가 생긴다.
    레아이웃의 제약조건이 완료가 되었을 때 ViewController의 viewDidLayoutSubviews 에 호출한다.

 

✔️ draw( )

  • 전달된 CGRect rect 사각형으로 뷰를 그리는 메소드이다.
  • 뷰가 처음 표시되거나 뷰의 보이는 부부을 무효화하는 이벤트가 발생할 때 호출된다.
  • 해당 메소드는 직접 호출하면 안된다. 뷰의 일부를 무효화하여 해당 부분을 다시 그리게 하려면 setNeedsDisplay() 또는 setNeedsDisplay(_ :)를 사용해야 한다.

 

 


👉 UIView 와 UIViewController 메소드 호출

UIView.swift

import UIKit

class View: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        print("View : \(#function)")
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
        print("View : \(#function)")
    }
    
    override func addConstraints(_ constraints: [NSLayoutConstraint]) {
        super.addConstraints(constraints)
        print("View : \(#function)")
    }
    
    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        print("View : \(#function)")
    }
    
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        print("View : \(#function)")
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        print("View : \(#function)")
    }
    
    override func willMove(toWindow newWindow: UIWindow?) {
        super.willMove(toWindow: newWindow)
        print("View : \(#function)")
    }
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        print("View : \(#function)")
    }
    
    override func updateConstraints() {
        super.updateConstraints()
        print("View : \(#function)")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        print("View : \(#function)")
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        print("View : \(#function)")
    }
}

  • 인터페이스 빌더를 사용하지 않기에 해당 메소드는 사용하지 않는다.

 

 

UIViewController.swift

import UIKit

class ViewController: UIViewController {
    
    override func loadView() {
        super.loadView()
        print("ViewController : \(#function)")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("ViewController : \(#function)")
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("ViewController : \(#function)")
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        print("ViewController : \(#function)")
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        print("ViewController : \(#function)")
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("ViewController : \(#function)")
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("ViewController : \(#function)")
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        print("ViewController : \(#function)")
    }
}

  • 뷰를 연결하지 않고 앱을 실행했을 때의 결과는 ViewController의 메서드만 출력된다.

 

 

 

간단하게 코드로 View에 라벨을 추가하여 출력함수의 차이를 보자요~

class View: UIView {
    lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.text = "제목"
        label.font = UIFont.boldSystemFont(ofSize: 30)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        print("View : \(#function)")
        self.backgroundColor = .white
        
        self.addSubview(titleLabel)
        self.titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0).isActive = true
        self.titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
    }
    
    ... 중략
}
  • lazy var 로 라벨을 만든다.
  • 배경을 흰색으로 설정한다.
  • View에 라벨을 추가한다.
  • 라벨의 제약조건을 설정한다. (x축과 y축 가운데로 설정)View를 생성한다.
class ViewController: UIViewController {
    
    let mainView = View()
    
    override func loadView() {
        super.loadView()
        print("ViewController : \(#function)")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("ViewController : \(#function)")
        
        self.view = mainView
    }
    
    ...중략
}
  • Viewcontroller의 최상위뷰에 대입한다.

  • 뷰를 연결한 후의 출력 결과이다.

 

 

스토리보드를 사용한 View.swift

required init?(coder: NSCoder) {
    super.init(coder: coder)
    print("View : \(#function)")
}

override init(frame: CGRect) {
    super.init(frame: frame)
    print("View : \(#function)")
}

  • 스토리보드를 사용했을 때에는 init(coder: ) 가 호출되고 init(frame: )은 호출되지 않는다.
  • init(frame: ) 해당 메소드는 작성하지 않아도 에러가 나지 않는다.

 

 

 

 

 


📂 정리

UIView에 생각보다 메소드가 많다는 것을 알게 되었고...

제약조건이나 뷰의 변화를 주었을 때 먹히지 않는 경우 어느 메소드에 구현해야되는 지 이해가 되었다.

 

init(frame: ) : 코드베이스를 작성할 때 사용한다.

init(coder: ) : 인터페이스 빌더를 사용할 때 사용한다. 하지만 코드베이스일 때에도 꼭 작성해줘야 한다.

 

 

 

 

 

 

 

 

 

 

[예제 소스코드 깃허브 링크]

https://github.com/HANLeeeee/PracticeTest/tree/main/ViewLifeCycleTest

 

GitHub - HANLeeeee/PracticeTest

Contribute to HANLeeeee/PracticeTest development by creating an account on GitHub.

github.com

[참고자료]

https://developer.apple.com/documentation/uikit/uiview

 

UIView | Apple Developer Documentation

An object that manages the content for a rectangular area on the screen.

developer.apple.com

 

 

 

 

 

 

728x90
반응형