본문 바로가기

Mobile/iOS

[iOS] 이전 화면으로 값을 전달하는 방법

반응형

Description

동기방식으로 이전 화면으로 값을 전달하는 방법에 대해 설명하고자 합니다.
(iOS 13 이후 버전에서는 Present Modally 방식에서 full screen이 아닌 경우 실행되지 않는다고 합니다. 해당 이슈에 대해서는 아래에서 서술하도록 하겠습니다.)

동기방식은 저장소를 이용하지 않고 직접 값을 이전 화면으로 전달하는 것이라고 할 수 있습니다. 두 번째 View Controller에서 첫 번째 View Controller로 값을 직접 전달하는 방법입니다.

우선 프로젝트를 생성한 후에 첫 번째 ViewController 와 두 번째 View Controller 인 FormViewController를 아래 그림과 같이 만들어 줍니다. 첫 번째 ViewController에는 Label과 Button을 하나씩, 두 번째에는 그림상으론 잘 안보이지만 TextField와 Button을 만들어 줍니다.

첫 번째 ViewController의 코드는 아래와 같습니다.

// ViewController.swift

import UIKit

class ViewController: UIViewController {
	
    // 아웃렛 변수 선언! 해당 아웃렛이 두 번째 뷰 컨트롤러에서 받아오는
    // 텍스트를 화면에 표시됩니다.
    @IBOutlet var resultLabel: UILabel!
    
    // 두 번째 뷰 컨트롤러에서 보내오는 텍스트를 담을 그릇입니다.
    // viewWillAppear에서 resultLabel로 내용물을 옮겨 담을 겁니다.
    var paramLabel: String?
    
    // viewWillAppear가 실행되면서 두 번째 뷰 컨트롤러에서 받아온 변수를
    // 확인하고 옮겨담습니다.
    override func viewWillAppear(_ animated: Bool) {

        if let labelText = paramLabel {
            resultLabel.text = labelText
        }
    }
}

viewWillAppear는 뷰 컨트롤러 라이프 사이클상 처음 실행되거나 다음 화면으로 넘어갔다가 다시 등장하기 시작하는 상태로 바뀌는 동안 호출됩니다. 따라서 처음 앱을 실행할 때 한 번, 다음 화면으로 넘어갔다가 다시 돌아올 때 한 번 실행될 수 있습니다.

처음 앱을 실행할 때는 paramLabel의 값은 nil 상태로 if 문에서 옵셔널 바인딩에 실패하고, 두 번째에는 값을 받아와 옵셔널 바인딩에 성공해 resultLabel의 text를 바꿔줍니다.

만들어 두었던 Button은 Action Segue로 우클릭 후 드래그해서 두 번째 ViewController를 지정하고, Present Modally로 설정합니다.

아래는 두 번째 ViewController 입니다. 우선 TextField를 아웃렛 변수로 선언합니다. 다음으로 Button을 대상으로 액션 메소드(IBAction)를 선언합니다.

// FormViewController.swift
import UIKit

class FormViewController: UIViewController {
    @IBOutlet var labelText: UITextField!
    
    @IBAction func onSubmit(_ sender: Any) {
        let preVC = self.presentingViewController
        guard let vc = preVC as? ViewController else {
            return
        }
        
        vc.paramLabel = self.labelText.text
        
        self.presentingViewController?.dismiss(animated: true)
    }
}

우리는 두 번째 뷰 컨트롤러에서 입력받은 값을 첫 번째 뷰 컨트롤러로 옮기고자 합니다. 따라서 첫 번째 뷰 컨트롤러의 인스턴스를 찾아와야 합니다. 그 역할을 하는 코드가 바로 아래 코드입니다.

        let preVC = self.presentingViewController
        guard let vc = preVC as? ViewController else {
            return
        }

다음으로 vc 로 할당된 인스턴스에 앞서 선언해두었던 paramLabel에 값을 할당합니다. self.labelText.text로 입력받은 값을 받아 paramLabel에 할당하고, dismiss 메소드로 화면을 이전으로 복귀합니다.

이때 첫 번째 뷰 컨트롤러의 viewWillAppear가 호출되면서 값을 바꿔주게 됩니다.

Issue

하지만 막상 실행해보면 값이 바뀌지 않고 Label 로 남겨져 있는 것을 확인할 수 있습니다. 그 이유는 iOS 13 이후 Present Modally 방식이 full Screen이 되지 않은 상태에서는 presentingViewController가 첫 번째 뷰 컨트롤러의 viewWillAppear 메소드를 호출하지 못한다고 합니다.

iOS 13 부터 프레젠트 메소드를 사용해 화면을 이동하면 해당 뷰 컨트롤러가 전체 화면으로 뜨는 것이 아니라 부모 뷰가 뒤쪽에 남아있게 됩니다. 이를 레이어링 디자인(Layering Design)이라고 합니다. iOS 12까지는 뷰 컨트롤러의 속성 modalPresentationStyle의 기본값이 fullScreen이었던 반면, iOS 13부터는 automatic으로 바뀌었습니다. 따라서 해당 속성을 설정하지 않고 화면을 전환 처리하면 레이어링 디자인이 적용되어, 원래의 뷰 컨트롤러가 뒤 쪽에 남아있게 됩니다.

https://stackoverflow.com/questions/51089058/swift-viewwillappear-not-being-called-after-dismissing-view-controller

 

Swift viewWillAppear not being called after dismissing view controller

I am presenting a view controller from a view controller called HomeController like so: let viewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginController") as!

stackoverflow.com

Solution

Present Modally 방식을 선택했을 때, Presentation을 full screen으로 바꿔주어야 해결이 됩니다. 첫 번째 뷰 컨트롤러와 두 번째 뷰 컨트롤러를 연결해주는 Action Segue를 클릭한 후 Attributes inspector 항목으로 들어가서 Presentation을 Full Screen 으로 변경해줍니다.

그러면 두 번째 뷰 컨트롤러의 모습이 아래와 같이 바뀝니다. 아래 그림 같이 바뀌었으면 다시 실행해보시기 바랍니다. 그러면 원하던 대로 Label 이 입력한 값으로 바뀌게 됩니다.

 

::코드로 변경하는 방법::

// navigationController에서 코드로 설정하는 방법
let viewController = self.storyboard?.instantiateViewController(
	withIdentifier: "LoginController"
    ) as! LoginController

let navigationController: UINavigationController = UINavigationController(
	rootViewController: viewController
    )

navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true, completion: nil)

// 일반 viewController에서 코드로 설정하는 방법
let view = self.storyboard?.instantiateViewController(withIdentifier: "StoryBoardId")
view.modalPresentationStyle = .fullScreen
self.present(uv!, animated: true)
반응형