안녕하세요. iOS 개발을 하다 보면 이전 화면에 값을 전달해야 할 때가 있습니다. 이전 화면에 값을 전달할 때는 다음 화면에 값을 전달할 때보다는 고려할 것이 더 많은 것 같습니다. 예제를 통해 역값 전달에 대한 몇 가지 방법을 알아보겠습니다.
첫 번째 화면은 이미지뷰와 다음 화면으로 이동하는 버튼을 가지고 있습니다. 버튼을 누르면 네비게이션 컨트롤러를 통해 push 방식으로 두 번째 화면으로 이동할 수 있습니다. 목적은 두 번째 화면에서 색을 선택하고 돌아왔을 때 이미지뷰의 배경색을 선택한 색상으로 만드는 것입니다.
두 번째 화면은 3개의 버튼을 가지고 있고, 버튼을 누르면 선택한 색을 label에 표시해 줍니다. 여기서 선택한 색을 첫 번째 화면에 전달해야 합니다.
첫 번째 화면의 코드입니다. imageColor라는 UIColor 타입의 프로퍼티를 가지고 있고, imageView의 backgroundColor를 imageColor로 설정해 주고 있습니다.
class FirstViewController: UIViewController {
var imageView = UIImageView()
var nextButton = UIButton()
var imageColor = UIColor.white
override func viewDidLoad() {
super.viewDidLoad()
configureView()
imageView.backgroundColor = imageColor
}
// ...
}
두 번째 화면의 코드입니다. selectedColor라는 프로퍼티를 가지고 있고, 버튼을 누르면 그 버튼의 배경색을 저장하게 됩니다.
저희의 목적은 SecondViewController의 selectedColor의 값을 FirstViewController의 imageColor에 전달하는 것이 되겠네요.
class SecondViewController: UIViewController {
let redButton = UIButton()
let blueButton = UIButton()
let yellowButton = UIButton()
let resultLabel = UILabel()
var selectedColor = UIColor.white
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
@objc func colorButtonTapped(sender: UIButton) {
switch sender {
case redButton:
resultLabel.text = "빨간색을 선택하였습니다."
case blueButton:
resultLabel.text = "파란색을 선택하였습니다."
case yellowButton:
resultLabel.text = "노란색을 선택하였습니다."
default:
break
}
selectedColor = sender.backgroundColor!
}
// ...
}
1. navigationController?.viewControllers.last!
첫 번째 방법은 SecondViewController의 viewWillDisappear 시점에 네비게이션 컨트롤러에 접근해서 직접 이전 화면을 찾아내 값을 넘겨주는 방법입니다. 네비게이션 컨트롤러가 가지고 있는 viewControllers에는 지금까지 화면 이동해 온 vc들이 차곡차곡 쌓여있고, 그 마지막 값에 바로 이전 화면이 담겨있게 됩니다. viewControllers는 [UIViewController] 타입이기 때문에 as를 통해 타입 캐스팅을 해주어야 합니다.
// SecondViewController
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let vc = navigationController?.viewControllers.last! as! FirstViewController
vc.imageColor = selectedColor
}
'navigationController?.viewControllers에 자기 자신은 왜 안 담겨있는 거지?'라는 궁금증이 생겨서 print를 찍어보았습니다.
viewDidLoad()
[<Test.FirstViewController: 0x10060d170>, <Test.SecondViewController: 0x100612350>]
viewWillDisappear(_:)
[<Test.FirstViewController: 0x10060d170>]
viewDidLoad() 시점에서는 두 번째 화면도 담겨있고, viewWillDisappear() 시점에서는 자신은 사라지는 것을 알 수 있습니다.
navigationController?.viewControllers를 사용하려면 생명주기를 잘 생각하면서 사용해야 할 것 같습니다!
첫 번째 화면에서 값을 전달받을 때는 viewWillAppear() 시점에 받아온 데이터를 바탕으로 뷰를 업데이트해 주었습니다.
// FirstViewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
imageView.backgroundColor = imageColor
}
성공적으로 동작합니다.
다만, viewWillDisappear()에서 값을 전달하고 있기 때문에, 제스처를 통해 실제로 화면을 이동하지 않더라도 호출이 되어 불필요한 호출이 늘어날 수 있을 것 같았습니다.
// SecondViewController
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
let vc = navigationController?.viewControllers.last! as! FirstViewController
vc.imageColor = selectedColor
}
그래서 단 한 번만 호출되는 viewDidDisappear()에서 값을 전달하고 싶다는 생각이 들어서 코드를 옮겨보니...
오류가 납니다.
확인해 보니 viewDidDisappear() 시점에서는 네비게이션 컨트롤러가 nil이 되더라고요? 이미 화면이 사라진 이후이니 당연한 결과일 수 있겠네요. 그래서 viewDidDisappear()에서 값을 전달할 수 있는 다른 방법을 찾아보게 되었습니다.
2. 클로저
두 번째 화면에서 sendData라는 클로저를 프로퍼티로 만들고, 제가 원하는 viewDidDisappear() 시점에 데이터를 담아 함수를 실행시킵니다. swift에서 클로저는 1급 객체이기 때문에 변수나 상수에 담는 것이 가능합니다. 이 부분이 이해가 안 된다면 1급 객체에 대해 알아보세요!
// SecondViewController
class SecondViewController: UIViewController {
var sendData: (UIColor) -> Void = { _ in }
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
sendData(selectedColor)
}
}
그리고 첫 번째 화면에서 화면 이동을 할 때 sendData를 정의해 줍니다.
// FirstViewController
@objc func nextButtonTapped() {
let vc = SecondViewController()
vc.sendData = { data in
self.imageColor = data
}
navigationController?.pushViewController(vc, animated: true)
}
데이터가 바뀌지 않았을 때도 viewWillAppear()는 호출이 되기 때문에,
뷰를 업데이트하는 부분도 viewWillAppear()에서 imageColor의 didSet으로 옮겨주었습니다.
// FirstViewController
var imageColor = UIColor.white {
didSet {
imageView.backgroundColor = imageColor
}
}
3. delegate
세 번째 방법은 protocol을 이용한 방법입니다. 클로저 방식과 유사합니다.
먼저 프로토콜을 생성해 줍니다.
protocol SecondViewControllerDelegate {
func sendData(data: UIColor) -> Void
}
클로저 때와 마찬가지로 두 번째 화면에서 delegate를 프로퍼티로 갖고, viewDidDisappear()에서 delegate가 가진 sendData를 실행하여 데이터를 넘겨줍니다. 프로토콜을 변수에 담을 수 있는 이유는 프로토콜도 swift에서 1급 객체처럼 사용할 수 있기 때문입니다.
// SecondViewController
var delegate: SecondViewControllerDelegate?
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
delegate?.sendData(data: selectedColor)
}
첫 번째 화면에서 delegate를 채택해 주고, 화면 이동을 합니다. 함수의 내용은 프로토콜을 채택한 곳에서 정의해 줍니다.
// FirstViewController
@objc func nextButtonTapped() {
let vc = SecondViewController()
vc.delegate = self
navigationController?.pushViewController(vc, animated: true)
}
extension FirstViewController: SecondViewControllerDelegate {
func sendData(data: UIColor) {
imageColor = data
}
}
오늘은 이전 화면에 값을 전달하는 방법에 대해 알아보았습니다. 이외에도 다양한 방법이 있을 것 같아요. 긴 글 읽어주셔서 감사합니다! :)
'iOS' 카테고리의 다른 글
| [iOS] Result Type으로 네트워크 통신 개선해보기 (0) | 2024.08.05 |
|---|---|
| [iOS] 싱글톤 패턴 (0) | 2024.07.30 |
| [iOS] Dispatch Queue (0) | 2024.07.18 |
| [iOS] identifier를 다루는 세가지 방법 (0) | 2024.07.10 |
| [iOS] UIButton - Style: Default vs Plain (0) | 2024.06.25 |