화면 전환에 대하여 공부해 봅시다 😀
- 화면 전환 방식
- 뷰 컨트롤러 직접 호출 방식
- 네비게이션 컨트롤러 이용 방식
- segue way 이용 방식
화면 전환 방식은 쉽게 4가지로 구분할 수 있음.
첫 번째 방식인 <view controller의 view 위에 다른 view를 가져와 바꿔치기>는 하나의 뷰 컨트롤러가 2개 이상의 루트 뷰를 관리해야 하고, 뷰를 제어할 책임을 지는 컨트롤러가 모호해진다는 단점이 있음.
또한 고려할 부분이 많기 때문에, view를 이용한 화면 전환은 되도록이면 사용하지 말 것 ❌
그 외 3가지 방식은 전환할 화면의 인스턴스를 생성하고 호출하여 기존 화면에 덮는 방식임.
뷰 컨트롤러를 직접 호출해서 화면을 표시한다 하여 프레젠테이션(presentation)방식이라고도 함.
프로젝트를 생성할 시 자동으로 만들어지는 View Controller을 보면, UIViewController 클래스를 상속받음을 알 수 있음.
프레젠테이션 방식은 이 클래스에 정의된 present 메소드를 이용
present(<새로운 뷰 컨트롤러 인스턴스>, animated: <애니메이션 여부>
화면 전환이 완료되는 시점에 특정 로직을 실행해 주려면,
present(_: animated: completion: )
메소드를 이용.
마지막 매개변수는 실행 구문을 클로저나 함수 형식으로 입력받아, 화면 전환이 완전히 끝난 후 호출해 줌.
Q. 그냥 present 메소드 다음에 코드를 이어서 작성하면 안 될까?
A. 불행하게도 present 메소드 다음에 작성된 구문이 화면 전환이 끝난 다음에 실행된다는 보장을 할 수가 없음 ㅜ.ㅜ,,
dismiss(animated:)
메소드를 사용해 주기.
이전 화면으로 돌아가는 것이기 때문에 뷰 컨트롤러의 인스턴스를 인자값으로 설정해 주지 않아도 됨.
화면 복귀가 처리되었을 때 실행하고 싶은 로직이 있다면, 마찬가지로
dismiss(animated: completion: )
메소드를 사용.
이때, 화면을 걷어내는 주체는 자기 자신이 아니라 자신을 띄우고 있는 이전 뷰 컨트롤러임.
다음 화면의 뷰 컨트롤러가 이전 뷰 컨트롤러에게 화면을 내려달라고 요청하는 방식으로 화면 복귀가 이루어지는데, 화면을 내려달라고 요청할 대상을 참조할 포인터가 presentingViewController이다.
따라서, 복귀 메소드를 호출하는 대상 인스턴스는 self가 아니라 self.presentingViewController 임
present 메소드를 이용 예제
첫 번째 화면 구성: "첫 번째 화면" label과 다음 화면으로 넘어갈 수 있는 버튼
두 번째 화면 구성: "두 번째 화면" label과 다음 화면으로 넘어갈 수 있는 버튼
두 번재 뷰 컨트롤러가 선택된 상태에서, 인스펙터 영역에서 StoryBoardID 항목을 찾아 SecondVC라고 입력!
(다른 것으로 입력해도 상관없음)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func tapPresentNext(_ sender: UIButton) {
// 이동할 뷰 컨트롤러 객체를 참조
guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") else {
return
}
//인자값으로 뷰 컨트롤러 인스턴스를 넣고 프레젠트 메소드 호출
self.present(uvc, animated: true)
}
}
self.storyboard?.instantiateViewController(withIdentifier: "SecondVC")
새로 표시할 뷰 컨트롤러를 스토리보드로부터 읽어와 인스턴스화하는 부분.
입력된 StoryboardID와 일치하는 뷰 컨트롤러를 찾아, 인스턴스를 생성하고 이 값을 받아옴
present 메소드를 사용할 때, 전환할 대상이 될 인스턴스(instantiateViewController 메소드를 이용하여 만들어둔 uvc 상수)를 인자값으로 넘겨주면 화면 전환이 이루어짐.
두 번째 뷰 컨트롤러와 SecondViewController 클래스가 연결 완료!
SecondViewController에 뒤로 가기 버튼 액션을 설정해 주기
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tapPresentUnwind(_ sender: UIButton) {
self.presentingViewController?.dismiss(animated: true)
}
}
이전 화면으로의 복귀를 진행 완료
navigation controller 의 특성
내비게이션 컨트롤러는 항상 콘텐츠 계층 구조의 시작점 역할을 하는 뷰 컨트롤러와 함께 다니는데, 이를 루트 뷰 컨트롤러라고 함.
앞서 정리한 것처럼, navigation controller은 화면에 현재 표시되고 있는 뷰 컨트롤러들을 navigation stack을 이용하여 관리한다..
스택 형식으로 쌓았을 때,
가장 아래에 있는 첫 번째 뷰 컨트롤러 => 루트 뷰 컨트롤러,
최상위에 있는 마지막 뷰 컨트롤러 => 현재 화면에 표시되고 있는 뷰 컨트롤러
스택을 이용하여 관리하는 Navigation controller 답게, 다음화면으로 넘어갈 때는 pushViewController 메소드 이용.
pushViewController(_: animated: )
마찬가지로 화면을 복귀할 때는,
popViewController(animated: )
메소드를 사용.
내비게이션 컨트롤러를 추가하는 방식 2가지
뷰 컨트롤러를 선택하여 활성화한 뒤,
Editor - Embed Int - Navigation Controller 을 선택하면,
스토리보드에서 선택된 뷰 컨트롤러를 루트 뷰 컨트롤러로 삼아, 내비게이션 컨트롤러만 추가됨.
내비게이션 바가 생성.
프레젠테이션 방식을 이용할 때와 동일하게, 뷰 컨트롤러를 만들어 주고 label과 버튼을 만들어 준 뒤,
StoryboardID 항목에 SecondVC를 입력
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func tapNCNext(_ sender: UIButton) {
//두 번째 뷰 컨트롤러 인스턴스를 가져온다.
guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") else {
return
}
//화면 전환
self.navigationController?.pushViewController(uvc, animated: true)
}
}
pushViewController 메소드는 기술적 관점에서 내비게이션 스택 최상위에 뷰 컨트롤러 객체 uvc를 추가하는 것이지만, 사용자들에게는 화면을 전환하는 결과로 나타남.
pushViewController 메소드는 호출하는 대상이 내비게이션 컨트롤러임.
코드를 실행하여 다음 화면으로 버튼을 누르면,
무사히 다음 화면으로 넘어왔는데, 프레젠테이션 방식과 다른 점은 바로 자동으로 back 버튼이 만들어진다는 것.
마찬가지로, SecondViewController 클래스를 만들어 준 뒤 두 번째 화면 뷰 컨트롤러와 연결해 주기.
이전 화면 버튼의 액션을 만들어 주고, popViewController 메소드를 사용하면 화면 복귀가 이루어진다.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tapNCUnwind(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
}
}
앞서 프로그래밍 방식으로 화면을 전환하는 방법에 대하여 알아보았음.
지금부턴 스토리보드의 화면 전환 기능을 이용해 보겠음!
main에 추가되어 있는 뷰 컨트롤러 외에 하나를 더 추가해 준 후에, 앞선 예제들처럼 레이블과 버튼을 추가해 준다.
버튼을 선택하고 두 번째 뷰 컨트롤러 전체에 드래그 하면, 세그웨이 연결 옵션이 뜨는데,
여기서 Present Modally가 앞서 배운 present(:animated:) 메소드를 이용한 화면 전환과 같은 기능을 함.
Present Modally를 선택하면
화면과 화면 사이에 화살표가 생기는데, 이 화살표가 바로 두 개의 뷰 컨트롤러가 세그웨이로 연결되었다는 표시
이처럼 pushViewController 메소드처럼 이용하려면 내비게이션 컨트롤러를 생성해 준 뒤, 옵션을 Show 로 선택하기.
화면 복귀를 하고 싶으면, 앞서 배운 화면 복귀용 메소드를 똑같이 이용
- present Modally 옵션 - dismiss(animated:)
- show 옵션 - popViewController(animated:)
peformSegue(withIdentifier: <세그웨이 식별자>, sender: <세그웨이 실행 객체>)
매뉴얼 세그웨이를 연결할 때는 뷰 컨트롤러와 뷰 컨트롤러 사이를 직접 연결
도크바에서 view controller을 선택한 후, 두 번째 뷰 컨트롤러로 드래그한 뒤, Present Modally를 선택
화살표 마크가 추가되면, 세그웨이의 Identifier을 ManualWind로 입력해 보자.
세그웨이를 실행하기 위해 첫 번째 화면에 다음 화면으로 버튼을 만든 뒤 앞서 언급한 performSegue 메소드를 작성
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func tapManualNext(_ sender: UIButton) {
//세그웨이 실행
self.performSegue(withIdentifier: "ManualWind", sender: self)
}
}
화면을 복귀하기 위해서는 똑같이 앞에서 언급했던 메소드들을 이용하면 됨. 😀
이처럼 세그웨이를 이용하여 화면을 전환하는 방식은 구현이 쉽고 간단하지만, 전환 과정에서 제어할 수 있는 것이 제한됨.
그렇다면, 세그웨이를 이용하여 화면을 전환하는 과정에서 특별한 처리가 필요할 땐 어떻게 해야 할까?
다행히도 코코아 터치 프레임워크는 세그웨이가 실행되기 전에 전처리 메소드를 호출하도록 설계되어 있음.
이를 이용하면 화면을 전환하기 전에 필요한 처리가 가능하다.
prepare(for segue: UIStoryboardSegue, sender: Any?) { //code}
사실 전처리 메소드는 UIViewController 클래스에 이미 정의되어 있는 메소드이므로, 사용하고 싶을 땐 오버라이드하여 작성하면 됨.
출처: 꼼꼼한 재은씨의 Swift