UIKit

Autoresizing Mask, translatesAutoresizingMaskIntoConstraints

Phililip
728x90

뷰를 배치하는 방법에는 autolayout만 있는 줄 알았는데, autoresizing이라는 것이 있더라구요..

 

(기초 같은데 몰랐다니.....;;;)

 

이제라도 알아보자..라는 심정으로 autoresizingMask에 대해 알아보려고 합니다.

 

알아보는 김에 translatesAutoresizingMaskIntoConstraints이 어떤 역할을 하는지도 같이 알아볼게요.

 

(뷰를 코드로 배치할 때 습관처럼 translatesAutoresizingMaskIntoConstraints = false로 줬는데 이젠 그 이유를 알아야 할 때가 온 것 같습니다...)  

 

#1 AutoresizingMask

 

우선 autoresizingMask의 apple document부터 보겠습니다.

 

An integer bit mask that determines how the receiver resizes itself when its superview’s bounds change.

 

AutoresizingMask란, superview의 영역이 바뀔 때 하위 뷰의 크기를 어떻게 조정할지에 대한 정보를 가지고 있는 정수형 비트마스크 라고 합니다.

 

마스크 종류는 이렇게 있습니다.

 

static var flexibleLeftMargin: UIView.AutoresizingMaskResizing
static var flexibleWidth: UIView.AutoresizingMaskResizing
static var flexibleRightMargin: UIView.AutoresizingMaskResizing
static var flexibleTopMargin: UIView.AutoresizingMaskResizing
static var flexibleHeight: UIView.AutoresizingMaskResizing
static var flexibleBottomMargin: UIView.AutoresizingMaskResizing

 

동일 축에서 여러 옵션이 설정된 경우, 유연한(flexible)한 부분을 비례하게 분배를 해줍니다.

 

이게 무슨 말이고... 하니

 

 

예를 들어,

 

flexibleWidthflexibleRightMargin을 설정했고, flexibleLeftMargin은 설정하지 않았다고 가정한다면, 

 

왼쪽 여백에 대해선 유연(flexible)하게 지정해주지 않았기 때문에 여백의 크기가 고정되고, 너비와 오른쪽 여백은 유연(flexible) 하기 때문에 크기 변경이 생길 수 있습니다.

 

즉!!! 화면이 커지게 되면 뷰는 왼쪽에 고정되고 너비와 오른쪽 여백이 증가하는 듯한 모습을 보일 것입니다.

 

말로만 해선 잘 이해가 안 가니.. 스토리보드에서 직접 확인해보겠습니다.

 

우선 iPhone 11을 기준으로 (x: 50, y: 50, width: 200, height: 200)인 View를 그려줄게요.

 

우측 Size inspector를 보면 Autoresizing 탭이 있는데 여기서 Autoresizing 설정을 해주는 것입니다.

(뷰를 추가하면 기본적으로 left, top margin은 체크가 되어 있습니다.)

 

 

근데 Autoresizing 설정할 수 있는 = 네모칸을 자세히 보면 네모 안의 선은 그냥 직선이 아닌 화살표가 있는 것을 볼 수 있어요.  화살표가 의미하는 것은 flexible 하다는 것입니다. (즉, 뷰의 크기에 따라 증가/감소할 수 있다는 것이죠)

 

다르게 말하면 상하좌우 여백은 화살표가 없으니 고정값을 준다! 라고 이해를 하면 될 것 같습니다.

(상하좌우는 선택 안 하면 flexible 인 듯....?)

 

그럼 처음에 예로 들은 'flexibleWidth와 flexibleRightMargin을 설정했고, flexibleLeftMargin은 설정하지 않았다고 가정' 은 아래처럼 설정해야 할 것입니다. 

 

 

저렇게 Autoresizing을 설정했을 때, 화면이 커지면 왼쪽 여백은 고정이고 너비와 오른쪽 공백의 크기가 변하는 것이죠. 

 

iPhone 4s 인 경우

 

