먹고 기도하고 코딩하라

[UIKit] pushViewController vs show 차이점 본문

앱/Swift

[UIKit] pushViewController vs show 차이점

사과먹는사람 2022. 8. 2. 13:28
728x90
728x90

 

어떤 문서를 보다가 pushViewController와 show 방법을 이야기하면서 필자는 show를 더 선호한다고 쓰인 문장을 봤다.

궁금해졌다. pushViewController와 show 방식 모두 내비게이션 컨트롤러 기반으로 움직이는 게 아닌가? 좀 더 자세히 살펴봐야겠다는 생각이 들어 문서를 찾아보고, 실험도 함께 곁들이려 한다.

 

 

문서

일단 pushViewController부터 본다. 

 

pushViewController(_:animated:)

뷰 컨트롤러를 수신자(receiver)의 스택에 푸시하고 디스플레이를 업데이트한다.

func pushViewController(
    _ viewController: UIViewController,
    animated: Bool
)
  • viewController : 스택에 푸시될 뷰 컨트롤러. 탭바 컨트롤러여서는 안 된다. 만약 내비게이션 스택에 이미 이 뷰 컨트롤러가 존재한다면 예외가 발생한다.
  • animated : 뷰 전환 시 발생할 애니메이션에 대한 설정값이다. 

viewController 매개변수의 객체는 내비게이션 스택의 가장 최상단에 위치하는 뷰 컨트롤러가 된다. 뷰 컨트롤러를 푸시하면 내비게이션 인터페이스에 그 뷰를 끼워넣게 된다. 스택의 최상단에 올라온 새로운 뷰 컨트롤러와 연관된 뷰를 보여줄 뿐만 아니라, 내비게이션 바와 툴바도 같이 업데이트한다.

 

 

show(_:sender:)

뷰 컨트롤러를 primary context에 띄운다.

func show(
    _ vc: UIViewController,
    sender: Any?
)
  • vc : 보여줄 뷰 컨트롤러
  • sender : 요청을 초기화하는 객체

이 메소드를 사용해 뷰 컨트롤러를 보여주는 것과 뷰 컨트롤러가 실제 스크린에 나타나는 과정을 분리할 수 있다. 이 메소드를 사용하면 뷰 컨트롤러가 내비게이션 컨트롤러에 임베딩되는지 스플릿뷰 컨트롤러에 임베딩되는지 알 필요가 없다. 둘 다 같은 메소드를 호출하기 때문이다. UISplitViewController와 UINavigationController 클래스는 이 메소드를 오버라이딩해서 디자인에 어울리게 어떻게 보일지 조정할 수 있다. 예를 들어, 내비게이션 컨트롤러는 이 메소드를 오버라이드해서 내비게이션 스택에 vc를 푸시하는 방식이다.

이 메소드를 vc를 primary context에 보이는 데에 사용하면 된다. 예를 들어, 컨테이너 뷰 컨트롤러는 이 메소드를 primary child를 대체하는 데에 사용할 수 있다. 

 

 

실험

단촐한 구성의 스토리보드를 만든다. 루트 뷰에서 서브 뷰들 간 세그로 연결된 건 없다.

루트 뷰의 요소들을 뷰 컨트롤러 파일에 IBOutlet 변수와 IBAction 함수로 다 연결한다. 

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var receivePushLabel: UILabel!
    @IBOutlet weak var sendPushTextField: UITextField!
    @IBOutlet weak var receiveShowLabel: UILabel!
    @IBOutlet weak var sendShowTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        receivePushLabel.text = ""
        receiveShowLabel.text = ""
    }
}

오른쪽 상단의 뷰 컨트롤러는 push 방식, 하단의 뷰 컨트롤러는 show 방식을 사용할 예정이다.

하지만 두 뷰 컨트롤러의 코드는 거의 똑같다. 클래스 이름만 다르다는 걸 빼면 아래와 같다. viewWillAppear에서 루트 뷰에서 전달받은 메시지가 있으면 표시하기로 한다.

여기서는 delegate 방식이 아니라 클로저 방식으로 루트 뷰에 데이터를 넘겨주도록 하겠다. String? 을 받아 반환형은 없는 함수 타입을 sendMessage로 이름짓고, Send 버튼을 눌렀을 때 호출될 tapSendButton 메소드 내에서 해당 함수를 호출한다. 

showExViewController도 마찬가지로 일단 popViewController로 똑같이 써준다.

import UIKit

class pushExViewController: UIViewController {

    var message: String?
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var sendRootTextField: UITextField!
    
    var sendMessage: ((String?) -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        if let msg = message {
            self.messageLabel.text = msg
        }
    }
    
    @IBAction func tapSendButton(_ sender: UIButton) {
        self.sendMessage?(self.sendRootTextField.text ?? "")
        self.navigationController?.popViewController(animated: true)
    }

}

 

이제 ViewController 파일로 돌아가 pushViewController 방식으로 서브 뷰를 띄워본다.

instantiateViewController로 서브 뷰를 찾고, 텍스트 필드에 적힌 문자열을 서브 뷰의 message 변수에 대입한다. 그리고 서브 뷰가 전달한 데이터를 각각의 변수로 받는다.

