UIViewController

역할

  • 데이터의 변경에 대한 응답을 View에 Update한다.
  • View와 User와의 상호 작용에 응답한다.
  • View의 크기 조정 및 Layout 관리
  • 다른 객체(viewController 포함)를 조정한다.

간략하게, View와 밀접하게 연결되어 있다.

class UIViewController : UIResponder

또한, UIViewController는 UIResponder를 상속받았다. UIResponder에 대한 이야기는 밑에서 다시 할 것이다.

우선은 SuperView/SubViews 들과의 응답자 체인으로 엮여있다.
이것은 즉

1. 자신의 event의 처리를 SuperView에 passing할 수 있다.

2. 자신의 SubView들의 event를 자신이 처리할 수 있다.

가 가능하다.

 

대부분의 애플리케이션 내에는 여러개의 ViewController가 존재하고 사용된다.

어떤 VC(ViewController)는 리스트를 나타내는 테이블뷰 하나만을 관리할 수 있고,

다른 VC는 리스트, 회원창, 친구목록 등 다양한 View를 관리할 수 있다.

 

UIResponder

앞선 블로그 코코아 프레임워크에서 말했듯, 이 또한 클래스로 NSObject를 상속받는다.

class UIResponder : NSObject
  • Event 처리에 중요한 역할을 한다.
  • 여러 메소드와 next라는 프로퍼티를 사용하여 부모에게 event를 전달하거나, 동시 처리도 가능해진다.
    • touchesBegan
    • touchesMoved
    • touchesEnded
    • touchesCancelled
  • next라는 프로퍼티를 사용하여 다음 responder에게 이벤트를 전달 가능

responder 라고는 주로 becomeFirstResponder() / resignFirstResponder() 를 사용한 기억이 많다.

TextField를 사용하며 키보드를 올리고 내리고 하기 위해 사용해왔지만, 정확히 어떻게 동작하는 지에 대해서는 생각해보질 못했다.

키보드가 올라오기 위해서는 TextField가 window의 첫번째 Responder가 되어야 한다.

메소드이름 그대로 TextField가 첫번째 Responder가 되기 때문에, 키보드가 올라오며, 반대로 Resign하면 내려간다.

 

View 관리

  • 각 VC는 View들이 포함되는 RootView를 관리한다.
  • RootView는 주로 나머지 뷰 계층 구조에서 Container 역할을 한다.
  • VC의 View들은 lazy하게 메모리에 Load된다.
    • 선언한다고 메모리에 적재되는 것이 아닌, 사용할 때(즉, 호출될 때) 메모리에 적재된다.
    • VC 생성 시의 생명주기

StoryBoard를 사용할 때

스토리보드 내에서 1. VC로 사용할 View 와 2. VC 를 지정(연결)한다.

주로 간단한 화면 구조의 애플리케이션이나 혼자서 작업할 때 사용되는 방법이다.

// 스토리보드를 통한 VC 초기화
let newVC = self.storyboard?.instantiateViewController(withIdentifier: "VC이름")

 

Nib  파일을 사용할 때

Nib파일을 사용하여 VC에 대한 View를 지정한다.

스토리보드를 사용하지 않기에, 스토리보드 내에서 가능했던 VC간의 Segue나 관계를 사용/정의할 수 없다.

초기화 방법 : init(nibName:bundle:)을 사용해서 초기화한다.

 

이후의 View들의 Load

VC의 생명주기 메소드들 중 가장 첫 단계인 loadView() 메소드를 사용하여 나열된다.

이후에 DidLoad, willAppear, DidAppear ... 

 

VC의 크기(RootView의 크기)

VC의 RootView는 항상 할당된 공간에 크기가 맞춰진다. 또한 RootView 내에 있는 SubView들은 Interface Builder를 사용하여 각 뷰와 SuperView와의 관계를 통해 크기, 위치 등이 지정된다.

 

구현 방식

1. AutoLayout - View 사이의 관계, 제약조건 등을 통하여 크기, 위치 등을 설정하며, 기기 크기에 대해 대응 가능

2. Frame - CGSize(크기), CGRect(위치)를 직접 할당한다.

 

 

View 관련 Notification

위에 사진으로 나타낸 VC의 생명주기의 메소드이름을 통해 View들이 어떤 상태인지를 알 수 있다.

