Ch.15 화면전환

  • iOS에서 화면을 전환하는 방법에는 크게 두가지가 있음
  • 하나는 소스 코드를 통해 전환하는 방식이고 (동적으로 화면전환)
  • 또 다른 하나는 스토리보드가 제공하는 기능을 이용하여 전환하는 방식(정적으로 화면전환)
  • 동적인 방식은 특정 상황에 대응할 수 있지만 조금 복잡하고 어려움
  • 정적인 방식은 일괄적으로 적용되는 것이라 특정 상황에 대응하기 어렵지만 그만큼 구현하기 쉽다는 장점이 있음

15.1 iOS에서의 화면 전환 개념

  • iOS에서 화면 전환 방식은 분류기준에 따라 크게 4가지로 나누어 볼 수 있음
1. 뷰 컨트롤러의 뷰 위에 다른 뷰를 가져와 바꿔치기 하기
2. 뷰 컨트롤러에서 다른 뷰 컨트롤러를 호출하여 화면 전환하기
3. 내비게이션 컨트롤러를 사용하여 화면 전환하기
4. 화면 전환용 객체 세그웨이를 사용하여 화면 전환하기
  • 위 1은 특수한 상황에서 제한적으로 사용하는 방법임. 일부 뷰 컨트롤러들은 콘텐츠를 직접 배치하여 화면을 보여주는 역할 대신 다른 뷰 컨트롤러를 구조화 하는 역할을 하는데 이때 화면을 구조화하는 방식이 이것임
  • 이같은 뷰 컨트롤러를 컨테이너 뷰 컨트롤러라고 함
  • 1 을 제외한 나머지 대부분의 화면 전환은 모두 뷰 컨트롤러를 호출하는 방식으로 이루어짐. 전환할 화면을 담당하는 뷰 컨트롤러의 인스턴스를 생성하고, 이를 불러들여서 기존의 화면 위에 덮으면 화면이 전환된다는 뜻. 이에 따라 현재의 화면이 다른 화면으로 완전히 교체되는 것이 아니라 현재 화면이 있는 상태에서 그 위에 새로운 화면이 얹어지는 모양새가 됨
  • 이같은 특성 때문에 기존 화면과 새로운 화면 사이에는 서로 참조 관계가 성립함. 화면이 전환되는 방식에 따라 이들은 서로 직접 참조할수 있거나 또는 화면전환을 관리하는 전담 객체를 통해 간접적으로 참조하기도 함

iOS에서 화면 전환은 다음 두가지 특성을 가짐

  1. 다음 화면으로 이동하는 방법과 이전 화면으로 되돌아가는 방법이 다름
  2. 화면 전환방식에 따라 이전 화면으로 되돌아 가는 방법이 다름

15.2 화면 전환 기법1 : 뷰를 이용한 화면 전환

  • 하나의 뷰 컨트롤러 안에 두 개의 루트뷰를 준비한 다음 상태에 따라 뷰를 적절히 교체해 주는 것
  • 이 방식은 하나의 뷰 컨트롤러가 두개의 루트뷰를 관리해야 하므로 그리 좋은 방법은 아님
  • iOS에서는 하나의 뷰 컨트롤러 아래에 하나의 루트뷰를 관리하는 MVC패턴을 기본으로 하는데 위에방식은 이같은 구조를 완전히 거스르는 방식
  • 또 다른 방식은 다른 뷰 컨트롤러에 올려진 루트 뷰를 가져와 표시하는 방식
  • 이 방법 역시 뷰 컨트롤러 내부에 있어야 할 뷰가 다른 뷰 컨트롤러로 옮겨가 버리므로 뷰를 제어할 책임을 지는 컨트롤러가 모호해진다는 단점이 있음

15.3 화면 전환 기법2 : 뷰 컨트롤러 직접 호출에 의한 화면 전환

  • 현재의 뷰 컨트롤러에서 이동할 대상 뷰 컨트롤러를 직접 호출해서 화면을 표시하는 방식