iPad Air 인 경우

 

iPhone 4s, iPad Air에서 확인했을 때, 너비와 top margin은 변경되었지만, left maring은 끝까지 '50'에서 바뀌지 않는 것을 볼 수 있습니다. 👍

 

 

Autoresizing은 스토리보드에서 설정도 쉽게 어떻게 바뀌는지 바로바로 적용이 되어서, 직접 이것저것 만져보시면 좀 더 이해하는데 도움이 될 것 같습니다.

 

 

#2 translatesAutoresizingMaskIntoConstraints

translatesAutoresizingMaskIntoConstraints는 UIView의 인스턴스 프로퍼티입니다.

 

이름만 봐서는 "AutoresizingMask를 Constraints로 바꿔준다." 라고 보이는데, autolayout을 프로그래밍 방식으로 구현할 때 왜 이 값을 false로 설정해줘야 하는지.... 감이 잘 안 잡히네요.

 

우선 apple document부터 보겠습니다.

 

A Boolean value that determines whether the view’s autoresizing mask is translated into Auto Layout constraints.

 

뷰의 Autoresizing mask를 Auto Layout constarints로 바꿀지 말지를 결정하는 Boolean 값

 

이라고 하는데... 이건 프로퍼티 명만 봐도 어느 정도 예상하고 있던 사실이니... 좀 더 자세한 내용을 살펴봅시다.

 

 

If this property’s value is true, the system creates a set of constraints that duplicate the behavior specified by the view’s autoresizing mask.

Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts. If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.

By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false.

 

translatesAutoresizingMaskIntoConstraints = true이면, 뷰의 autoresizing mask의 동작을 constraints로 복제를 합니다.

 

또한, autoresizing mask constraints는 뷰의 크기와 위치를 지정해버리기 때문에, 이후에 추가적인 constraints를 추가할 수 없습니다.

 

만약, Autolayout을 동적으로 계산해서 사용하고 싶다면, translatesAutoresizingMaskIntoConstraints = false로 지정해야 합니다. 이때, constraints를 모호하지 않게(not ambiguous) 지정해주세요.

 

뷰를 프로그래밍 방식(programmatically)으로 생성했을 때 기본값은 true이며, interface Builder(스토리보드를 말하는 것이겠죠?)에서 뷰를 추가했을 땐 기본값이 false입니다.

 

아.... 직접 확인하는 게 이해가 빠를 것 같네요ㅎㅎ.. 프로그래밍 방식과 interface Builder 방식 모두 확인해볼게요.

 

 

## 2.1 프로그래밍 방식

우선, 프로그래밍 방식으로 뷰를 추가하는 것부터 살펴봅시다.

 

노란색 배경의 전체 화면을 덮는 UIView를 하나 추가해줄게요.

 

class ViewController: UIViewController {
    private lazy var myView: UIView = {
        let myView = UIView()
        myView.backgroundColor = .yellow
        return myView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myView)
        myView.frame = view.bounds
        myView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
}

 

현재 myView는 전체 화면으로 나오고, 화면의 크기가 변하면 (회전 등..) 그에 맞춰서 유동적으로 변하게 됩니다.

 

constraint로 너비를 절반으로 줄여볼게요.

 

class ViewController: UIViewController {
    private lazy var myView: UIView = {
        let myView = UIView()
        myView.backgroundColor = .yellow
        return myView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myView)
        myView.frame = view.bounds
        myView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        NSLayoutConstraint.activate([
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            myView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
            myView.heightAnchor.constraint(equalTo: view.heightAnchor),
        ])
    }
}

 

 

빌드해보면... 너비는 줄지 않고... 아래 같은 로그가 나왔습니다.

 

[LayoutConstraints] Unable to simultaneously satisfy constraints.

Probably at least one of the constraints in the following list is one you don't want. 

