5.3 📌 이전 화면으로 값 전달하기
새로운 화면으로 값을 전달하는 방법을 요약
1. 값을 받을 새로운 화면을 스토리보드에 추가, 클래스 파일을 작성하여 연결 - VC2라고 부르도록 하겠음
2. 코드를 통해 VC2의 인스턴스 참조를 얻어오거나, 인스턴스 참조가 없는 경우 인스턴스를 생성
3. 전달하고자 하는 값을 인스턴스의 적절한 프로퍼티에 대입함, 프로퍼티를 미리 정의해야 함
4. 화면 전환
레이아웃을 구성하고 전달받을 프로퍼티들을 정의한다.
import UIKit
class ViewController: UIViewController {
@IBOutlet var resultEmail: UILabel!
@IBOutlet var resultIsUpdate: UILabel!
@IBOutlet var resultIntreval: UILabel!
var paramEmail: String?
var paramUpdate: Bool?
var paramInterval: Double?
override func viewWillAppear(_ animated: Bool) {
if let email = paramEmail {
resultEmail.text = email
}
if let update = paramUpdate {
resultIsUpdate.text = update == true ? "자동갱신" : "자동갱신안함"
}
if let interval = paramInterval {
resultIntreval.text = "\(Int(interval))분마다"
}
}
}
VC1 - VC2로 갈 때는 VC2의 화면이 출력되면 실행하도록 viewDidLoad메소드에 작성했다.
VC2로 갈 때마다 새로 인스턴스가 생성되기 때문에 화면이 처음 만들어질 때 호출되는 viewDidLoad 메소드가 화면 전환이 이루어질 때
항상 실행되기 때문이다.
하지만 VC1으로 다시 돌아올 때 전달되는 상황에서는 VC1은 이미 생성된 인스턴스이기 때문에
돌아올 때마다 새로 생성되지 않아서 viewDidLoad를 사용할 수 없다.
이럴 때는 뷰가 화면에 표시될 때마다 실행되는 viewWillAppeared메소드를 작성해서 호출시키면 된다.
Regist버튼을 트리거로 사용하는 세그웨이를 만들어준다.
import UIKit
class FormViewController: UIViewController {
@IBOutlet var emailTextField: UITextField!
@IBOutlet var isUpdate: UISwitch!
@IBOutlet var interval: UIStepper!
@IBOutlet var isUpdateText: UILabel!
@IBOutlet var intervalText: UILabel!
@IBAction func submitButtonTapped(_ sender: UIButton) {
let preVC = self.presentingViewController
guard let vc = preVC as? ViewController else { return }
vc.paramEmail = self.emailTextField.text
vc.paramUpdate = self.isUpdate.isOn
vc.paramInterval = self.interval.value
}
@IBAction func switchIsOn(_ sender: UISwitch) {
if sender.isOn == true {
self.isUpdateText.text = "갱신됨"
} else {
self.isUpdateText.text = "갱신되지않음"
}
}
@IBAction func intervalStepper(_ sender: UIStepper) {
let value = Int(sender.value)
self.intervalText.text = "\(value)분마다"
}
}
IB아웃렛 변수로 레이아웃을 연결해 주고 값을 입력받아 VC1 프로퍼티로 전달해 주는 코드를 짠다.
@IBAction func submitButtonTapped(_ sender: UIButton) {
let preVC = self.presentingViewController
guard let vc = preVC as? ViewController else { return }
vc.paramEmail = self.emailTextField.text
vc.paramUpdate = self.isUpdate.isOn
vc.paramInterval = self.interval.value
self.presentingViewController?.dismiss(animated: true)
}
submit버튼을 누르면 self.presentingViewController 속성을 통해서 VC1의 인스턴스를 참조한다.
현재의 뷰 컨트롤러 VC2를 화면에 표시해주고 있는 뷰 컨트롤러 VC1를 가리키는 의미이다.
그리고 dismiss메소드를 호출해서 이전 화면으로 복귀한다.
왜 다운캐스팅을 해주는가❓
VC1에 paramEmail, paramUpdate, paramInterval이 정의되어 있다.
let preVC = self.presentingViewController
이 구문을 통해 VC1을 참조할 수 있다.
하지만 xcode는 가리킨 뷰 컨트롤러가 상속받고 있는 UIViewController로 기본적으로 판단한다.
따라서 UIViewController에는 paramEmail, paramInterval 등의 프로퍼티가 없기 때문에 인식을 하지 못한다.
ViewController는 UIViewController를 상속받았기 때문에 자식 클래스이다.
UIViewController로 인식된 클래스를 자식 클래스인 ViewController로 인식시키기 위해 다운캐스팅이 필요하다.
문제발생❗️
위의 과정대로 진행했지만 입력받은 내용이 ViewController의 레이블에서 출력이 안 되는 문제가 발생했다.
알아보니
- 문제의 원인: 이 문제는 "타이밍" 때문에 발생. 마치 달리기 선수가 결승선에 도착하기도 전에 메달을 주려고 하는 것과 비슷한 상황
- FormViewController에서 데이터를 설정하고 즉시 화면을 닫았음
- 하지만 ViewController가 이 데이터를 받아 화면에 표시할 준비가 되기 전에 화면이 닫혔음.
- 결과적으로 ViewController가 화면에 나타날 때(viewWillAppear), 아직 새 데이터를 받지 못한 상태였음.
- 해결 방법: 우리는 이 문제를 "기다림"으로 해결. 마치 달리기 선수가 결승선에 도착한 후에 메달을 주는 것처럼요.
- FormViewController에서 화면을 완전히 닫은 후에 ViewController에게 "이제 데이터를 표시해도 좋아"라고 알려줍니다.
- 이를 위해 dismiss(animated:completion:) 메소드의 completion 핸들러를 사용.
- 이 completion핸들러로 화면이 닫힌 후 함수, 클로저를 실행
- 왜 이렇게 하면 해결되는가
- ViewController는 새 데이터를 받을 준비가 완전히 된 상태에서 updateUI() 메소드를 호출
- 이는 마치 달리기 선수가 결승선에 도착하고, 숨을 고른 후에 메달을 받는 것과 같음
- 결과적으로 사용자는 FormViewController에서 입력한 데이터가 ViewController에 즉시 반영되는 것을 볼 수 있음.
func updateUI() {
if let email = paramEmail {
resultEmail.text = email
}
if let update = paramUpdate {
resultUpdate.text = update ? "자동갱신" : "자동갱신안함"
}
if let interval = paramInterval {
resultInterval.text = "\(Int(interval))분마다"
}
}
값을 받아서 ui에 표시해 주는 부분을 함수로 구현해서 분리
@IBAction func onSubmit(_ sender: Any) {
let preVC = self.presentingViewController
guard let vc = preVC as? ViewController else { return }
vc.paramEmail = self.emailTextField.text
vc.paramUpdate = self.isUpdate.isOn
vc.paramInterval = self.interval.value
dismiss(animated: true, completion: vc.updateUI)
}
dismiss 메소드의 completion 핸들러에서 함수를 받아서 화면이 완전히 사라진 후에 함수를 실행한다.
dismiss(animated: true) {
vc.updateUI()
}
completion핸들러를 생략하고 후행클로저로 위와 같이 간략화해서 작성할 수 있다.
실행결과
'Swift > 꼼꼼한 재은씨 기본편' 카테고리의 다른 글
[꼼꼼한 재은씨 기본편 Chapter 05] 다른 뷰 컨트롤러와 데이터 주고받기 - 저장소를 사용하여 값을 주고 받기 (0) | 2024.07.16 |
---|---|
[꼼꼼한 재은씨 기본편 Chapter 05] 다른 뷰 컨트롤러와 데이터 주고받기 ( 동기 방식 ) (0) | 2024.07.10 |
[꼼꼼한 재은씨 기본편 Chapter 04] 화면 전환 - 전처리 메소드의 활용 (0) | 2024.07.08 |