present(<새로운 뷰 컨트롤러 인스턴스>, animated:<애니메이션 여부>)
present(_:animated:completion:)
completion -> 화면 전환이 완전히 끝난후에 호출해주는 역할

  • 프레젠테이션 방식으로 화면을 전환했을 때 iOS시스템은 화면에 표시된 뷰 컨트롤러(VC2)와 표시하고 있는 뷰 컨트롤러(VC1) 사이에 참조할 수 있는 포인터를 생성하여 서로 참조할 수 있게 해줌
  • VC1은 presentedViewController 속성에다 자신이 표시하고 있는 새로운 뷰 컨트롤러의 포인터를 저장하고, 새로운 뷰 컨트롤러인 VC2에는 presentingViewController 속성에다 자신을 표시한 뷰 컨트롤러의 포인터를 저장
  • VC1 에서는 presentViewController속성을 이용하여 VC2를 참조 할 수 있고 VC2에서는 presentingViewController속성을 이용하여 VC1을 참조할 수 있다는 뜻
  • 이전 화면으로 복귀할 때는 다음과 같은 복귀 메소드를 사용함

dismiss(animated:)

  • 이전 화면으로 돌아가는 기능이기 때문에 뷰 컨트롤러의 인스턴스를 인자값으로 받지는 않음. 화면 복귀시 애니메이션을 적용할지 말지를 결정하는 값만 전달해주면 됨

dismiss(animated:completion:)
completion -> 화면 복귀가 완전히 처리되었을 때 실행할 구문을 인자값으로 입력받는 매개변수

  • 두 메소드는 기존화면이 새로운 화면 위로 올라오는 것이 아니라 기존 화면을 덮고 있던 새 화면을 걷어내는 것. 걷어낸 화면의 뷰 컨트롤러 객체는 운영체제에 의해 곧 메모리에서 해제됨
  • iOS에서 화면이 사라지게 처리하는 것은 사라질 화면의 뷰 컨트롤러 자신이 아니라 자신을 띄우고 있는 이전 뷰 컨트롤러임
  • 즉 VC1 이 VC2 를 호출하여 화면에 표시해주었다면 반대로 VC2를 화면에서 사라지게 하는것도 VC1

self.presentingViewController?.dismiss(animated:)

15.3.1 화면전환 실습