Try this: 
	(1) look at each constraint and try to figure out which you don't expect; 
	(2) find the code that added the unwanted constraint or constraints and fix it. 
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(

    "<NSAutoresizingMaskLayoutConstraint:0x600002760e60 h=-&- v=-&- UIView:0x7fa3eb31e240.minX == 0   (active, names: '|':UIView:0x7fa3eb3361f0 )>",
    "<NSLayoutConstraint:0x600002718050 UIView:0x7fa3eb31e240.centerX == UIView:0x7fa3eb3361f0.centerX   (active)>",
    "<NSLayoutConstraint:0x6000027181e0 UIView:0x7fa3eb31e240.width == 0.5*UIView:0x7fa3eb3361f0.width   (active)>",
    "<NSLayoutConstraint:0x600002760d70 'UIView-Encapsulated-Layout-Width' UIView:0x7fa3eb3361f0.width == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002718050 UIView:0x7fa3eb31e240.centerX == UIView:0x7fa3eb3361f0.centerX   (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

 

 

 

이유를 생각해보면, 

 

프로그래밍 방식으로 뷰를 추가하면 translatesAutoresizingMaskIntoConstraints = true이고,

 

translatesAutoresizingMaskIntoConstraints = true이면, autoresizing mask constraints로 뷰의 크기, 위치를 고정하기 때문에 constraint를 추가할 수 없다고 했던 것 기억하시나요???

 

즉, Swift 입장에서 autoresizing mask로 이미 뷰의 크기를 고정시켰는데 추가적인 constraint로 너비를 반으로 줄이라고 하니... 이를 적용할 수 없다는 것이죠.

 

 

그래서 결국에는 autolayout을 사용하기 위해서 아래처럼 translatesAutoresizingMaskIntoConstraints = false로 설정해야 하는 것입니다.

 

class ViewController: UIViewController {
    private lazy var myView: UIView = {
        let myView = UIView()
        myView.backgroundColor = .yellow
        return myView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myView)
        myView.frame = view.bounds
        myView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            myView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
            myView.heightAnchor.constraint(equalTo: view.heightAnchor),
        ])
    }
}

 

 

👍 👍 👍

 

 

만약에 아래처럼 설정한 autoresizing mask와 같은 의미를 가진 constraint를 추가해주면 어떨까요??

 

class ViewController: UIViewController {
    private lazy var myView: UIView = {
        let myView = UIView()
        myView.backgroundColor = .yellow
        return myView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(myView)
        myView.frame = view.bounds
        myView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        myView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            myView.widthAnchor.constraint(equalTo: view.widthAnchor),
            myView.heightAnchor.constraint(equalTo: view.heightAnchor),
        ])
    }
}

 

이땐 신기하게도, constraint 관련 어떠한 warning log도 없습니다.

 

이건 제 추측인데... autoresizing mask constraint와 충돌이 없는(동일한 설정) constraint는 아무런 문제가 없다고 판단을 하고, 충돌이 발생할 만한 constraint(같은 축에 대한 설정 변경 등)인 경우에는 warning log와 함께 무시하는 것은 아닌가 싶습니다.

 

 

 

## 2.2 Interface Builder 방식

 

이제, Interface Builder 방식에 대해 알아봅시다.

 

스토리보드에서 UIView를 추가하고 autoresizing mask 설정을 추가해줍니다.

 

 

 

ViewController로 가서 IBOutlet으로 연결시킨 후에 

 

class ViewController: UIViewController {
    @IBOutlet weak var myView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

 

빌드해보면 잘 나오겠죠??

 

 

 

interface builder에서 뷰를 추가하면 translatesAutoresizingMaskIntoConstraints = false로 설정된다는 것 기억하시나요??

 

translatesAutoresizingMaskIntoConstraints = false이면, constraint를 내 맘대로 추가할 수 있겠군요!!

 

이번에도 너비를 반으로 줄여봅시다.

 

class ViewController: UIViewController {
    @IBOutlet weak var myView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        NSLayoutConstraint.activate([
            myView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
        ])
    }
}

 

