1. UINavigation Controller
2. 화면 전환 방법
3. View Controller Life Cycle
4. 화면 간 데이터를 전달하는 방법
5. 에셋 카탈로그 (Asset Catalog)
앱이 복잡해질수록 Controller들을 잘 관리해 주어야 하며, 알맞은 타이밍에 내가 원하는 코드를 작성하는 것이 중요하다! 이를 위해서 View Controller Life Cycle
을 잘 이해해야 한다,,,
UIViewController
객체에는 View 객체를 관리하는 메서드들이 정의되어 있는데, 이 메서드들은 각자 자신들이 불려져야 하는 타이밍일 때 iOS 시스템에 의해 자동으로 호출된다. UIViewController
의 자식 클래스를 생성할 때 UIViewController
에 정의된 메서드들을 override 하여 Life Cycle
상황에 맞게 적절한 로직들을 메서드에 추가할 수 있다!
Appearing : 뷰가 화면에 나타나는 중
Apperad : 뷰가 화면에 나타나는 것이 완료된 상태
Disappeaing : 뷰가 화면에서 사라지는 중
Disappeared : 뷰가 화면에서 사라지는 것이 완료된 상태
viewWillAppear()
-> 뷰가 뷰 계층에 추가되고, 화면에 보이기 직전에 매번 호출
-> 다른 뷰로 이동하였다가 돌아오면 재호출
-> 뷰와 관련된 추가적인 초기화 작업을 이 메서드에 정의
viewDidAppear()
-> 뷰 컨트롤러의 뷰가 뷰 계층에 추가된 후 호출됨
-> 뷰를 나타낼 때 필요한 추가 작업이나
-> 애니메이션을 시작하는 작업을 이 메서드에 정의
viewWillDisappear()
-> 뷰 컨트롤러의 뷰가 뷰 계층에서 사라지기 전에 호출됨
-> 뷰가 생성된 뒤 작업한 내용을 되돌리는 작업이나
-> 최종적으로 데이터를 저장하는 작업들을 이 메서드에 정의
viewDidDisappear()
-> 뷰 컨트롤러의 뷰가 뷰 계층에서 사라진 뒤에 호출
-> 뷰가 사라지는 것과 관련된 추가 작업을 이 메서드에 정의
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
print("ViewController 뷰가 로드되었음.")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("ViewController 뷰가 나타날 것임.")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("ViewController 뷰가 나타났음.")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("ViewController 뷰가 사라질 것임.")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("ViewController 뷰가 사라졌음.")
}
이렇게~!! View Controller Life Cycle
은 View의 상태 변화에 따라 시스템에 의해 특정 메서드를 호출한다는 것을 알 수 있따!
CodePresentViewController.swift
mport UIKit
class CodePresentViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
// 전달받은 name 프로퍼티를 nameLabel에 표시.
if let name = name {
self.nameLabel.text = name
self.nameLabel.sizeToFit()
}
}
@IBAction func tabBackButton(_ sender: UIButton) {
self.presentingViewController?.dismiss(animated: true)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tabCodePushButton(_ sender: UIButton) {
// 우선 스토리 보드에 있는 뷰 컨트롤러를 인스턴스화 해 주어야 함.
// 이 메서드에 전환되는 화면의 뷰 컨트롤러 클래스 타입으로 다운 캐스팅 해줌
// => 아까 CodePush / CodePresentViewController에 정의한 name 프로퍼티에 접근 가능. 그러면 다른 화면으로 푸쉬와 프레젠트 되기 전에 네임 프로퍼티에 값을 넘겨주면 전환된 화면으로 데이터를 전달할 수 있음
guard let viewController = self.storyboard?.instantiateViewController(identifier: "CodePushViewController") as? CodePushViewController else { return } // Identifier => 전환할 스토리보드 아이디
// 옵셔널로 반환하기 때문에 가드문으로 처리해야 함.
viewController.name = "Sohee"
self.navigationController?.pushViewController(viewController, animated: true)
// 액션 함수 안에서 네비게이션 스택에 코드푸쉬뷰컨트롤러가 푸쉬 되게 코드 작성
}
@IBAction func tabCodePresentButton(_ sender: UIButton) {
// 마찬가지로 스토리 보드에 있는 뷰 컨트롤러를 인스턴스화 해 주어야 함.
guard let viewController = self.storyboard?.instantiateViewController(identifier: "CodePresentViewController") as? CodePresentViewController else { return }
viewController.name = "Sohee"
self.present(viewController, animated: true, completion: nil)
}
}
CodePushViewController.swift
mport UIKit
class CodePushViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
// 전달받은 name 프로퍼티를 nameLabel에 표시.
if let name = name {
self.nameLabel.text = name
self.nameLabel.sizeToFit()
}
}
@IBAction func tapBackButton(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController, SendDataDelegate {
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tabCodePushButton(_ sender: UIButton) {
// 우선 스토리 보드에 있는 뷰 컨트롤러를 인스턴스화 해 주어야 함.
// 이 메서드에 전환되는 화면의 뷰 컨트롤러 클래스 타입으로 다운 캐스팅 해줌
// => 아까 CodePush / CodePresentViewController에 정의한 name 프로퍼티에 접근 가능. 그러면 다른 화면으로 푸쉬와 프레젠트 되기 전에 네임 프로퍼티에 값을 넘겨주면 전환된 화면으로 데이터를 전달할 수 있음
guard let viewController = self.storyboard?.instantiateViewController(identifier: "CodePushViewController") as? CodePushViewController else { return } // Identifier => 전환할 스토리보드 아이디
// 옵셔널로 반환하기 때문에 가드문으로 처리해야 함.
viewController.name = "Sohee"
self.navigationController?.pushViewController(viewController, animated: true)
// 액션 함수 안에서 네비게이션 스택에 코드푸쉬뷰컨트롤러가 푸쉬 되게 코드 작성
}
@IBAction func tabCodePresentButton(_ sender: UIButton) {
// 마찬가지로 스토리 보드에 있는 뷰 컨트롤러를 인스턴스화 해 주어야 함.
guard let viewController = self.storyboard?.instantiateViewController(identifier: "CodePresentViewController") as? CodePresentViewController else { return }
viewController.name = "Sohee"
viewController.delegate = self // self로 초기화하면 델리게이트를 위임받게 됨.
self.present(viewController, animated: true, completion: nil)
}
func sendData(name: String) {
self.nameLabel.text = name
self.nameLabel.sizeToFit() // 텍스트에 맞게 Label 사이즈 조정.
}
}
CodePresentViewController.swift
import UIKit
protocol SendDataDelegate: AnyObject {
func sendData(name: String)
}
class CodePresentViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var name: String?
weak var delegate: SendDataDelegate? // weak 키워드, 델리게이트 패턴 사용할 땐 delegate 변수 앞에 붙여줘야함. 메모리 누수 방지를 위해서!
// 델리게이트 선언 완료 -> 델리게이트 변수는 이를 위임할 준비를 완료한 것
override func viewDidLoad() {
super.viewDidLoad()
// 전달받은 name 프로퍼티를 nameLabel에 표시.
if let name = name {
self.nameLabel.text = name
self.nameLabel.sizeToFit()
}
}
@IBAction func tabBackButton(_ sender: UIButton) {
self.delegate?.sendData(name: "HanSohee") // 데이터를 전달받은 뷰 컨트롤러에서 sendData 델리게이트 Protocol을 채택하고, 델리게이트를 위임받게 되면 채택하기 이전 화면에서 뷰 컨트롤러에서 정의된 센드데이터함수가 실행됨.
self.presentingViewController?.dismiss(animated: true)
}
}
ViewController.swift
기존 코드에 추가
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// SeguePushViewController로 다운 캐스팅
if let viewController = segue.destination as? SeguePushViewController {
viewController.name = "sohee"
}
}
SeguePushViewController.swift
import UIKit
class SeguePushViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var name: String?
// 전환되는 화면에 값을 전달하기 위한 젤 좋은 위치 : 전처리 prepare 메서드(오버라이드 하면 세그웨이를 실행 직전에 시스템에 의해 자동 호출됨)
override func viewDidLoad() {
super.viewDidLoad()
if let name = name {
self.nameLabel.text = name
self.nameLabel.sizeToFit()
}
}
@IBAction func tapBackButton(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
/*
self.navigationController?.popToRootViewController(animated: true) // Back Button을 눌렀을 때 네비게이션 컨트롤러의 첫 화면인 Root View Controller로 이동하게 됨. */
}
}
Xcode에서 프로젝트를 생성하면 Assets.xcassets
폴더가 자동으로 만들어진다. 이 폴더는 앱에서 사용될 다양한 에셋을 관리해 주는 역할을 한다.
Image Set
에 1x, 2x, 3x 세 가지 이미지 리소스를 추가하는 이유는 다양한 해상도에서 화질이 깨지지 않는 이미지 리소스를 사용하기 위해서이다!
ViewController.swift
import UIKit
class ViewController: UIViewController, LEDBoardSettingDelegate {
@IBOutlet weak var contentsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.contentsLabel.textColor = .yellow
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let settingViewController = segue.destination as? SettingViewController {
settingViewController.delegate = self
settingViewController.ledText = self.contentsLabel.text
settingViewController.textColor = self.contentsLabel.textColor
settingViewController.backgroungColor = self.view.backgroundColor ?? .black
}
}
func changedSetting(text: String?, textColor: UIColor, backgroundColor: UIColor) {
if let text = text {
self.contentsLabel.text = text
}
self.contentsLabel.textColor = textColor
self.view.backgroundColor = backgroundColor
}
}
settingViewController.swift
import UIKit
protocol LEDBoardSettingDelegate: AnyObject {
// 설정값을 전달하는 함수
func changedSetting(text: String?, textColor: UIColor, backgroundColor: UIColor)
}
class SettingViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var yellowButton: UIButton!
@IBOutlet weak var purpleButton: UIButton!
@IBOutlet weak var greenButton: UIButton!
@IBOutlet weak var blackButton: UIButton!
@IBOutlet weak var blueButton: UIButton!
@IBOutlet weak var oragneButton: UIButton!
weak var delegate: LEDBoardSettingDelegate?
var ledText: String?
// 설정된 값을 델리게이트에 정의한 changedSetting 메서드에 전달하기 위해 이 텍스트컬러와 백그라운드컬러 프로퍼티를 추가하겠음
var textColor: UIColor = .yellow
var backgroungColor: UIColor = .black
override func viewDidLoad() {
super.viewDidLoad()
self.configureView()
}
// 다시 설정 뷰로 돌아갔을 때도 설정한 값들이 유지되도록 하는 함수
private func configureView() {
if let ledText = self.ledText {
self.textField.text = ledText
}
self.changeTextColor(color: self.textColor)
self.changeBackgroundColorButton(color: self.backgroungColor)
}
@IBAction func tabTextColorButton(_ sender: UIButton) {
if sender == self.yellowButton
{
self.changeTextColor(color: .yellow)
self.textColor = .yellow
}
else if sender == self.purpleButton
{
self.changeTextColor(color: .purple)
self.textColor = .purple
}
else if sender == self.greenButton
{
self.changeTextColor(color: .green)
self.textColor = .green
}
}
@IBAction func tabBackgroundColorButton(_ sender: UIButton) {
if sender == self.blackButton
{
self.changeBackgroundColorButton(color: .black)
self.backgroungColor = .black
}
else if sender == self.blueButton
{
self.changeBackgroundColorButton(color: .blue)
self.backgroungColor = .blue
}
else if sender == self.oragneButton
{
self.changeBackgroundColorButton(color: .orange)
self.backgroungColor = .orange
}
}
@IBAction func tabSaveButton(_ sender: UIButton) {
self.delegate?.changedSetting(
text: self.textField.text,
textColor: textColor,
backgroundColor: backgroungColor
)
self.navigationController?.popViewController(animated: true)
}
private func changeTextColor(color: UIColor) {
self.yellowButton.alpha = color == UIColor.yellow ? 1 : 0.2
self.purpleButton.alpha = color == UIColor.purple ? 1 : 0.2
self.greenButton.alpha = color == UIColor.green ? 1 : 0.2
}
private func changeBackgroundColorButton(color: UIColor) {
self.blackButton.alpha = color == UIColor.black ? 1 : 0.2
self.blueButton.alpha = color == UIColor.blue ? 1 : 0.2
self.oragneButton.alpha = color == UIColor.orange ? 1 : 0.2
}
}