//
//  ViewController.swift
//  Scene-Trans01
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    @IBAction func moveNext(_ sender: Any) {
        
        // 여러개의 스토리 보드가 있을경우 이렇게 사용..Main은 스토리보드 파일명
        
        // let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        // let uvc = storyboard.instantiateViewController(withIdentifier: "SecondVC")
        
        
        // let uvc = storyboard!.instantiateViewController(withIdentifier: "SecondVC")
        // self.storyboard 값은 옵셔널 타입, 경우에 따라 nil값이 될수도 있음
        // 이값을 nil검사 없이 바로 ! 연산자를 사용하여 강제 해제하였으므로 만약 self.storyboard 값이 nil이라면 오류가 발생. 이를 옵셔널 체인과 옵셔널 바인딩으로 보강하면 다음과 같음
        
        /* if let uvc = storyboard?.instantiateViewController(withIdentifier: "SecondVC"){
                uvc.modalTransitionStyle = UIModalTransitionStyle.coverVertical
        
                self.present(uvc, animated: true)
            }
        */
        
        //뷰 컨트롤러 인스턴스는 moveNext메소드 전체 실행에서 비어있어서는 안되는 필수조건이기 때문에 guard 조건문으로 필터링
        //안전한 코드를 위해 self.storyboard를 옵셔널 체인으로 처리하여 instantiateViewController(withIdentifier:)메소드를 호출하여 뷰 컨트롤러의 인스턴스를 받아온 다음 옵셔널 타입을 해제하기 위해 상수 uvc에 옵셔널 바인딩, 만약 바인딩에 실패하면 메소드의 실행은 중지됨.
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") else {
            return
        }
        
        // 화면 전환할 때의 애니메이션 타입
        
        uvc.modalTransitionStyle = UIModalTransitionStyle.coverVertical
        
        // 인자값으로 뷰컨트롤러 인스턴스를 넣고 프레젠트 메소드 호출
        
        self.present(uvc, animated: true)
        
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
//
//  SecondViewController.swift
//  Scene-Trans01
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class SecondViewController: UIViewController {

    //self.dismiss가 아님!!
	  //vc2(secondViewController를 사라지게 하는건 vc1 임

    @IBAction func dismiss(_ sender: Any) {
        self.presentingViewController?.dismiss(animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

15.4 네비게이션 컨트롤러를 이용한 화면 전환

  • 네비게이션 컨트롤러는 뷰 컨트롤러의 특별한 종류로 계층적인 성격을 띠는 콘텐츠 구조를 관리하기 위한 컨트롤러(내비게이션 바가 내장되어 있음)
  • 이 컨트롤러가 제어하는 모든 뷰 컨트롤러에 내비게이션 바를 생성하는 특징이 있음
  • 루트뷰 컨트롤러는 내비게이션 컨트롤러에 직접 연결된 컨트롤러이므로 화면 UI상단에 내비게이션 바가 표시됨
  • 내비게이션 컨트롤러는 화면에 현재 표시되고 있는 뷰 컨트롤러들을 내비게이션 스택을 이용하여 관리함
  • 내비게이션 컨트롤러 최상위 뷰컨트롤러(마지막컨트롤러), 최하위 컨트롤러(루트뷰 컨트롤러)
  • 최상위 뷰 컨트롤러에 추가할 때는 pushViewController(animated:) -> 새로운화면표시
  • 최상위 뷰 컨트롤러에서 제거할 때는 popViewController(animated:) -> 이전화면으로 돌아올때
//
//  ViewController.swift
//  Scene-Trans02
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func moveByNavi(_ sender: Any) {
        
        // 
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVc") else {
            return
        }
        self.navigationController?.pushViewController(uvc, animated: true)
    }
    @IBAction func movePresent(_ sender: Any) {
        guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVc") else {
            return
        }
        self.present(uvc, animated: true)
    }

}
//
//  SecondViewController.swift
//  Scene-Trans02
//
//  Created by 이재성 on 2017. 6. 13..
//  Copyright © 2017년 jaeseong. All rights reserved.
//

import UIKit

class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func back(_ sender: Any) {
        self.presentingViewController?.dismiss(animated: true)
        
    }
    @IBAction func back2(_ sender: Any) {
        self.navigationController?.popViewController(animated: true)
    }
}

 

15.5 세그웨이를 이용한 화면 전환

  • 세그웨이를 이용하면 뷰 컨트롤러에 대한 정보가 없어도 됨.
  • 또한 뷰 컨트롤러의 객체를 생성할 필요도 없음. 세그웨이가 스토리보드상의 연결정보를 이용하여 대상 뷰 컨트롤러의 인스턴스를 자동으로 만들어줌
  • 출발점이 뷰 컨트롤러 자체인 경우를 메뉴얼세그, 컨트롤이 출발점인 경우를 액션세그 또는 트리거세그라고 나누어 부르기도 함.
  • 메뉴얼세그를 실행하려면 UIKit프레임워크에 정의된 performSegue(withIdentifier:sender:)메소드를 사용

15.5.1 액션 세그웨이

  • 액션세그는 트리거와 세그웨이가 직접 연결된 것을 의미. 터치 또는 클릭 이벤트를 발생시켜 세그웨이를 실행할 수 있는 요소를 말함
  • 액션세그는 화면 전환을 위해 프로그래밍 코드가 필요없고, 스토리보드에 구현된 객체를 트리거로 지정만 하면 되므로 전체적인 구성이 굉장히 단순해짐
  • PresentModally 는 화면전환 메소드 중 present(_:animated:)메소드를 이용한 화면 전환과 같은 기능을 함
  • 즉, 뷰 컨트롤러 자신이 새로운 화면을 불러들이도록 처리하는것
  • 내비게이션 컨트롤러가 추가되어 있는 상태에서 Show 타입의 세그웨이를 생성하면 내비게이션 컨트롤러를 통한 화면 이동이 발생. 즉, 모든 화면 이동의 결과는 내비게이션 컨트롤러의 통제하에 있게됨. 따라서 모든 뷰 컨트롤러에는 내비게이션 바가 추가됨
  • 내비게이션 컨트롤러가 없을 때는 세그웨이를 Show타입으로 생성하였더라도 PresentModally방식으로 실행됨
  • 뷰컨트롤러에 임베디드 인 하여 네비게이션 컨트롤러 추가한후에 새로운 뷰 컨트롤러를 추가하고 세그웨이로 연결한 경우 내비게이션 아이템을 추가해야 타이틀 입력 또는 바버튼을 추가 할 수 있음.

