[swift] 다른 페이지로 직접 데이터 전달하기
1️⃣ 목표
- 첫번째 뷰에서 두번째 뷰로
데이터 를 전달하는 방법에 대해 알아볼 예정입니다. - 먼저 첫번째 뷰에서 버튼을 클릭하면 present메서드를 이용하여 두번째 뷰가 열림과 동시에 데이터를 전달하도록 구현했습니다.
- 단, User Defauls**, 커스텀 plist, DB를 이용하지 않습니다.
- 뷰컨트롤은 스토리보드에서 구현했기 때문에 다음과 같이 두번째 뷰컨트롤러를 불러왔습니다.
2️⃣ 최초로 열어줄 페이지에 데이터 전달하기
- 부제목이 “최초로 열어줄 페이지”라고 썼지만 사실 한페이지위에서
다른페이지 를 열어줄 때를 뜻합니다.다른페이지 에서 dismiss()와 같은 메서드로 되돌아가면 자동으로 인스턴스 메모리를 해제시키기 때문에 또다시다른페이지 를 열더라도 최초로 열어준 것 처럼 동작할 것 이기 때문입니다. - 그리고 다음과 같이 세가지 방법으로 선언된 변수를 비교하는데 있어서 중요한 요소는 최초로 페이지를 열었을 때만 동작하는 함수인 viewDidLoad()이기 때문입니다.
(1) 스토리뷰 Label: 스토리뷰에서 생성해준 라벨
(2) 코드Label: 코드로 만든 Label. viewDidLoad()에서 UILabel인스턴스가 할당.
(3) 클래스코드Label: 코드로 만든 Label. 클래스레벨에서 UILabel인스턴스가 할당.
(1) 단순하게 전달하기
- 먼저 다음과 같이 첫번째 뷰컨트롤러에서 데이터를 전달해준 뒤 두번째 뷰컨트롤러를 present메서드를 이용하여 열어줬습니다.
@IBAction func nextPageBtn(_ sender: Any) {
/* 중략 */
vc.storyLabel?.text = "바뀐 스토리 Label"
vc.codeLabel?.text = "바뀐 코드 Label"
vc.classLevelCodeLabel.text = "바뀐 클래스코드 Label"
self.present(vc, animated: true, completion: nil)
}
- 아쉽게도 세가지 Label 모두 값이 변경되지 않았습니다.
- 비교를 하기 이전에 실수한점이 있었습니다. 두번째 뷰컨트롤러에서 viewDidLoad()함수에서 코드로 구현했던 2번째, 3번째 라벨의 텍스트를 변경해 줬습니다. present함수가 호출된 이후에 화면이 로드된다는 것을 생각해보면 당연한 결과입니다.
- 다음과 같이 텍스트변경코드를 주석처리하고 다시 실행해봤습니다.
- 이번에는 3번째 Label만이 값이 변경되었습니다.
- 다시한번 두번째뷰컨트롤러에서 변수가 선언된 모습을 보겠습니다.
- 2번째 Label은 클래스레벨에서 타입만 지정되어있을 뿐 viewDidLoad()메서드 안에서 인스턴스를 할당받습니다. 결국 이전에 실패한 이유와 동일합니다.
- 반면 3번째 Label은
클래스레벨 에서 선언되었습니다. 즉, 다른 언어의 클래스문법과 동일하게 첫번째 뷰컨트롤러에서 두번째 뷰컨트롤러의 인스턴스를 생성하는 시점에서 인스턴스변수들이 생성됩니다. - 스토리보드로 직접 만들어준 첫번째 Label은 왜 바뀌지 않았을까요?
(2) 스토리보드적용시점 생각해보기
- 스토리보드로 직접 만들어준 첫번째 Label 또한 클래스레밸에서는 인스턴스 생성없이
UILabel!
로 타입선언만 되어있기 때문에 당연한 결과일 수 도 있습니다. - 하지만 swift언어에서 스토리보드 개념을 처음 접한 입장에서 스토리보드가 언제 적용되는지 알 수 없었습니다. 타입만 선언되었지만 스토리보드만의 특수한 힘(?)으로 인스턴스 생성과정에서 적용될 것 같다는 생각을 했습니다. 그렇기 때문에 Label이 바뀌지 않은 이유를 다음과 같이 2가지로 생각할 수 있습니다.
- UILable()인스턴스는 생성됐지만 인터페이스설정이 로드된 이후에 적용되기 때문에 텍스트가 덮혀씌워졌기 때문
- 스토리보드적용은 뷰가 로드된 이후에 적용되기 때문
- 둘중 맞는 이유를 확인하는 방법은 간단합니다. 다음과 같이 데이터를 적용하는 시점에서
!
키워드를 적용해서 실행해보면 됩니다.
- 위의 오류를 통해 두번째 컨트롤뷰를 생성한 이후에도 스토리보드를 통해 생성된 아웃렛변수에 값이
nil
임을 알 수 있습니다. - 결론적으로 스토리보드에서 만들어준 뷰는 뷰가 로드되는 시점에 초기화된 것 입니다.
(3) 라이프사이클, 코드의 절차적인 처리 우선순위?
- 지금까지의 과정을 통해 swift언어는 라이프사이클, 스토리보드 적용시점등등을 고려해야해서 코드를 작성해 줘야합니다.
- 그렇다면 코드의 절차적 처리의 특성을 이용해서 present메서드로 뷰를 호출한 후에 데이터를 변경해준다면 어떻게 될까요?
@IBAction func nextPageBtn(_ sender: Any) {
/* 중략 */
self.present(vc, animated: true, completion: nil) // 먼저 호출
vc.storyLabel?.text = "바뀐 스토리 Label"
vc.codeLabel?.text = "바뀐 코드 Label"
vc.classLevelCodeLabel.text = "바뀐 클래스코드 Label"
}
- 놀랍게도 호출 순서를 바꿔준 것 만으로 모든 라벨의 정상적으로 변경되었습니다.
- 하지만 이것이 코드의 절차적인 처리가 라이프 사이클보다 우선순위기 때문이라고 생각하기는 힘들 것 같습니다. 만약 우선순위 비교가 가능하다면 좀 전에
viewDidLoad()메서드호출 -> 데이터전달 -> 뷰로드
순으로 적용된 것을 설명 못합니다. - 오히려 “present를 이용한 뷰를 로드하는 과정이 비동기로 처리되는데 라이프사이클단계중 viewDidLoad와 view(Will)Appear사이에 데이터가 변경되었기 때문”이라고 생각하는게 맞을 수도 있을 것 같습니다. 아직 이 부분에 대해서는 좀 더 공부가 필요할 것 같습니다.
(4) 베스트 프렉티스
- 개인적인 입장일 수도 있지만 단순히 present를 먼저 호출해서 처리하는 것은 안심할 수 없을 것 같습니다. 또한 실수로 present메서드를 더 늦게 호출할 수도 있습니다.
- 하지만 각 클래스의 인스턴스 변수들은 생성직후 생성된 다는 것은 확실합니다.
- 이점을 이용하여 다음코드와 같이 값을 대신 받아줄 인스턴스 변수를 만들어준 뒤 viewDidLoad()함수에서 값을 옮겨주는 방법을 쓰면 안전하게 사용할 수 있을 것 같습니다.
- 추가로 스토리보드에서 생성한 아웃렛 변수는
private
키워드를 붙여주면 외부에서 참조를 못해 좀 더 안전하게 사용할 수 있습니다. - 코드로 구현한 라벨의 경우도 동일하게 사용하거나, 클래스레벨에서 인스턴스를 생성해주는 방식으로 사용하면 될 것같습니다.
3️⃣ 반대 페이지로 데이터 전달할 때
- 이번에는
첫번째페이지 -> 두번째페이지 -> 첫번째페이지
의 순서로 돌아오면서 첫번째 페이지로 데이터를 전달하는 것에 대해 생각해보겠습니다.
- 이 과정에서 첫번째 컨트롤러의 viewDidLoad()는 다시 호출되지 않습니다. 그렇기 때문에 viewwillAppear()에서 값을 옮겨줘야 합니다.
- 이전에 이와 관련된 포스트가 있기 때문에 자세한 설명은 생략 하겠습니다.
관련 포스트👉🏻👉🏻👉🏻 present, dismiss를 이용한 페이지 전환에서 viewWillAppear메서드가 호출이 안되는 문제 해결하기
- 그리고 이번경우에는 첫번째 뷰가 한번 호출된 후 이기 때문에 아웃렛변수에 직접 값을 대입해줘도 됩니다.
- 하지만 통일성 있는 코드를 위해서는 동일하게 임시 변수를 선언하여 사용하고 라이프사이클관련 함수를 이용하는 것이 좋을 것 같다고 생각합니다.