먼저 pushViewController 방식이다. sendMessage를 실제로 구현하는 역할을 루트 뷰에서 하면 된다. 

    @IBAction func tapPushButton(_ sender: UIButton) {
        guard let pushVC = self.storyboard?.instantiateViewController(withIdentifier: "pushExViewController") as? pushExViewController else { return }
        pushVC.message = sendPushTextField.text
//        자세히 써주려면 이렇게도 가능
//        pushVC.sendMessage = { message in
//            self.receivePushLabel.text = message
//        }
        pushVC.sendMessage = { self.receivePushLabel.text = $0 }
        self.navigationController?.pushViewController(pushVC, animated: true)
    }

다음은 show 방식이다. 마지막 문장을 제외하고는 위의 tapPushButton IBAction 함수와 완전히 똑같다. self인 ViewController가 show 요청을 초기화하므로 sender에는 self를 넣어준다.

    @IBAction func tapShowButton(_ sender: UIButton) {
        guard let showVC = self.storyboard?.instantiateViewController(withIdentifier: "showExViewController") as? showExViewController else { return }
        showVC.message = sendShowTextField.text
        showVC.sendMessage = { self.receiveShowLabel.text = $0 }
        self.show(showVC, sender: self)
    }

 

이제 빌드해보자. 먼저 pushViewController부터 해 본다.

잘 동작하는 것을 볼 수 있다. 서브 뷰로 넘어갈 때, 당연하게도 내비게이션 스택에 서브 뷰가 최상단으로 올라오면서 루트 뷰로 돌아갈 수 있는 Back 버튼이 생겼다.

그렇다면 show도 그럴까? show도 마저 테스트한다.

 

show 방식 역시 마찬가지로 내비게이션 스택에 서브 뷰가 올라오면서 내비게이션 바에 Back 버튼이 생긴 것을 볼 수 있다.

만약 show 뷰 컨트롤러에서 dismiss를 쓰면 어떨까?

    @IBAction func tapSendButton(_ sender: UIButton) {
        self.sendMessage?(self.sendRootTextField.text ?? "")
        self.dismiss(animated: true)
    }

빌드는 성공적으로 잘 되지만, Send 버튼을 누른다고 루트 뷰로 돌아가지는 않는다. dismiss는 모달로 띄워진 뷰를 내리는 메소드이기 때문에 이렇게 내비게이션 스택에 올라온 뷰를 내릴 수는 없다. 때문에 iOS로 개발할 때 show로 서브 뷰를 띄웠다면 popViewController를 사용해야 한다.

 

실험 결과 : pushViewController와 show는 루트 뷰에서 호출하는 메소드는 다르지만, 서브 뷰에서 루트 뷰로 돌아갈 때 popViewController를 한다는 점에서는 똑같다.

 

이 예제의 전체 프로젝트 코드는 아래 링크에 있다.

 

GitHub - dev-dain/ios-blog: 블로그에 올릴 iOS 포스트 예제 프로젝트를 올리는 저장소입니다.

블로그에 올릴 iOS 포스트 예제 프로젝트를 올리는 저장소입니다. Contribute to dev-dain/ios-blog development by creating an account on GitHub.

github.com

 

 

차이점

그럼 이렇게 언뜻 보면 차이가 없는 듯한 pushViewController와 show 메소드는 무슨 차이가 있을까?

조금 더 글을 찾아보다가 이 글을 발견했다. 영어로 써 있어서 중요한 부분만 옮겨오자면...

pushViewController는 아주 범용적으로 많이 쓰이고 있지만, 아이패드 프로처럼 기기 사이즈가 크면 content view controller를 보여주는 데에 UISplitViewController를 사용하거나 큰 디스플레이에 알맞게 쓸 수 있는 다른 형태의 컨테이너 뷰를 사용하게 될 수도 있다.

그렇기 때문에 뷰 컨트롤러 간 이동을 하는 데에 현재 내비게이션 컨트롤러에 pushViewController를 직접 호출하는 것보다는 show 메소드를 쓰는 것이 좋은데, 그 이유는 show 메소드를 사용하면 시스템이 다음 뷰 컨트롤러로 뭘 보여줄지 정확히 결정하게 하고, 현재 뷰 컨트롤러와 내비게이션 패러다임을 완전히 분리할 수 있기 때문이다.

UINavigationController를 사용할 경우, pushViewController와 show 메소드는 완전히 똑같이 동작한다. 다만 show 메소드를 사용하면 좀 더 유연하고 미래에 대응하기 더 좋다. 여기에 덧붙여서, 뷰 컨트롤러를 보여줄 방법을 커스터마이징하고 싶었다면 좋은 소식이다. show 메소드를 오버라이딩해 커스터마이징할 수 있다. 

-> 정리하자면, iOS는 UISplitViewController를 사용하지 않고, 내비게이션 컨트롤러를 주로 사용하기 때문에 pushViewController와 show 메소드의 동작이 똑같다.

하지만 iPadOS나 큰 기기를 위해 코드를 짠다면 이 둘의 차이를 숙지하는 것이 좋을 것이다.

 

 

 

728x90
반응형
Comments