오늘 공부한 UIKit - 스토리보드 없이 코드로 constraint 지정하기
스토리보드를 사용하지 않고 Constraint를 코드로 부여하기
뷰를 구성하는 요소들의 개수나 위치가 모두 정해지지 않은 경우, 뷰를 구성하는 순간 그 안의 요소들을 생성하고 각각의 constraint를 지정해줘야 할 땐 스토리보드를 사용하기가 애매하다.
나는 받아오는 데이터의 개수에 따라 라벨을 만들고, 각각의 위치를 지정해주고 싶었기 때문에 코드로 constraint 지정하는 방식을 시도하기로 했다.
과정
스토리보드를 사용할 땐, 내가 원하는 UI 요소를 드래그 앤 드랍 한 뒤 해당 요소의 어트리뷰트를 설정해주고, Xcode 하단의 constraint 설정 버튼을 활용하거나, ctrl 드래그를 이용해 제약조건을 설정 해 주면 된다.
코드로 짤 때 역시 이 과정을 똑같이 해 주면 된다.
- UI 요소 생성
- 어트리뷰트 설정
- 제약조건 추가
코드
1. UI 요소 생성
class MyViewController: UIViewController {
var myLabel: UILabel = {
var label = UILabel()
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
}
}
myLabel
이라는, 현재로선 위치도, 너비와 높이도, 내용도 없는 빈 라벨을 생성했다.
이 라벨이 우리 화면에 제대로 보이기 위해선 너비와 높이를 갖고, 특정 위치를 갖고, 색이나 내용을 가져야 한다. 이를 위의 과정 2, 3번에서 해 주면 된다.
2. 어트리뷰트 설정
라벨을 생성할 때 기본적인 어트리뷰트를 설정해줘도 되고, 함수로 따로 만들어줘도 된다. 나는 함수로 만들어 주었다.
class MyViewController: UIViewController {
...
private func setAttribute() {
myLabel.text = "2unbini"
myLabel.textAlignment = .center
myLabel.layer.borderColor = UIColor.systemYellow.cgColor
myLabel.layer.borderWidth = 5.0
myLabel.backgroundColor = UIColor.systemTeal.withAlphaComponent(0.2)
}
}
생성해준 라벨에 텍스트, 배경색, 겉 라인을 추가해줬다. 이렇게 하고 viewDidLoad에 해당 함수를 넣으면..!! 아무 것도 안 뜬다.
안 뜨는 이유 1 - 라벨이 선언만 되어 있다.
스토리보드 상에서 뷰 컨트롤러에 어떤 UI 요소를 추가하면 자동으로 해당 뷰에 그 요소가 들어가게 된다. 하지만, 코드상에선 이 작업을 추가로 해 주어야 한다.
func addSubview(_ view: UIView)
인자로 받는 뷰를 리시버의 서브 뷰 리스트에 추가하는 함수다. 즉, 추가하고자 하는 요소를 인자로 넣어, 추가하고자 하는 뷰에 addSubview
해 주면 된다.
나는 myLabel을 루트 뷰의 자식 뷰로 넣어주고 싶기 때문에 다음과 같은 문장을 추가했다.
view.addSubview(myLabel)
안 뜨는 이유 2 - 라벨의 너비와 높이가 설정되어있지 않다.
그래도 안 뜬다. 왜냐면, 내가 추가한 라벨은 너비와 높이를 갖고 있지 않기 때문이다.
이를 해결하기 위한 방법은 세 가지가 있다. 뷰를 생성할 때 frame을 설정하거나, 어트리뷰트 설정에서 width와 height를 지정해주거나, 제약조건을 줄 때 widthAnchor, heightAnchor의 조건을 지정해주면 된다.
어떻게 하든 제약조건이 짱쎄기 때문에 일단 확인하기 위해 생성 부분을 수정 해 주었다.
class MyViewController: UIViewController {
var myLabel: UILabel = {
var label = UILabel(CGRect(x: 0, y: 0, width: 100, height: 100))
return label
}()
...
}
이렇게 하고 Run 해주면 이렇게 디바이스 왼쪽 위 끝에 딱 붙은 100 * 100짜리 라벨이 나온다.
3. 제약조건 추가
이제 저 옹졸하게 왼쪽 위에 딱 붙은 녀석의 위치를 제약조건을 통해 조정해주자.
코드로 constraints를 주는 방법은 여러 가지(갓택오버플로우 발)가 있다.
NSLayoutConstraint
근-본 애플 문서를 보자.
두 UI 객체 간 만족해야 하는 관계를 뜻한다. 이게 뭔 말이냐? 함수의 용례를 보면 이해가 쉽다.
NSLayoutConstraint(item: myLabel,
attribute: .top,
relatedBy: .equal,
toItem: view,
attribute: .top,
multiplier: 1,
constant: 100).isActive = true
위 NSLayoutConstraint 객체는 myLabel
의 상단이 view
의 상단에 1을 곱하고 100만큼 더해진 상태와 동일하다는 것을 뜻한다.
즉, mylabel.top = 1.0 * view.top + 100.0
의 식으로 표현할 수 있는 상태다.
내가 제약조건을 설정하고자 하는 뷰를 item
에, 그리고 그 제약조건의 관계를 함께 할 뷰를 toItem
에, 그리고 구체적으로 각 뷰의 어떤 부분인지를 attribute
에 넣는다. 그리고 위의 식처럼 같은지, 크거나 같은지, 작거나 같은지를 relatedBy
에 설정 해 주고, 비율과 상수를 각각 multiplier
와 constant
에 넣어 주면 된다.
이렇게 만들어진 제약 조건을 active
하게 만들어주면 내가 정해 준 뷰에 제약조건이 설정된다.
NSLayoutAnchor
근데 위와 같이 해 주면 근본있게 제약조건을 설정해줄 수 있지만, 코드가 길어지는 단점이 있다. 단순히 myLabel의 top에 view의 top + 100 만큼의 제약조건을 주는 것이기 때문에 NSLayoutAnchor
클래스를 사용해 다음과 같이 표현할 수 있다.
myLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
이렇게 해 주면, 위에서 구구절절 써내려갔던 것과 동일한 제약조건이 실행된다.
translatesAutoresizingMaskIntoConstraints
설정한 제약조건을 모두 active하게 해주고 빌드 해 보면, 아무 일도 일어나지 않는다.
이는 인터페이스 빌더로 생성하지 않은, 코드로 생성한 뷰에 자동으로 설정되는 불리언 값이다. 이 값이 true로 설정된 뷰는 루트 뷰의 제약조건 복사본을 시스템에 의해 자동으로 부여받는다.
뷰의 크기와 위치를 그대로 따르기 때문에 내가 코드로 생성한 뷰의 크기나 위치에 대한 제약조건을 걸 수 없다. 이 크기나 위치에 대해 동적으로 설정하고 싶다면, 이 값을 false
로 설정해준 뒤 제약조건을 추가해주면 된다.
라는 내용이 문서에 있다.
다 적용하면
class MyViewController: UIViewController {
...
private func setConstraints() {
myLabel.translatesAutoresizingMaskIntoConstraints = false
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
다음과 같이 제약조건을 설정해주는 함수를 만들었다.
런 해 보면, 이렇게 짜잔 하고 설정된다.
결론
코드로(Programmatically) UI 요소를 만들고, 제약조건을 걸기 위한 과정은 인터페이스 빌더에서 하는 것과 동일하다.
그대신 코드로 하기 때문에 신경 써 줘야 하는 부분들(addSubview, translatesAutoresizingMaskIntoConstraints)이 있다.
소스코드
📍 전체 소스코드
import UIKit
class ViewController: UIViewController {
var myLabel: UILabel = {
var label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configureLabel()
}
private func configureLabel() {
view.addSubview(myLabel)
setAttribute()
setConstraints()
}
private func setAttribute() {
myLabel.text = "2unbini"
myLabel.textAlignment = .center
myLabel.layer.borderColor = UIColor.systemYellow.cgColor
myLabel.layer.borderWidth = 5.0
myLabel.backgroundColor = UIColor.systemTeal.withAlphaComponent(0.2)
}
private func setConstraints() {
myLabel.translatesAutoresizingMaskIntoConstraints = false
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
잘못된 정보나 더 나은 방향이 있다면 언제든 알려 주세요!
댓글남기기