반대로 말하면, View들의 현재 상태를 VC를 통해 알 수 있다.

 

StoryBoard vs Code

iOS 프로젝트를 처음 그리고 초반에 진행을 할 때는 무조건 스토리보드로 UI를 작성하고 ViewController와 연동하였다.

why?

쉬우니까! 간편하니까! 내가 작성한 것이니 내가 제일 잘 아니 헷갈리지도 않으니까!

BUT!

혹시라도 2명이상과 프로젝트를 진행하게 된다면 스토리보드의 가장 큰 단점이 보인다.

  • 하나의 스토리보드로 함께 작업시에 충돌할 가능성이 진부함
  • 스토리보드를 나눠서 작업을 해도 굳이 하나의 스토리보드에 들어갈 화면들이 나눠짐 -> 이후에 다시 합쳐야 하는 불편함
  • 이후에 다시 합치는 과정에서도 Ctrl+C/V를 하더라도 다시 AutoLayout을 위한 Constraint를 지정해주는 것이 어렵다!
    • 물론 정리된 문서가 있으면 금방할 수 있다고 생각했지만, 아니었다...
    • 내가 UI Component들에 지정한 이름을 보고 이게 이거였나? 이게 이거였겠지? 하면서 헷갈리는 순간 시간이 굉장히 딜레이됐다.
    • 그래서 이후로는 UI Component들의 이름도... 어떠한 체계로 명확히 작성해야한다는 것을 꺠달았따...

TabBar

예제로 진행할 탭바 StoryBoard

목표 : 이처럼 TabBarVC를 가지고 있고 2개의 VC를 삽입하여 간단한 TabBar를 구현하는 것!!

 

class TabBarViewController: UITabBarController{
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let profileVC = ProfileViewController()
        let vc2 = VC2()
        
        let icon1 = UITabBarItem(title: "프로필", image: UIImage(systemName: "person.circle"), selectedImage: UIImage(systemName: "person.circle"))
        let icon2 = UITabBarItem(title: "VC2", image: UIImage(systemName: "person.circle"), selectedImage: UIImage(systemName: "person.circle"))
        
        profileVC.tabBarItem = icon1
        vc2.tabBarItem = icon2
        let vcArr = [profileVC, vc2]
        self.setViewControllers(vcArr, animated: true)
    }
        
}

 

  1. TabBarVC라는 ContainerVC에 들어갈 ProfileVC, VC2를 생성한다.
  2. 각각의 VC들이 TabBar에서 보일 Item을 만든다.
  3. 2번에 이어서 붙여준다.
  4. VC들을  TabBarVC의 VC들로 지정한다.

이과정을 진행하면 문제없이 시뮬레이터에서 실행이될 것이다.

ProfileVC
VC2

보라! 정상적으로 TabBar가 구현되었다!!

응??? 근데 뭔가 이상하다??

스토리보드에 있는 ProfileVC

스토리보드에 있는 ProfileVC에는 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ라고 적힌 Label이 있는데??

아! ProfileVC에 Outlet으로 가져오면 되겠구나!

 

class ProfileViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.label.text = "aa"
    }


}

하고 실행!

Outlet을 지정한 후의 ProfileVC

엥? 아직도 안뜨네? 거기다가

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value 라는 에러도 나타났다.

이 에러는 타입에 !를 선언해주고 nil에 접근했을 때 나타나는 건데....

스토리보드에서 profileVC와 ProfileViewController를 연결하고 Outlet에 Label을 연결했고! 아무 문제가 없어야하는데 문제가 발생??




해결 방법

guard let profileVC = self.storyboard?.instantiateViewController(withIdentifier: "ProfileVC") else { return }

위 코드처럼 바꿔주면된다.

해석하자면

  1. self가 존재하는 스토리보드에 있는 VC들 중에 "ProfileVC"라는 ID를 가진 VC를 가져온다.

스토리보드에 UI Component들을 작업하고 그 컴포넌트들을 코드로 생성한 VC에서 사용하고 싶으면 이런 방법을 써야한다.

물론 스토리보드에 UI Component들을 넣지 않고 코드로

let label = UILabel() 로 선언한 후에

text와 constraint들을 지정해 줘도 되지만... 아직까지는 그렇게 익숙하지 않으니 이 방법도 알아보게 되었다.

+ Recent posts