15.5.2 메뉴얼 세그

  • 메뉴얼세그는 해당 이벤트만 발생하면 자동적으로 실행되는 액션세그와 달리 뷰 컨트롤러와 뷰 컨트롤러 사이에 연결되는 수동 실행 세그웨이임.
  • 액션세그는 트리거의 터치에 의해 실행되므로 별도의 처리코드가 전혀 필요없지만, 메뉴얼세그는 트리거 없이 수동으로 실행해야 하므로 소스 코드에서 세그웨이를 실행할 메소드를 호출해야함

performSegue(withIdentifier :<세그웨이 식별자>, sender :<세그웨이 실행 객체>)

  • 두개의 인자값은 세그웨이가 여러 개일 경우를 대비한 세그웨이 식별자와 세그웨이를 실행하는 객체정보임.
  • 필요한 시점에서 세그웨이 식별자를 통해 특정 세그웨이를 지정하고 위 메소드를 호출하면 세그웨이가 실행되면서 화면이 전환되는 구조

15.5.3 Unwind - 화면 복귀

  • 새로운 화면으로 전환하는 것은 Wind라고 한다면 Unwind는 wind작업을 해제 한다는 의미. 다시 말해 새로운 화면을 해제하고 본래의 화면으로 돌아간다는 해석이 됨
  • 세그웨이는 목적지가 되는 뷰 컨트롤러의 인스턴스를 자동으로 생성. 따라서 두번째 뷰 컨트롤러에서 첫번째 뷰 컨트롤러로 세그웨이를 연결하면 자동으로 첫번째 뷰 컨트롤러의 인스턴스가 만들어짐. 하지만 이미 첫번째 뷰 컨트롤러의 인스턴스가 존재하는 상황
  • 역방향 세그웨이를 다시 생성한다는 것은 이미 존재하는 뷰 컨트롤러의 인스턴스를 또다시 만들어 낸다는 의미.
  • 뷰 컨트롤러의 인스턴스는 하나 이상 존재해서는 안됨

