# 화면 이동(코드로만 연결하기)

xcode에서 화면 이동을 구현하기 위해서는 스위프트 파일을 먼저 생성해야 한다.

파일 생성시 cocoa touch class 타입의 파일로 생성하게 되면 상속받을 클래스를 선택하여 기본적인 코드 세팅을 도와준다. override func viewDidLoad()와 같은 코드들을 자동으로 생성해주는 것이다. 상속받는 클래스는 UIViewController로 지정하면 된다.

화면 이동의 흐름은 아래와 같다.

  1. 새로운 ViewController 자식클래스를 생성한다.
  2. 해당 클래스의 UI를 셋업한다. 이때, addSubView순서에 유의하여 클래스 자체에 오류가 없도록 해야한다. (중요)
  3. 화면을 전환하고자 하는 원래 코드로 돌아와 새로 생성한 ViewController 자식클래스의 인스턴스를 생성한다.
  4. 인스턴스의 modalPresentationStyle 열거형 속성을 지정하면 화면 전환 효과를 바꿔줄 수도 있다. (모달 또는 풀스크린 등)
  5. present 메서드를 통해 화면을 전환한다.
// 이동할 페이지
class CustomViewController: UIViewController {
    // UI 셋업 코드
    override func viewDidLoad(){
        // ....
    }
}
// 처음 화면 위치에서 연결한 버튼을 탭하면 화면이 이동하도록 설계
@IBAction func codeNextButtonTapped(_ sender: UIButton) {
    // 인스턴스 생성
    let customVC = CustomViewController()
    customVC.modalPresentationStyle = .fullScreen
    present(customVC, animated: true)
}

# 데이터 전달

데이터를 주고받기 위해서는 전달받는 쪽의 클래스에 저장속성을 하나 마련해야 한다. ViewController 클래스에서는 생성자를 다루는 것이 쉽지 않기 때문에 옵셔널 타입으로 선언하는 것이 일반적이다.

위와 같이 저장속성을 옵셔널 타입으로 선언해도 UILabel 인스턴스의 text속성도 String의 옵셔널 타입이므로 옵셔널 바인딩을 할 필요 없이 바로 대입 가능하다.

class FirstViewController: UIViewController {
    var someString: String?

    let myLabel: UILabel = {
        let label = UILabel()

        // 전달될 someString의 값이 label로 세팅된다
        label.text = someString

        return label
    }()

    override func viewDidLoad(){

    }
}

데이터 공간을 마련했다면 인스턴스에 대한 저장속성에 접근하여 데이터를 전달하면 된다.

@IBAction func codeNextButtonTapped(_ sender: UIButton) {
    // 인스턴스 생성
    let firstVC = FirstViewController()
    firstVC.someString = "데이터 전달~~"
    firstVC.modalPresentationStyle = .fullScreen
    present(firstVC, animated: true)
}

뒤로가기

스위프트에서 뒤로가기는 dismiss(animated:) 메서드를 사용하면 된다.

// 버튼에 대해 addTarget 메서드를 호출, backButtonTapped라는 이름으로 셀렉터 메서드 연결
@objc func backButtonTapped(){
    dismiss(animated: true)
}

# 화면 이동(스토리보드)

Main스토리보드로 이동한 뒤 라이브러리에서 ViewController요소를 끌어다가 배치한다. 이후 좌측 파일 리스트에 코드로 설정했던 부분과 동일하게 Cocoa touch class타입으로 파일을 하나 생성한다.

Main 스토리보드에 올려놓은 ViewController요소를 선택한 뒤 우측 메뉴에서 (Inspector 메뉴) 왼쪽으로부터 4번째 메뉴를 클릭한다. (Identity Inspector)

해당 메뉴의 Custom Class 섹션의 Class에 위에서 연결하고자 하는 ViewController 클래스 파일명을 입력한 뒤 엔터를 치면 연결된다.

이뿐 아니라 present 함수 호출을 위해 ViewController 인스턴스 생성을 하는 코드가 있었는데, 코드로만 작성하던 방식과는 다른 방법을 사용해야 한다. 기존 코드로만 작성했을 때는 UI 오토 레이아웃에 대한 인스턴스와 스토리보드 자체에 대한 인스턴스가 함께 올라가서 동작했다면 스토리보드를 사용하는 방식은 둘이 분리되어 메모리상에서 관리가 되기 때문이다.

따라서 필요한 부분은 storyboard라는 변수에 접근하여 새로 생성한 뷰 컨트롤러를 연결한 인스턴스를 생성하는 부분이다.

@IBAction func storyboardWithCodeButtonTapped(_ sender: UIButton) {
    let secondVC = storyboard?.instantiateViewController(withIdentifier: "myVC") as!  MyViewController

    present(secondVC, animated: true)

}

storyboard?.instantiateViewController 메서드를 사용하며, 파라미터로 withIdentifier를 전달한다. 이때 identifier는 스토리보드와 ViewController 클래스를 연결할때 사용했던 Identity Inspector 메뉴를 사용한다.

연결하고자 하는 스토리보드를 누르고 Identity 섹션의 Storyboard ID를 원하는 문자열로 정의한다. 이후 storyboard.instantiateViewController 메서드를 호출할때 해당 아이디값을 전달하면 되며, 이렇게 생성한 인스턴스는 뷰컨트롤러 클래스로 타입 캐스팅을 해야한다.

