5.1 📌 화면 전환 과정에서의 값 전달 방식
뷰 컨트롤러 사이에 값을 주고받는 방식은 두 가지 개념이 있다.
1. 뷰 컨트롤러에서 다음 뷰 컨트롤러로 값을 직접 전달하는 방식 ( 동기 방식 )
2. 공동 저장소를 만들어 뷰 컨트롤러에서 여기에 값을 저장하고 화면을 이동하면 다음 뷰 컨트롤러에서 저장소를 열어 다시 값을 꺼내오는 공유 방식이 있다. ( 비동기 방식 )
직접 전달 방식 ( 동기 방식 )
뷰 컨트롤러에 직접 값을 전달하는 방식으로 주로 영속성으로 값을 저장할 필요가 없는 화면 전환에 사용된다.
화면이 전달될 때 데이터가 함께 전달되거나 화면이 전환되기 전에 데이터가 미리 전달되므로 전달 과정에서 값이 누락될 염려가 없다는 장점이 있다.
값이 전달되지 않으면 화면 전환 자체가 이루어지지 않게 구현할 수도 있다.
간접 전달 방식 ( 비동기 방식 )
지속적으로 값을 저장할 필요가 있는 화면 전환에서 사용된다.
ex) 로그인 처리, 메모장 앱
로그인 정보가 다음 화면에서도 유지가 되어야하는 경우, 유지하지 않으면 뷰 컨트롤러를 이동할 때마다 로그인해야 하는 불편함이 생긴다.
로그인이 성공했을 때 로그인 정보를 저장소에 등록해놓고 화면 전환을 한다.
비동기 방식은 마치 공을 하늘에 던져놓고 목적지에 가서 받는 것이라고 볼 수 있는데
화면이동과 네트워크는 속도가 다르다. 화면이동이 완료가 되어도 네트워크 통신이 안되어 있으면 화면은 빈 화면이 출력된다.
그러므로 이에 대한 처리가 필요한 방식이다.
5.2 📌 동기 방식으로 값을 전달하기
1. VC1에서 VC2로 전달할 값을 준비
입력폼, 테이블 셀같은 곳에 입력받은 데이터를 준비한다.
아울렛 변수들을 생성한다.
import UIKit
class ViewController: UIViewController {
@IBOutlet var isUpdateText: UILabel!
@IBOutlet var intervalText: UILabel!
@IBOutlet var email: UITextField!
@IBOutlet var isUpdate: UISwitch!
@IBOutlet var interval: UIStepper!
@IBAction func submitButton(_ sender: UIButton) {
}
@IBAction func onSwitch(_ sender: UISwitch) {
if sender.isOn == true {
self.isUpdateText.text = "갱신됨"
} else {
self.isUpdateText.text = "갱신하지 않음"
}
}
@IBAction func onStepper(_ sender: UIStepper) {
let value = Int(sender.value)
self.intervalText.text = "\(value)분마다"
}
}
스위치가 On 될때와 Stepper 에 버튼이 눌렸을 때 레이블 텍스트가 변하도록 한다.
전달해야할 값은 email 텍스트 값, OnSwitch 여부, 갱신주기 값으로 총 3개이다.
@IBOutlet var email: UITextField!
@IBOutlet var isUpdate: UISwitch!
@IBOutlet var interval: UIStepper!
그러므로 전달받을 프로퍼티도 3개이다.
2. VC2에서 값을 대입받을 프로퍼티를 정의
여러 값을 동시에 전달할 수도 있으므로 값의 갯수만큼 프로퍼티도 정의해야하고 타입도 일치해야 한다.
새 뷰 컨트롤러를 추가하고 스토리보드 아이디를 RVC로 지정한다.
전달한 값이 표시되는 화면을 구성한다.
ResultViewController.swift파일을 만들고 RVC와 연결해준다.
import UIKit
class ResultViewController: UIViewController {
@IBOutlet var resultEmail: UILabel!
@IBOutlet var resultUpdate: UILabel!
@IBOutlet var resultInterval: UILabel!
@IBAction func backButtonTapped(_ sender: UIButton) {
}
}
ResultViewController 클래스를 생성하고
화면에 값을 표시하는데 사용될 레이블들을 아울렛변수로 연결한다.
var paramEmail: String = ""
var paramUpdate: Bool = false
var paramInterval: Double = 0
값을 받을 변수를 선언한다. 타입이 일치하도록 선언해야한다.
아울렛 변수는 외부에서 값을 직접 대입할 수가 없고 외부 객체에서 직접 참조할 수 없도록 제한되어 있다.
VC1에서 VC2의 아울렛 변수에 바로 값을 대입할 수 없기 때문에 값을 받을 프로퍼티를 정의한다.
3. VC1에서 VC2의 인스턴스를 직접 생성하거나, 이미 생성되어 있는 인스턴스의 참조를 읽어온다.
프레젠테이션 방식으로 화면을 전환하거나 내비게이션 컨트롤러를 이용하여 화면을 전환하려면 intantiateViewController메소드,
세그웨이를 이용한다면 세그웨이 객체가 목적지에 해당하는 뷰 컨트롤러의 인스턴스를 알아서 생성해주기 때문에
.destination 속성을 통하여 인스턴스 참조를 읽어온다.
@IBAction func submitButton(_ sender: UIButton) {
guard let rvc = self.storyboard?.instantiateViewController(identifier: "RVC") as? ResultViewController else { return }
}
VC1에서 VC2의 인스턴스를 직접 생성하는 코드이다.
너무 일찍 인스턴스를 생성하면 메모리 낭비이기 때문에 화면 전환을 하는 시점에 생성을 하는 것이 적당하다.
Submit버튼을 누르면 VC2의 인스턴스를 생성하도록 한다.
as? 로 다운캐스팅을 한 이유❓
intantinateViewController 메소드를 이용해서 VC2의 인스턴스를 생성하면 UIViewController 타입으로 기본 생성된다.
UIViewController타입은 뷰 컨트롤러들 중 가장 상위에 있는 클래스이다.
하지만 paramEmail, paramUpdate, paramInterval 프로퍼티들은 ResultViewController에 정의되어 있고
UIViewController에는 정의되어있지 않다. 그렇기 때문에 참조하려고하면 찾을 수 없다고 에러를 발생시킨다.
class ResultViewController: UIViewController
따라서 생성한 VC2의 인스턴스를 ResultViewController 타입으로 캐스팅해야 한다.
ResultViewController는 UIViewController를 상속받은 자식클래스이다.
부모 클래스로 생성된 인스턴스를 자식클래스로 캐스팅하는 것을 다운캐스팅이라고 한다.
캐스팅에는 옵셔널 캐스팅과 강제 캐스팅이 있는데 강제 캐스팅은 as!로 캐스팅하는 방법으로 nil이 발생하면 앱이 죽는 문제가 있다.
때문에 as?를 사용하는 옵셔널 캐스팅으로 안전하게 캐스팅한다.
옵셔널타입이기 때문에 guard let 구문을 사용해서 일반 타입으로 옵셔널 바인딩 처리한 모습이다.
4. VC1에서 VC2에서 정의한 프로퍼티에 값을 대입한다.
뷰 컨트롤러의 인스턴스를 얻어오면 2에서 정의한 프로퍼티를 속성 변수로 사용할 수 있다. 전달할 값을 여기에 직접 대입한다.
ResultViewController에 값을 받을 변수들을 다음과 같이 선언해두었다.
@IBAction func submitButton(_ sender: UIButton) {
guard let rvc = self.storyboard?.instantiateViewController(identifier: "RVC") as? ResultViewController else { return }
guard let emailText = self.email.text else { return }
rvc.paramEmail = emailText
rvc.paramUpdate = self.isUpdate.isOn
rvc.paramInterval = self.interval.value
self.present(rvc, animated: true)
}
VC1 뷰 컨트롤러 클래스에서는 VC2 인스턴스의 프로퍼티에 값을 대입하는 코드를 짠다.
값을 받는 변수들의 타입에 맞게 타입을 맞추어 전달해야한다.
rvc.paramUpdate = self.isUpdate.isOn
paramUpdate는 Bool타입이다.
instantiateViewController(identifier: "RVC")메소드를 사용해서 스토리보드 아이디 RVC를 가진 뷰 컨트롤러에 연결된 클래스파일에서 paramUpdate 변수에 Bool타입을 맞추어 값을 전달해야한다.
.isOn은 Bool타입이기 때문에 타입이 일치하게 된다.
마찬가지로 .value는 Double타입이다.
다만 email.text는 텍스트가 없을 수 있기 때문에 옵셔널로 선언되어 있다.
guard let 문으로 언래핑하여 값을 넘겨준다.
그 후 present 메소드로 VC2로 화면 전환한다.
5. VC1에서 VC2로 화면을 전환한다.
전달된 값들을 화면의 레이블에 띄우는 시점❓
너무 일찍 처리하면 메모리에 화면 객체가 로드되기 전에 실행되고, 늦으면 빈 화면이 뜨는 문제가 발생한다.
때문에 화면이 메모리에 로드되고 난 직후가 적절한 시점이다.
iOS 생명주기에서는 화면이 메모리에 로드괴고 난 시점에 viewDidLoad() 메소드를 호출한다.
viewDidLoad() 메소드는 상위 클래스에서 이미 정의되어 있기 때문에 오버라이딩만 해주면 된다.
import UIKit
class ResultViewController: UIViewController {
@IBOutlet var resultEmail: UILabel!
@IBOutlet var resultUpdate: UILabel!
@IBOutlet var resultInterval: UILabel!
var paramEmail: String = ""
var paramUpdate: Bool = false
var paramInterval: Double = 0
override func viewDidLoad() {
self.resultEmail.text = paramEmail
self.resultUpdate.text = (self.paramUpdate == true ? "자동갱신" : "자동갱신안함")
self.resultInterval.text = "\(Int(paramInterval))분 마다 갱신"
}
@IBAction func backButtonTapped(_ sender: UIButton) {
self.presentingViewController?.dismiss(animated: true)
}
}
각 레이블 텍스트에 받아온 값을 저장하는 변수를 출력한다.
실행결과
[5.2.2] 내비게이션 컨트롤러를 통해 화면 전환하면서 값 전달하기
📌 내비게이션 컨트롤러를 Embed in 해주고 submit버튼을 내비게이션 아이템으로 변경한다.
@IBAction func submitButton(_ sender: UIButton) {
guard let rvc = self.storyboard?.instantiateViewController(identifier: "RVC") as? ResultViewController else { return }
guard let emailText = self.email.text else { return }
rvc.paramEmail = emailText
rvc.paramUpdate = self.isUpdate.isOn
rvc.paramInterval = self.interval.value
self.present(rvc, animated: true)
}
넘어가는 방식이 바뀌었기 때문에 코드를 수정해주어야 한다.
일단 sender를 UIButton에서 UIBarButtonItem으로 수정해준다.
화면 전환도 내비게이션 컨트롤러를 이용한 push방식으로 변경해야 한다.
@IBAction func submitButton(_ sender: UIBarButtonItem) {
guard let rvc = self.storyboard?.instantiateViewController(identifier: "RVC") as? ResultViewController else { return }
guard let emailText = self.email.text else { return }
rvc.paramEmail = emailText
rvc.paramUpdate = self.isUpdate.isOn
rvc.paramInterval = self.interval.value
self.navigationController?.pushViewController(rvc, animated: true)
}
실행결과
[5.2.3] 세그웨이를 이용하여 화면 전환하면서 값을 전달하기
📌 두 뷰 컨트롤러를 메뉴얼 세그웨이로 연결하고 타입은 Show로 연결한다.
세그웨이를 선택하고 Identifier 항목을 "ManualSubmit"으로 입력한다.
@IBAction func omPerformSegue(_ sender: UIBarButtonItem) {
self.performSegue(withIdentifier: "ManualSegue", sender: self)
}
📌 세그웨이 identifier를 입력했으면 이제 세그웨이를 실행하는 구문을 작성한다.
기존 submit연결을 끊고 새로운 @IBAction메소드를 생성해서 실행한다.
세그웨이 이름이 ManualSegue라고 적혀있는 세그웨이를 실행시키고
sender의 아규먼트인 self는 출발지가 현재의 뷰 컨트롤러라는 것을 알려주는 것이다.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let dest = segue.destination
guard let rvc = dest as? ResultViewController else { return }
guard let emailText = self.email.text else { return }
rvc.paramEmail = emailText
rvc.paramUpdate = self.isUpdate.isOn
rvc.paramInterval = self.interval.value
}
📌 모든 세그웨이는 실행되기전에 prepare메소드를 먼저 호출한다.
만약 커스텀 클래스에서 오버라이드하지 않았다면 그보다 상위 클래스에 정의된 동일 메소드를 호출한다.
이 메소드를 오버라이드해서 메소드를 작성한다.
세그웨이에는 항상 출발지와 목적지로 연결된 뷰 컨트롤러 인스턴스가 존재하는데, destination 속성을 이용하면
세그웨이의 목적지 뷰 컨트롤러의 인스턴스를 얻을 수 있다.
1. 목적지 뷰 컨트롤러의 인스턴스를 dest 상수에 할당한다.
2. 목적지 뷰 컨트롤러의 인스턴스는 UIViewController 타입이기 때문에 ResultViewController의 프로퍼티를 인식하지 못한다.
때문에 ResultViewController로 다운캐스팅이 필요하다. as? 로 옵셔널 다운캐스팅해준다.
3. 다운 캐스팅을 하여 얻은 ResultViewController 타입의 인스턴스를 rvc상수에 할당하고
4. rvc의 프로퍼티 3개에 차례로 값을 대입한다.
'Swift > 꼼꼼한 재은씨 기본편' 카테고리의 다른 글
[꼼꼼한 재은씨 기본편 Chapter 05] 다른 뷰 컨트롤러와 데이터 주고받기 - 이전 화면으로 값 전달하기 (0) | 2024.07.11 |
---|---|
[꼼꼼한 재은씨 기본편 Chapter 04] 화면 전환 - 전처리 메소드의 활용 (0) | 2024.07.08 |
[꼼꼼한 재은씨 기본편 Chapter 04] 화면 전환 - 커스텀 세그웨이 (0) | 2024.07.08 |