본 프로젝트는 해외 유튜버 "Lets Build that App" 님의 영상을 참고하여 주요 내용만 요약했습니다.
StoryBoard
와 이별하기func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.makeKeyAndVisible()
self.window?.rootViewController = UINavigationController(rootViewController: HomeController())
return true
}
StoryBoard
의 품을 벗어나, 프로그래밍적으로 레이아웃을 구현하기 위해 가장 먼저 할 일은 AppDelegate
를 설정해주는 것이다. 그 중 가장 중요한 작업은 AppDelegate
클래스의 내부 프로퍼티로 지정되어있는 window
객체에 값을 새로 할당해주는 것이다.
window
객체는 일반적인 모바일 앱에선 하나만 동작하며, root뷰컨트롤러
를 가리키고 있는데, 보통 외부 이벤트를 전달받아서 뷰에 전달하는 역할을 하는 객체다. window
는 따로 만들지 않아도 기본적으로 appDelegate
내에 선언되어있는데, storyboard
가 실행될 때 내부적으로 window
객체에 값이 지정되는 방식이다.
나는 스토리보드를 사용하지 않기 때문에, 이를 새로운 인스턴스로 덮어씌워준다. 방법은 간단하다. 위 코드와 같이 didFinishLaunchingWithOptions
함수에서 window
객체를 새로 정의해고 rootViewController
를 설정해주면 된다.
이 때 makeKeyandVisable
은 해당 윈도우를 키윈도우(의미는 잘 모르겠지만, 메인으로 사용하는 윈도우라는 뜻인 것 같다.)로 지정하는 역할을 한다.
TableViewController
구현원래 "Lets Build That App" 채널의 강의에선 CollectionsViewController
를 사용해서 세로로 구현하지만, 나는 대신 TableViewController
를 사용하기로 했다. TableViewcontroller
를 사용할 때, 핵심적으로 사용하는 메서드는 다음과 같다.
참고로 아래의 함수들은 모두 UITableViewController
클래스에 정의되어있고, 일반적으로 ViewController
에 이 클래스를 상속하게 한 뒤 아래 함수들을 Override
한다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {}
각 Section에 몇개의 행이 들어갈 것인지 반환한다.
func tableView(\_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ }
적절한 `TableViewCell`을 반환한다. 반드시 위에서 정의한 숫자만큼 반환해줘야한다.
func tableView(\_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {}
각 인덱스에 해당하는 행의 높이를 정의한다.
addConstraint
를 통해 오토레이아웃 구현스토리보드에선 몇번의 클릭만으로 추가할 수 있는 AutoLayout
이, 코드로 구현하려고 하니, 처음엔 조금 복잡하다고 느껴졌다. 코드로 오토레이아웃을 구현할 때의 장점은 여러가지 있겠지만, 가장 큰 것은 협업에 용이하다는 것이다.
즉 처음 짤 땐, 까다롭지만 구현된 코드를 확인할 땐, 알아보기가 나름 편리하다. (들은 말인데 개인차가 있을 수 있다고 본다.)
두번째는 내가 직접 경험해보지는 못했지만, git
을통한 협업에서 충돌위험이 적다고 들었다. 스토리보드의 오토레이아웃은 보통 내부적인 xml코드
로 자동생성되는데, 이 때 다른사람의 코드와 merge
시 충돌이 발생하면 알아보기 좀 까다롭다고 한다.
어쨌거나 오토레이아웃의 핵심함수는 다음과 같다.
말그대로 제약조건을 하나 추가하는 함수와 여러개를 추가하는 함수이다. 코드에서 제약조건은 NSLayoutConstraint
라는 객체로 구현되어있다.
addConstraint(NSLayoutConstraint(item: subTitleLabel, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .left, multiplier: 1, constant: 0))
생긴것 복잡하지만 자세히보면 스토리보드로 구현했을 때와 상당히 유사한 형태를 갖는다. 위 제약조건을 해석하면 "subTItleLabel
의 left와 titleLabel
의 left가 같은 값을 갖는다" 정도 되겠다. 만약 같지않고 특정 길이만큼 차이를 주고싶으면 constant
인자를 건들면 된다.
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-16-[v0]-8-[v1(44)]-16-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0" : thumbnailImageView, "v1" : profileImageView]))
적절한 인자를 받아서 [NSLayoutConstraint]
배열을 반환하는 함수. 특이한점은 withVisualFormat
이라는 인자로 독특한 표현식의 문자열을 받는다는 것이다. 다행히 따로 찾아보지 않아도 나름 보다보면 이해가 가는 모양을 하고 있다.
이 때, 해당 표현식이 여러가지 제약조건을 반환할 수 있으므로 여러개의 Constraint 배열을 반환하며, 그렇기 때문ㅇ에 당연히 addContraints
함수를 통해 추가하게 된다.
위의 표현식을 해석하자면, V(vertical)
이므로 세로방향의 제약조건을 정의하고 있고, 컨테이너와 16만큼 떨어진곳에 v0
이 위치하고, 그 아래에 8만큼의 거리에 44의 높이를 갖는 v1
가, 그리고 16만큼 아래에 컨테이너의 바닥이 존재한다는 뜻이 된다.
v1
과 v2
가 무엇인지에 대해서는 마지막 인자인 views
에 정의되어 있다. 대충 v0
의 높이만 동적으로 표현된다고 해석하면 될 것 같다.
중요한 부분을 빼먹을 뻔 했다. 위와같이 코드로 제약조건을 설정해주려면, 가장 먼저 UIView
를 상속하는 객체의 위 속성을 False
로 설정해줘야만 한다. 여기서 다 설명하긴 길어질것같지만, 저 기다란 이름 안에 답이 들어있다.
앱은 AutoresizingMask
에서 지정된 내용을 translate
하여 Constraints
를 정의한다. 이 때의, 제약조건은 완전하게 객체의 위치를 고정해버리기 때문에 추가적인 Constraints
의 추가를 막아버리게 된다. 그렇기 때문에 우리가 Constraint
를 추가하려면 반드시 저 속성을 명시적으로 false
처리 해줘야한다.
참고로 위 속성은, InterfaceBuilder
로 만든 객체의 경우 기본값이 false
지만, 프로그래밍적으로 만들면 기본적으로 true
값이 들어간다.
자세한 내용은 Zedd님의 포스팅을 참조했다.