iOS를 하게 되면 가장 처음엔 하나의 화면으로만 앱을 만들지만 상황에 따라 여러 개의 화면이 필요해진다.
이 때, 화면을 전환하기 위해 여러가지 방법이 있는데, 전체적인 소개는 아래 링크를 참고하자.
이 글에서는 Segue를 통하여 화면 이동을 하는 방법을 소개하고자 한다.
Segue는 한 뷰 컨트롤러에서 다른 뷰 컨트롤러로 전환을 수행하기위한 수단이다.
이를 이용해 사용자가 앱 내에서 다른 화면으로 이동할 수 있도록 할 수 있다.
좀더 자세히 알아보자면
Segue의 클래스는 UIStoryboardSegue
다.
이 클래스는 ViewController
간의 이동을 관리하기 위해 추상화하고 전환하는데에 있어서의 세부사항을 캡슐화를 하는 클래스이다.
이전 ViewController에서 새로운 ViewController로 전환할 때, segue
인스턴스를 사용한다.
Segue에서 특별한 점을 소개하자면 아래와 같다.
- 출발지 / 도착지
- identifier
- (modal일 경우)
- modalPresentationStyle
- modalTransitionStyle
Segue는 전 화면과 후 화면을 부드럽게 전환을 해야하기 때문에, 당연하게도 출발지와 목적지가 필요하다.
Segue는 스토리보드와 함께 사용하기 때문에 스토리보드에서 출발지와 도착지를 링크하면 된다.
한 화면에서 여러 개의 Segue가 존재할 수도 있고, 각 Segue에 따른 데이터가 필요할 수 있다.
이를 구별하기위해 사용하는 것이 identifier다.
modal방식이란,
현재의 화면을 보존하면서, 사용자의 주의를 새로운 작업에 집중시키기위해 전체 화면을 차지하는 방식
을 말한다.
예를 들어, 사용자가 사진을 선택하거나, 이메일을 작성할 때 사용할 수 있다.
이런 경우에는 새로운 뷰가 현재 뷰를 완전히 가릴 수 있다.
스토리보드에서 A화면에서 B화면으로 Segue를 만들어보자.
일단 필요한 것은 trigger가 될 요소를 만들어주는 것이다.
보통은 버튼을 trigger로 사용한다.
A 화면에서 버튼을 누르면 B 화면으로 이동하는 것이다.
- 시나리오
- 화면A(노랑)
- 버튼 클릭
- 화면B(파랑)로 전환
화면A(노랑)의 트리거(버튼)을 클릭하고 Control버튼을 누른 상태에서 화면B(파랑)로 드래그앤드랍 한다.
그러면 아래와 같이 선택을 위한 창이 생겨난다.
가장 일반적으로 사용하는 것은 Present Modally
. 이걸 선택해준다.
그러면 두 화면 사이에 줄이 생기고 줄을 클릭하면 우측 Attribute Pannel에서 identifier를 지정할 수도 있게 된다.
identifier를 설정하지 않아도 작동한다. 지금은 세팅하지않는다.
노란화면 (화면A)에서 버튼을 누르면 파란화면 (화면B)로 이동하는 것을 확인할 수 있다.
위 방법에서 Segue를 만들 때, 버튼을 다음화면으로 드래그하면서 Segue를 만들었다.
이렇게 하면 버튼을 눌렀을 때, 이동을 할수 있었다.
하지만 위 방법은 트리거가 될 버튼을 클릭했을 때, 곧장 이동을 하도록 되었기 때문에 좀더 디테일한 과정을 수행하기가 어렵다.
(물론 디테일한 부분을 보완할 수 있는 방법은 있다)
그러면 좀더 내가 원하는 시점에 Segue를 사용하려면 어떻게 해야할까?
아래는 performSegue(withIdentifier:sender:)
메서드를 이용하는 방법이다.
이번에도 노란화면(화면A)에서 파란화면(화면B)로 이동하는 상황이다. 이번엔 화면A의 버튼에 대한 IBAction을 ViewControllerA에 구현한 상태이다.
IBAction을 선언하고 링크하는 방법은 Storyboard - IBOutlet, IBAction 글을 참고하자
- 시작전 상황
-
⭐️ 1. A화면에서 B화면으로 Segue를 생성한다.
- ⭐️ 3. 원하는 시점에
performSegue(withIdentifier:sender:)
메서드 선언 이제 아래처럼 identifier를 적어서 performSegue메서드를 사용하자.
performSegue(withIdentifier: "goToB", sender: nil)
하지만 좀더 유연하게 시간이 흐르는 걸 시각적으로 나타내기 위해 아래와 같이 수정하였다.
주석에 설명한 것처럼, 변하는지 살펴보자.
- 1초뒤 : 배경이 빨강색으로
- 2초뒤 : 배경이 초록색으로
- 3초뒤 : 화면B로 이동
class ViewControllerA: UIViewController {
/// 버튼액션
@IBAction func btnAction(_ sender: Any) {
// 1초뒤 배경을 빨강으로 변경
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.view.backgroundColor = .systemRed
}
// 2초뒤 배경을 빨강으로 변경
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.view.backgroundColor = .systemGreen
}
// 3초뒤 화면B로 이동
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.performSegue(withIdentifier: "goToB", sender: nil)
}
}
}
노란화면 (화면A)에서 버튼을 누르면 파란화면 (화면B)로 이동하는 것을 확인할 수 있다.
이렇게 사용하면 좋은 점은
API통신의 성공시점
이나, 뭔가를 다운로드 완료한 시점
등 내가 원하는 상황에 performSegue()
메서드만 선언하기만 하면 된다는 것이다.
[Top]
A화면에서 B화면으로 Segue를 통해 화면을 띄웠다. 작업을 마치고 이제 다시 A화면으로 돌아가는 방법을 알아보자.
이걸 구현하는 방법은 두 가지가 있다.
- B화면에서
dismiss(animated:)
메서드를 통해 돌아가기 - A화면으로 돌아올
Unwind Segue
구현하기
두 방법 모두 @IBAction
을 구현해야한다.
- ⭐️ 1. B화면에서 트리거로 사용할 버튼을 만든다.
- 스토리보드에서 Button 생성하기
-
⭐️ 2-1. 스토리보드를 통해
@IBAction
코드를 만든다. -
⭐️ 2-2. B화면의 ViewController 내부에서
@IBAction
메서드를 만든다.
- ⭐️ 3. dismiss 메서드 구현
@IBAction func dismissToA(_ sender: Any) { self.dismiss(animated: true) }
모달로 표시된 화면에서는
self.dismiss()
메서드를 이용하여 이전 화면으로 돌아갈 수 있다. navigationController를 이용한 화면이라면popViewController()
메서드를 사용하여 이전 화면으로 돌아갈 수 있다.
- ⭐️ 4. 동작화면
- ⭐️ 1. B화면에서 트리거로 사용할 버튼을 만든다.
- 스토리보드에서 Button 생성하기
-
⭐️ 2. 도착할 화면인 화면A(노랑)의 ViewController 내부에
@IBAction
메서드를 만든다.
unwind segue를 사용할 때는 구현한 메서드 내부에는 따로dismiss()
메서드를 구현할 필요가 없다.
자체가 현재 화면으로 돌아올 통로를 만들어주는 과정이기 때문이다.-
파라미터를
segue: UIStoryboardSegue
로 세팅한다.@IBAction func unwindToA(segue: UIStoryboardSegue) { }
-
메서드 내부에는 돌아올 때, 구현하고 싶은 동작을 구현할 수 있다.
completion() 처럼 사용하는 것이다.
-
-
⭐️ 3. 스토리보드에서 unwind segue를 연결한다.
- ⭐️ 4. 동작화면
두 방법의 동작을 보면 사실상 동일해보인다.
첫번째 방법은 화면A의 상단에 생성된 화면B에서 self.dismiss()
메서드를 실행하는 것이다.
두번째 방법은 화면 A로 돌아갈 길을 만들기 위해 화면A의 내부 코드블럭에 unwind segue
를 구현했다.
즉, 첫번째는 돌아오기 시작하려는 곳에서 실행하는 것이라면 두번째는 돌아올 도착지에서 실행하는 것이다.
위에서 구현한 unwind의 장점은 돌아오려는 도착화면에서 지점을 만들어주는 방식이기 때문에 여러 화면에서 재사용이 가능하다.
아래 A(노랑), B(파랑), C(빨강) 세 화면을 보자. 모두 modal로 segue를 연결했다.
이 때 ViewController의 코드를 아래와 같이 각 A, B에 Unwind를 구현해보자.
import UIKit
class ViewControllerA: UIViewController {
@IBAction func unwindToA(segue: UIStoryboardSegue) { }
}
class ViewControllerB: UIViewController {
@IBAction func unwindToB(segue: UIStoryboardSegue) { }
}
class ViewControllerC: UIViewController { }
생성한 UIStoryboardSegue
를 각 화면에 연결하는데,
A로 돌아가는 segue는
화면B와 화면C에 중복으로 세팅이 가능하다.
- ⭐️ 4. 동작화면
위에서는 unwind segue를 버튼에 링크해줬다.
그런데 필요에 따라 버튼을 누르고 화면을 종료하는게 아니라
특정 API에 대한 성공시점이나 웹으로부터 다운로드가 완료되는 시점 처럼
특별한 시점에 Unwind segue를 실행할 수도 있지않을까? 해서 찾아봤는데 된다!!
일단 구현한 화면을 보자
- ⭐️ 2. Unwind Segue 구현
ViewControllerA(노랑)화면으로 돌아갈 거라서 이곳에 구현한다
import UIKit
class ViewControllerA: UIViewController {
@IBAction func unwindToA(segue: UIStoryboardSegue) { }
}
class ViewControllerB: UIViewController { }
- ⭐️ 3. Unwind Segue 링크시키기
이전에는 버튼에 Unwind Segue를 링크시켰는데, 이번엔 화면 자체에 링크시킨다.
그리고 이번엔 mannual을 선택한다.
- ⭐️ 4. Unwind Segue
ID
값 세팅하기
그러면 스토리보드 좌측 Document Outline 패널에 Unwind segue가 나온다.
이부분을 클릭한다.
그러면 우측 Attribute 패널이 활성화 되는데 이곳에 Storyboard Unwind Segue라는 부분이 보인다.
이곳의 Identifier부분에 사용할 ID값을 넣어주자.
여기선 unwindToA
라는 값을 넣었다.
- ⭐️ 5. Unwind Segue 를 실행할 함수를 구현한다. Unwind Segue가 아니더라도 Segue를 실행할 때는 아래 메서드를 이용한다.
performSegue(withIdentifier: "unwindToA", sender: nil)
이 함수를 특정 시점에 구현하자.
아래 코드는 ViewControllerB가 실행되자마자 2초 뒤에 performSegue가 실행되는 코드이다.
import UIKit
class ViewControllerA: UIViewController {
@IBAction func unwindToA(segue: UIStoryboardSegue) {}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.performSegue(withIdentifier: "unwindToA", sender: nil)
}
}
}
- ⭐️ 6. 동작화면
화면이동을 할때, 다음 화면에서 화면을 그리기 전에 정보가 필요한 경우가 있다.
아래 화면A(노랑), 화면B(초록), 화면C(파랑)이 있다.
A에서 C로 바로 갈 수도 있고, B를 거쳐서 C로 갈 수도 있다.
이 때, 어느 화면에서 왔는지 화면C에서 확인이 가능하다.
- ⭐️ 1. 스토리보드 모습
- ⭐️ 2. 코드 부분
Segue를 실행하게 되면 먼저 보내는 ViewController에서 prepare 메서드가 실행된다.
이때, 파라미터 segue는source
라는 시작지점,destination
라는 도착지점을 알려주는 변수를 가지고 있다.
source와 destination의 타입은UIViewController
다.
import UIKit
class ViewControllerA: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewControllerC {
destinationVC.textString = "A에서 왔어요"
}
}
@IBAction func unwindToA(segue: UIStoryboardSegue) { }
}
class ViewControllerB: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewControllerC {
destinationVC.textString = "B에서 왔어요"
}
}
}
class ViewControllerC: UIViewController {
var textString: String?
@IBOutlet weak var dataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let source = textString {
dataLabel.text = source
} else {
dataLabel.text = "데이터 없음"
}
}
}
- ⭐️ 3. 동작화면
이제 유연하게 동작을 위해서는 performSegue(withIdentifier:sender:)
메서드를 사용한다는 알았다.
그런데 performSegue를 실행할 때 보통 sender파라미터에는 nil을 넣는 걸 볼 수 있다.
이건 뭔데 사용하지않는 걸까?
아래 사례는 화면 A에서 과일이름이 적힌 칸을 눌렀을 때, sender에 값을 담아서 화면B로 보내는 방법이다.
- ⭐️ 1. 스토리보드 모습
-
⭐️ 2. 코드 부분 이번엔 코드가 길어서 약간의 설명이 필요하다.
구현되어있는 것을 몇가지 짚어보자.- tableView에 들어갈 data배열
- tableView 세팅
- Unwind Segue
- prepare메서드 내부에서 어떤 과일을 선택했는지 확인하고 전달
이번 상황에서는 performSegue가 테이블뷰의 셀을 클릭하는 시점에 실행된다.
tableView의 didSelecteRowAt에서 선택한 셀의 IndexPath를 가져와서
data중 몇번째 값인지를 가져온다.
그리고 마지막으로는 segue의 destination을 통해 도착화면 ViewController의 변수에 할당한다.
import UIKit
class ViewControllerA: UIViewController {
// 테이블뷰의 데이터 배열
var data = ["Apple", "Banana", "Orange"]
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = self
tableView.delegate = self
}
@IBAction func unwindToA(segue: UIStoryboardSegue) { }
}
extension ViewControllerA: UITableViewDataSource, UITableViewDelegate {
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = data[indexPath.row]
return cell
}
// MARK: - UITableViewDelegate
// 테이블뷰 셀 선택 시 호출되는 메서드
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 선택된 셀의 인덱스 패스를 Segue 실행 시 sender로 활용하여 전달
performSegue(withIdentifier: "goToB", sender: indexPath)
}
// Segue를 수행하기 전에 ViewControllerC의 델리게이트로 자신을 설정
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewControllerB {
if let indexPath = sender as? IndexPath {
// 선택된 테이블뷰 셀의 인덱스를 데이터로 전달
destinationVC.textString = "선택된 과일: \(data[indexPath.row])"
}
}
}
}
class ViewControllerB: UIViewController {
var textString: String?
@IBOutlet weak var fruitLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
fruitLabel.text = textString ?? "데이터 없음"
}
}
- ⭐️ 3. 동작화면
- 230724 : 초안작성
- 230725 : Unwind Segue 구현하기ㄴㅁ
- 230726 : Unwind Segue 트리거 커스텀하기
- 230727 : Segue의 identifier를 선언하는 방법
- 230727 : toc기능 구현
- 230727 : rformSegue의 Sender로 추가 정보 전달하