앱 프로그래밍에서 화면을 띄우거나 전환하는 것은 중요한 과정입니다. iOS에서 화면을 전환하는 것은 여러 방법이 있지만 오늘은 Segue를 이용한 방법을 정리해보았습니다. 그리고 Segue를 코드로 다루는 방법도 있는데, 이것도 함께 다루겠습니다.

이 글은 다음 문서와 공식 문서들을 참고하여 작성되었습니다.
View Controller Programming Guide for iOS - Presentations and Transitions

  • Segue란?

     Segue란 스토리보드에서 앱의 인터페이스 흐름을 나타내는 객체입니다. 쉽게 이야기 하면 스토리보드에서 뷰 컨트롤러 간의 이동을 나타내는 화살표를 추상화시킨 것입니다.

    Segue Object

    segue는 프로그래머가 직접 발동시키지 않아도, 런타임에 해당 뷰컨트롤러에 연관된 segue객체를 불러오고 segue를 발동시키는 요소와 연결을 시킵니다. 사용자가 이 요소와 상호작용하면, UIKit은 적절한 뷰컨트롤러를 불러오고, 앱에 segue가 실행될 것임을 알리는 알림을 보낸 후에 뷰컨트롤러의 전환을 수행합니다. 앱은 이 알림을 받아서 새로운 뷰 컨트롤러에 데이터를 넘기거나, segue 자체의 실행을 막는 등의 작업을 할 수 있습니다.

    스토리보드는 xml이므로 segue 역시 코드로 나타나는데, 다음과 같은 형태입니다.

      <segue destination=(목표 뷰컨트롤러 identifier) kind=(전환 형태) id=(segue 자체의 identifier)/> <!--화면 전환 segue-->
      <segue destination=(목표 뷰컨트롤러 identifier) kind="relationship" relationship="rootViewController" id=(segue 자체의 identifier)/> <!--컨테이너 뷰컨트롤러와 루트뷰컨트롤러 간의 관계-->
    

    전환 형태는 show,showDetail,presentation,popoverPresentation,custom 그리고 unwind가 있습니다.

  • Segue 만들기

    같은 스토리보드에 있는 두 개의 뷰컨트롤러를 고릅니다. 이 글에서는 그 중 segue의 시작점이 되는 뷰컨트롤러를 ‘출발 뷰컨트롤러’, segue의 결과로 띄워지는 뷰컨트롤러를 ‘도착 뷰컨트롤러’라고 하겠습니다. 출발 뷰컨트롤러에서 적절한 요소를 골라 control + 클릭을 해주고 전환하고자 하는 뷰 컨트롤러로 드래그해주는 것만으로 쉽게 segue를 정의해 줄 수 있습니다. 이 때 뷰컨트롤러 자체도 요소롤 지정할 수 있지만, 이 경우에는 스토리보드에서 해당 segue를 사용할 수 있는 법이 없어 코드로만 사용할 수 있게 됩니다.

    Creating Segue

    segue의 시작점은 반드시 액션이 정의되어 있는 뷰 또는 객체여야 합니다. 여기에 해당하는 것은 control(UIControl의 subclass), barButtonItem, gestureRecognizer 등이 있습니다.

    특정 요소는 2개 이상의 Segue를 가질 수도 있습니다. 예를 들어 tableViewCell은 accessory 뷰와 나머지 부분이 각기 다른 segue를 가질 수 있습니다. 이 경우는 한 요소가 두개의 컨트롤 요소를 가지고 있기 때문에 가능한 것입니다.

    드래그 한 뒤 마우스 버튼을 놓으면, 인터페이스 빌더는 두 뷰컨트롤러 간의 transition을 설정할 수 있도록 프롬프트를 띄워줍니다.

    Select Segue

    여기서 segue를 선택할 때는 반드시 적응형 segue(위 이미지에서 Selection Segue)를 골라주어야 합니다. 적응형 segue는 현재 상황에 따라 적절한 행동을 선택할 수 있기 때문입니다. 비적응형 segue는 적응형 segue가 지원되지 않는 iOS 7 이하에서만 사용하도록 합니다.

    segue 타입 행동
    show(push) 해당하는 메소드는 show(:sender:)이다. 대부분의 경우는 present modally로 동작하지만, 일부 뷰컨트롤러는 이 메소드를 오버라이드 해서 다른 동작을 취하기도 한다. (ex. navigationController는 새로운 뷰컨트롤러를 스택에 push한다.) UIKit은 targetViewController(forAction:sender:) 메소드를 이용해 다음 뷰컨트롤러를 띄워줄 뷰컨트롤러를 찾게 된다. 해당 메소드는 뷰컨트롤러 계층을 올라가면서 show(:sender:)를 오버라이드한 뷰컨트롤러를 찾게 되는데(해당되는 컨트롤러는 navigation,splitView 등이 있다) 찾을 경우 반환된 뷰컨트롤러의 show(:sender)를 이용하고, 찾지 못할 경우 nil을 반환하여 present(:animated:completion:)을 사용하게 한다.
    showDetail(Replace) 해당하는 메소드는 showDetailViewController(_:sender:)이다. UISplitViewController에서만 사용하는 것으로 SplitView의 두번째 자식 뷰컨트롤러(detail Controller)를 바꿔준다. 다른 뷰컨트롤러를 대상으로 하는 경우에는 present(:animated:completion:)로 동작한다.
    Present Modally 정해진(혹은 사용자가 설정한) presentationStyle과 transitionStyle에 따라 Modal하게 뷰 컨트롤러를 띄운다. presentationStyle에 따라 현재 뷰컨트롤러가 적절하지 않을경우에는 뷰컨트롤러 계층을 따라가면서 적절한 뷰컨트롤러를 발견했을 때 실제로 presentation이 실행된다. (ex - presentation style이 fullscreen인 경우, 화면 전체를 덮는 뷰를 가진 뷰컨트롤러만이 present를 실행할 수 있다.)
    Present as Popover 가로 길이가 regular 사이즈인 경우에는 popover로 띄우지만, 가로 길이가 compact 사이즈인 경우는 전체화면 Modal과 같아진다


    show(:sender:)를 오버라이드 할 때 절대 super.show(:sender:)를 호출하지 마세요!
    show 내부에서 targetViewController(forAction:sender:)을 호출하고, 이 메소드는 show가 override가 되어 있는 뷰컨트롤러에 대해서 show를 호출합니다. 이 과정에서 루프가 생기게 됩니다.

    segue가 만들어지면, 해당 객체에 식별자(Identifier)를 지정해주면, segue발동시 어떤 segue가 발동이 되었는지를 확인할 수 있습니다. 이는 특히 한 뷰컨트롤러에서 여러 개의 segue를 다룰 때 유용합니다.

  • 런타임에서 segue 컨트롤하기

    Segue Step

    1. Segue가 발동된 경우, UIKit은 뷰컨트롤러에 해당 segue를 수행할 것인지 여부를 묻습니다. 이를 수행하는 메소드는 shouldPerformSegue(withIdentifier:sender:)으로, true를 반환하면 segue가 수행되고 false가 반환되면 segue는 수행되지 않습니다. 다만 segue만 수행되지 않을 뿐 이벤트 자체를 무효화 시키지 않기 때문에 해당 요소에 다른 액션이 연결되어 있었다면 그대로 실행이 됩니다.
      만약 화면전환을 하기 전에 조건을 검사해서 전환 여부를 결정해야 한다면 해당 메소드를 오버라이드해서 처리하면 됩니다.

    2. 스토리보드를 기반으로 UIStoryboardSegue 객체와 전환이 일어날 뷰컨트롤러 객체가 생성됩니다.

    3. Segue가 실행되기 전 prepare(for:sender:) 함수를 호출해줍니다. 이 메소드는 화면 전환이 일어나기 전에 새로 띄워질 뷰컨트롤러에 필요한 데이터를 넘기는 과정을 수행하기 위해 존재합니다. 기본 구현은 아무 것도 하지 않는 것으로 되어 있기 때문에 데이터를 넘기기 위해서는 메소드를 오버라이드해야 합니다. UIStoryboardSegue 객체가 새로 띄워질 뷰컨트롤러의 참조를 가지고 있기 때문에 해당 객체를 이용해 새로 띄워질 뷰컨트롤러에 데이터를 넘깁니다.

    4. 실제로 segue가 실행되어 화면 전환이 이루어 집니다.

  • Unwind Segue 살펴보기
    이전까지 살펴본 segue는 새로운 화면을 띄우는 것이 주요한 목적이였습니다. 하지만 이와 반대로 화면을 내려서(dismiss) 특정 화면으로 돌아가기 위해서도 segue를 이용할 수 있는데, 이 경우는 ‘unwind segue’라고 합니다. unwind segue가 발동되면 UIKit은 뷰컨트롤러 계층을 뒤지면서 이 unwind segue를 처리할 수 있는 뷰컨트롤러를 찾는데, 찾아낸 경우에 이 unwind segue의 출발 뷰컨트롤러를 포함해서 도착 뷰컨트롤러 바로 직전까지의 모든 뷰 컨트롤러를 자동으로 내려줍니다.

    unwind segue를 만드는 방법은 다음과 같습니다.

    1. unwind segue의 출발 뷰컨트롤러(사라질 뷰컨트롤러), 도착 뷰컨트롤러(나타날 뷰컨트롤러)를 결정합니다.

    2. 도착 뷰컨트롤러 측에 다음과 같은 액션 메소드를 작성합니다.

       @IBAction func methodName(unwindSegue: UIStoryboardSegue)
      
    3. 출발 뷰컨트롤러의 요소 중 하나를 골라 ctrl + 드래그로 해당 뷰 컨트롤러의 exit과 연결해줍니다.

      unwindSegue

    4. 연결할 때 뜨는 프롬프트에서 2번에서 작성한 메소드를 선택합니다.

    unwind segue를 정의하기 위해서는 unwind segue를 받을 수 있는 액션 메소드를 반드시 정의해야 됩니다. 이 액션 메소드를 정의해야 스토리보드에서 unwind segue를 받을 수 있다는 것을 알 수 있습니다.

    unwind segue 역시 segue이기 때문에 shouldPerformSegue(withIdentifier:sender:)과 prepare(for:sender:)를 거칩니다. 해당 메소드를 이용해 정보를 넘기는 것도 가능하고, 도착 뷰컨트롤러에 정의된 액션메소드에서도 내려가기 직전인 뷰컨트롤러를 참조할 수 있으므로 액션 메소드에서 데이터를 처리할 수도 있습니다. 하지만 일관적인 코딩 스타일을 위해서라도 prepare(for:sender:)에서 처리해 주는 것을 권장합니다.

    unwind의 실제 동작은 상당히 많은 과정을 거치지만, 여기서는 다루지 않고 넘어가겠습니다. 더 알아보고 싶으신 분은 Programming iOS 12: Dive Deep into Views, View Controllers, and Frameworks라는 책을 참조하시면 좋습니다.

  • 코드로 segue 초기화하기

    특정 상황에서는 어떠한 segue를 발동시켜야 할 지 스토리보드 상에서 결정하지 못하는 경우가 있습니다. 예를 들어 게임같은 경우 조건에 따라 하나의 버튼이 서로 다른 화면을 띄워줘야 되는 경우 등이 있을 수 있겠죠. 이럴 경우 segue를 코드상에서 발동시킬 수 있습니다.

      //Defined in UIViewController
      func performSegue(withIdentifier identifier: String, sender: Any?)
    

    이 메소드는 identifier로 segue 객체를 스토리보드에서 찾아서 해당 segue를 발동시킵니다. sender 인자에는 실제 segue 실행시 참조할 필요가 있는 객체를 넣어주면 좋습니다.
    이 메소드를 사용할 경우 canPerformUnwindSegueAction(_:from:sender:)을 거치지 않고 segue를 무조건 실행시킬 수 있습니다.(단, prepare(for:sender:)는 거칩니다.) 위에서 설명했듯이 어떤 요소와의 상호작용을 통해 발동되는 segue를 action segue, 코드로 발동되는 segue를 manual segue라고 구분하기도 합니다.

  • 커스텀 segue 만들기
    스토리보드를 통해서 기본으로 제공되는 segue만으로 충분하지 않은 경우, 프로그래머가 직접 UIStoryboardSegue 클래스를 상속받아 커스텀 segue를 만들수도 있습니다. 하지만 이를 제대로 하기 위해서는 먼저 segue의 라이프사이클을 알아야 합니다.

    Segue의 생명 주기
    앱 내에서 Segue를 직접 만드는 일은 절대 없습니다. segue가 발동되면 UIKit이 자동으로 segue 객체를 만들어냅니다.

    1. 전환될 뷰 컨트롤러가 만들어지고 초기화됩니다.
    2. segue 객체가 만들어지고 init(identifier:source:destination:) 생성자가 호출됩니다. 인자는 각각 storyboard에서의 identifier, 출발 뷰컨트롤러,도착 뷰컨트롤러 입니다.
    3. 출발 뷰컨트롤러의 prepare(for:sender:) 메소드를 호출합니다.
    4. segue 객체의 perform 메소드가 호출되며 실제 전환이 일어납니다.
    5. segue 객체의 참조가 해제되면서 생명 주기가 끝납니다.

    커스텀 segue 객체를 만들기 위해서는 다음과 같은 과정이 필요합니다.

    1. 우선 init(identifier:source:destination:) 생성자를 오버라이드해야 합니다. 이 생성자를 오버라이드 할 때에는 반드시 super.init 을 호출해줘야 합니다.

    2. perform 메소드를 구현하고 그 안에서 전환 애니메이션을 구성합니다.

    커스텀하면서 추가적으로 들어간 프로퍼티의 경우는 스토리보드에서 설정할 수 없기 때문에, segue 실행 시 prepare(for:sender:)에서 나머지 프로퍼티를 설정해 주어야 합니다.

      func perform() {
          // 커스텀 애니메이션을 추가합니다.  
    
          self.source.present(self.destination,animated:true)
      }
    

    원래는 화면 전환을 전반적으로 다룰 계획이였지만, 정리하다보니 segue만으로도 내용이 상당하고 화면 전환에 대한 전반적인 내용이 대부분 포함되어 있어 segue에 대해서만 집중적으로 정리해보았습니다.