빌드해보면

 

 

 

억...... constraint가 적용이 안되었네요... 

 

AutoresizingTest[20670:858132] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
	(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x6000013846e0 h=-&- v=-&- UIView:0x7fc6f0128770.minX == 0   (active, names: '|':UIView:0x7fc6f0129790 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x6000013845f0 h=-&- v=-&- H:[UIView:0x7fc6f0128770]-(0)-|   (active, names: '|':UIView:0x7fc6f0129790 )>",
    "<NSLayoutConstraint:0x600001381900 UIView:0x7fc6f0128770.width == 0.5*UIView:0x7fc6f0129790.width   (active)>",
    "<NSLayoutConstraint:0x600001384500 'UIView-Encapsulated-Layout-Width' UIView:0x7fc6f0129790.width == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600001384500 'UIView-Encapsulated-Layout-Width' UIView:0x7fc6f0129790.width == 414   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

 

warning log까지 나온 상태입니다.....

 

분명.... interface builder로 뷰를 추가하면 translatesAutoresizingMaskIntoConstraints = false라고 했는데....

 

 

 

설마....... myView.translatesAutoresizingMaskIntoConstraints의 값이 뭔지 확인해볼게요

 

(lldb) po myView.translatesAutoresizingMaskIntoConstraints
true

 

엥????? .....;;;;;;

 

흠......

 

스토리보드에서는 false 였는데... IBOutlet으로 연결한 순간 Swift에서 프로그래밍 방식이라고 판단하고 translatesAutoresizingMaskIntoConstraints = true로 설정!!!

 

하는 것은 아닐까.... 하는 가설을 세워봤습니다....;;;;

 

 

결국엔 저희는 스토리보드에 뷰를 추가했어도, constraint를 추가하려면 translatesAutoresizingMaskIntoConstraints = false라고 직접 수정을 해줘야 하는 것 같아요.

 

 

 

그럼 아래처럼 translatesAutoresizingMaskIntoConstraints = false로 설정해주면 뷰가 원하는 대로 나오겠죠???

class ViewController: UIViewController {
    @IBOutlet weak var myView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        myView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            myView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
        ])
    }
}

 

 

엥......? (한 번에 되는 게 없네....ㅋㅋㅋㅋ)

 

이번엔 warning log는 없지만, 뷰가 안 나왔습니다.

 

 

view hierarchy를 보면

 

 

Position and height ar ambiguous.

 

라고 하죠? 

 

위에서, translatesAutoresizingMaskIntoConstraints = false인 상태에서 autolayout을 설정하려면 모호하지 않게(not ambiguous) 해야 한다는 것 기억하시나요??

 

저희는 constraint로 너비만 줬고, 위치 & 높이를 주지 않았으니 문제가 발생했다고 하네요.

 

 

결국엔 최종적으로, 아래 코드처럼 translatesAutoresizingMaskIntoConstraints = false로 주고 constraint로 완벽하게 설정해줘야지 정상적으로 화면에 출력되는 것입니다.

 

class ViewController: UIViewController {
    @IBOutlet weak var myView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        myView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            myView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
            myView.heightAnchor.constraint(equalTo: view.heightAnchor),
        ])
    }
}

 

 

 


 

 

저처럼 autoresizing mask가 뭔지, translatesAutoresizingMaskIntoConstraints = false로 왜 설정해야 하는지 궁금하셨던 분들에게 도움이 되었으면 좋겠습니다.

 

몇 가지 추측이 있긴 했지만.. 잘못된 부분이 있으면 말씀 부탁드립니다.

 

 

반응형

'UIKit' 카테고리의 다른 글

Image 비동기 로딩 API (UIKit)  (0) 2022.03.23
DiffableDataSource를 사용해서 TableView 드래그&드롭 기능 넣기  (0) 2022.02.19
DiffableDataSource  (0) 2022.02.19
UICollectionView (2)  (0) 2022.02.17
UICollectionView (1)  (0) 2022.02.13