스토리보드 기반의 데이터 전달은 코드 기반의 데이터 전달과 동일하다.

  1. 전달받는 뷰컨트롤러 클래스에 저장속성을 마련한다.
  2. 전달하는 뷰컨트롤러에서 전달할 클래스의 인스턴스를 생성하고 저장속성에 접근하여 값을 전달한다.

스토리보드 기반의 앱에서 데이터를 전달할때 반드시 유의해야할 점은 뷰컨트롤러 인스턴스가 생성되는 시점과 스토리보드가 생성되는 시점이 다르다는 것이다. 따라서 뷰 컨트롤러에서 @IBOutlet을 통해 연결하는 UI요소들의 속성에 직접 접근하는 방식으로 데이터를 전달하면 해당 시점에는 UI요소가 nil로 인식되어 에러가 발생하게 된다.

let myVC = MyViewController()
myVC.someString = "전달할 데이터" // OK
myVC.mainLabel.text = "오류" // Fatal Error!!

# 스토리보드로 화면 이동 구현 (간접 세그웨이)

화면 이동을 관리하는 객체로 세그웨이라는 타입의 인스턴스가 있다. 스토리보드 상에 올려둔 뷰컨트롤러 화면들을 보면 상단에 작은 세 가지 점이 있다. 이 중 가장 왼쪽에 View Controller 버튼을 컨트롤을 누른 상태에서 드래그하면 세그웨이를 다른 화면으로 연결할 수 있다.

다른 뷰 컨트롤러에 올려두고 Present될 타입을 선택하면 세그웨이가 연결된다.

세그웨이가 연결된 후 xcode상에 생성된 세그웨이 선을 클릭하고 우측 인스펙터 메뉴 중 Attributes Inspector를 선택하면 Storyboard Segue 섹션이 존재한다. Identifier에 세그웨이 id값을 지정해야한다.

세그웨이에 대한 아이디값까지 지정했으면 세그웨이를 동작시키면 된다.

@IBAction func storyboardWithSegueButtonTapped(_ sender: UIButton){
    performSegue(withIdentifier: "toThirdVC", sender: self)
}

performSegue 함수를 통해 sender는 화면을 이동하는 출발지점이라고 생각했을때 self라고 지정하게 되는 것이 일반적이고 withIdentifier 파라미터에 작동시킬 세그웨이 id값을 전달하면 된다.

여기까지 하면 화면 이동은 구현된다.

데이터를 전달하기 위해서는 prepare(for:segue:) 함수를 오버라이딩(재정의) 해야한다.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if(segue.identifier == "toThirdVC"){
        guard let thirdVC = segue.destination as? ThirdViewController else {return}

        thirdVC.someString = "낄낄"
    }
}

prepare 함수는 performSegue함수 호출과 함께 자동으로 호출되는 함수이다. 세그웨이를 동작시키기 이전에 전달할 데이터를 확인하고 종착지와 연결하는 역할을 한다.

  1. segue라는 세그웨이 객체의 identifier를 확인하는 로직을 추가한다.
  2. segue.destination을 통해 세그웨이 종착지를 얻는다. ViewController 타입은 맞지만 이를 상속한 자식 ViewController 타입은 아니기 때문에 타입 다운캐스팅을 한다.
  3. 얻어낸 뷰 컨트롤러 인스턴스 속성에 접근하여 데이터를 전달한다.

# 스토리보드로 화면 이동 구현 (직접 세그웨이)

뷰 컨트롤러에서 감지된 이벤트를 통해 세그웨이를 작동시키는 방식은 간접 세그웨이고, UI요소를 직접 세그웨이로 다른 화면에 연결하는 것을 직접 세그웨이라고 한다.

간접 세그웨이에서는 뷰컨트롤러 자체를 다른 화면으로 연결했다면 직접 세그웨이에서는 UI버튼을 컨트롤 클릭하여 다른 뷰 컨트롤러로 연결하면 된다.

나머지는 마찬가지로 세그웨이 id값을 지정하고 prepare메서드를 오버라이딩 하여 세그웨이 아이디에 따라 분기하여 데이터를 전달하면 된다.

간접 세그웨이와 다른 점은 performSegue 함수를 호출할 필요가 없다는 것이다. 연결한 UI요소 이벤트가 직접적으로 연결되어 있기 때문에 클릭과 같은 이벤트 트리거와 동시에 세그웨이가 동작한다.

직접 세그웨이에서는 performSegue가 아닌 shouldPerformSegue(withIdentifier:sender:) -> Bool 함수를 통해 조건을 설정할 수 있다.

저장속성의 값을 조건으로 설정하던지.. 조건은 커스텀하여 마음대로 지정하면 되고 델리게이트 프로토콜의 함수를 구현할때 should~ 형태의 함수를 구현하는 것과 동일하게 false를 리턴하면 세그웨이가 동작하지 않고 true를 리턴하면 세그웨이가 동작한다.

간접세그웨이에서 shouldPerformSegue가 필요하지 않은 이유는 UI이벤트 트리거 함수 내에서 직접 shouldPerformSegue처럼 내부 로직을 구성할 수 있기 때문이다. 저장속성에 대해 조건을 커스텀 한다던지..

# Reference

  1. 앨런 Swift 문법 마스터스쿨 (opens new window)
  2. Segue 파헤치기 (opens new window)