프레젠테이션 방식으로 이동했을 때 dismiss(animated:)
내비게이션 컨트롤러를 이용하여 이동했을 때 popViewController(animated:)

  • 다른 방법은 세그웨이 레벨에서 제공하는 것으로 이른바 Unwind Segue를 이용
  • 뷰 컨트롤러 도크바 Exit는 Unwind Segue를 구현할 수 있도록 지원함
  • 버튼을 두번째 뷰 컨트롤러에 Present Modally로 연결 후 첫번째 뷰 컨트롤러 클래스에 다음과 같이 소스 코드 작성
  • @IBAction func unwindToVC(_ segue : UIStoryboardSegue){
  • 이경우 반드시 UIStoryboardSegue타입을 인자값으로 입력받도록 정의해야함

Unwind Segue를 이용하여 한꺼번에 여러 페이지 복귀하기
중간페이지로 돌아가기

  • 되돌아갈 화면의 뷰 컨트롤러 클래스에 UIStoryboardSegue 객체를 인자값으로 받는 메소드를 구현해두기만 하면 이를 시스템에서 Unwind 메소드로 인식함
  • 따라서 Exit 아이콘에서 해당 메소드를 연결하는 것으로 Unwind Segue를 손쉽게 구현
  • 이 때문에 호출될 메소드의 이름은 앱 프로젝트 영역에서 구분되는 이름이어야 하며, 각 뷰컨트롤러를 대표할 수 있는 이름으로 만드는 것이 좋음

15.5.4 커스텀 세그

  • 지금까지 사용한 세그웨이 객체는 UIKit 프레임 워크에서 제공하는 UIStoryboardSegue 클래스를 통해 구현된 객체
  • 앱 개발시 기본적인 기능의 세그웨이로는 원하는 기능을 구현하기 힘든 경우 발생
  • 이럴때 대비하여 UIKit 프레임 워크는 UIStoryboardSegue 클래스를 서브 클래싱 하여 새로운 기능을 갖춘 세그웨이 객체를 정의 할 수 있도록 지원함
  • 이를 커스텀 세그라고 부름
  • UIStoryboardSegue를 상속받음
  • 세그웨이 실행을 처리하는 메소드는 perform()이기 때문에 커스텀 세그에서 원하는 화면 전환기능을 구현하기 위해서는 이를 오버라이드해야함
class NewSegue : UIStoryboardSegue {
    
    override func perform() {
        //세그웨이 출발지 뷰 컨트롤러
        let srcUVC = self.source
        
        //세그웨이 도착지 뷰 컨트롤러
        let destUVC = self.destination
        
        UIView.transition(from: srcUVC.view, to: destUVC.view, duration: 2, options: .transitionCurlDown)
        
    }
}

15.5.5 전처리 메소드의 활용

  • 세그웨이를 이용하여 화면을 전환하는 과정에서 뭔가 특별한 처리를 해 주어야 할 때에는 어떻게?
  • 코코아 터치 프레임워크는 세그웨이가 실행되기 전에 특정한 메소드를 호출하도록 정해져 있기 때문에 이를 이용하면 화면을 전환하기 전에 필요한 처리를 해줄 수 있음
  • 이를 전처리 메소드라고 함
  • 세그웨이를 실행하기전에 값을 저장해둘 필요가 있거나 혹은 경고창을 띄워주는 등의 처리를 해야할 경우 전처리 메소드에 해당 내용을 작성해 놓으면 그 내용이 세그웨이가 실행되기 전에 자동으로 실행됨

prepare(for segue : UIStoryboardSegue, sender : Any?) {...}

  • 주의! 이 메소드의 호출주체는 우리가 아니라 시스템이 호출하는 방식
  • 우리가 호출하고 싶을때 임의로 호출하지 못함
  • 일반적으로 뷰 컨트롤러에 연결된 세그웨이는 여러개가 될 수 있는데 이들 세그웨이는 모두 실행전에 전처리 메소드를 공통적으로 호출함
  • 하나의 전처리 메소드를 여러 세그웨이가 공유하는 방식
  • 이 때문에 전처리 메소드는 어느 세그웨이가 자신을 호출할 것인지를 알고 구분해 주어야할 필요가 있는데 그에 대한 정보가 첫번째 매개변수를 통해 전달
  • 이 매개변수를 사용하여 어느 세그웨이가 실행되는 것인지 알 수 있으므로 이를 이용하여 세그웨이에 따른 조건별 작업을 처리하면 됨 (segue.identifier)
  • 전처리 메소드의 두번째 매개변수는 세그웨이를 실행하는 트리거에 대한 정보
  • 하나의 세그웨이는 여러개의 트리거를 가질수 있음. 화면 내에 있는 여러요소가 동일한 세그웨이를 실행할 수 있다는 뜻
  • 만약 액션세그라면 버튼이나 테이블셀 혹은 제스쳐등의 객체가 주 대상이 되고
  • 메뉴얼세그라면 뷰 컨트롤러 자신이 인자값으로 전달될것임
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "custom_segue") {
            NSLog("커스텀세그 실행")
        }else if (segue.identifier == "action_segue"){
            NSLog("액션세그 실행")
        }else {
            NSLog("알수없는 세그 실행")
        }
        
    }
}


블로그 이미지

百見 이 不如一打 요 , 百打 가 不如一作 이다.

,