본문 바로가기

학습 노트/iOS (2021)

168. Debugging Auto Layout

Debugging Auto Layout

Auto Layout에서 오류가 발생하는 이유는

  • 필요한 제약이 추가되지 않은 경우
  • 필요 이상의 제약이 중복돼 있는 경우

처럼 명확하다.
필요한 제약만 올바르게 추가한다면 문제가 발생하는 경우는 없다.

하지만 코드를 사용해 제약을 추가하는 경우 앱을 실행할 때까지 결과를 확인할 수 없으므로,
이러한 규칙을 지키기 어려울 때도 있다.

Missing Constraints

예시는 위와 같다.
leading 제약만 추가돼있고, Indicator가 붉은 색으로 표시된다.
Interface Builder에서는 여러 방식으로 오류를 표현한다.

Design Time

  • Visual Indicator
    Scene에서 해당 제약을 붉게 표현한다.
  • Constraint Issue Indicator
    Interface Builder에서 Scene의 이름 옆에 붉은색 화살표를 표시한다.
    Fixit을 사용해 자동으로 필요한 제약을 추가할 수 있지만, 올바른 제약이 아닌 경우도 있다.
  • Issue Navigator

Run Time

시뮬레이터에선 이런 오류에 아무것도 표시되지 않거나 의도와는 다르게 표시될 수 있다.

이럴 때엔 View Debugger를 사용할 수 있다.

Xcode 좌측 하단 Debug Bar에서 의 해당 버튼을 누르면

시뮬레이터가 일시 정지되고, 전체 View의 계층을 확인할 수 있다.
오류가 존재하는 경우 연관된 View의 오른쪽에 보라색 아이콘이 표시된다.

선택하면 Size Inspector에 해당 View의 현재 속성들이 표시된다.

확인해 보면 지금은 View의 크기와 수직 방향의 위치의 모호함 때문에 X를 제외한 모든 값이 0으로 표시됐다.

또한 콘솔에도 관련된 메세지가 표시된다.

오류를 해결하는 방법은 간단하다.
앞의 방법을 통해 원인을 찾고, 필요한 제약을 추가하면 된다.

Comflicting Constraints

사용할 Scene은 위와 같다.

View에는 제약이 추가돼있는데 Height에 두 개의 제약이 추가돼있다.
두 제약은 같은 우선순위와 같은 대상에 적용돼있지만 서로 다른 값을 가지고 있어 충돌이 발생한다.

시뮬레이터에선 일단 표시가 되긴 한다.
Auto Layout은 충돌하는 제약이 있을 경우 충돌이 사라질 때까지 충돌하는 제약을 하나씩 제거한다.
이를 Fallback System이라고 부르고, 관련된 내용을 콘솔에 표시한다.

내용은 위와 같다.

괄호 안에 문제가 발생한 제약들을 모두 나열하고,
아래에서는 어떤 제약을 제거했는지를 알려준다.
높이가 좁게 표시된 이유는 120의 제약을 제거했기 때문이다.

해당하는 View를 Debugger로 확인해 보면 회색을 표시된 제약이 존재한다.
해당 제약이 삭제된 제약이다.

이렇게 Debugger와 콘솔을 확인해야 하는 경우는 Interface Builder를 사용할 때 보다 코드를 통해 작업하는 경우 더 유용하다.

Unsatisfiable Layouts

Unsatisfiable Layout Scene은 코드로 회색의 View를 생성하도록 대있다.
시뮬레이터는 제대로 View를 출력하고 있는 것 같지만,

로그를 확인하면 전혀 그렇지 못하다.
NSAutoresizingMaskLayoutConstraint와 NSLayoutConstraint 제약이 충돌했고,
NSLayoutConstraint가 제거됐다.
해당 문제는 이전 실습때 처럼 translateAutoresizingMaskIntoConstraint 속성을 비활성화하지 않은 문제다.
그 아래의 문제도 동일하다.

이렇게 원인을 찾았다면 어디서 생긴 문제인지를 확인해 봐야한다.
이럴 때 Debugger를 사용한다.

콘솔에서 포인터를 찾아 Dbugger의 검색창에 입력하면 사진과 같이 특정 View의 계층만 표시된다.
그리고 계층의 가장 아래에 존재하는 View가 바로 제약 오류가 발생한 View다.

콘솔의 더 아래엔 Symbolic breakpoint에 대한 내용이 존재한다.
제약 오류가 발생했을 때 breakpoint로 일시 정지하고, 다양한 명령어를 통해 문제를 추적할 수 있다.

해당 내용을 복사해

Breakpoint Navigator를 선택하고,


하단의 '+'버튼을 선택하고, Symbolic Breakpoint를 눌러

이어서 나타나는 팝업의 Symbol 란에 붙여 넣는다.

다시 시뮬레이터를 실행하고, 제약 오류가 발생하는 Scene으로 이동하면
Breakpoint로 인해 일시 정지되고, 이상한 코드가 표시됨과 함께 'lldb'라는 내용이 콘솔에 출력된다.

지금부터는 명령어를 입력할 수 있는데 예를 들어 'po $r15'라고 입력하면
제약과 관련된 상세 정보가 출력되는 식이다.
예전에는 이러한 방식으로 디버깅을 진행했지만 지금은 주로 위에서 설명한 Debugger를 사용한다.

Identifier

사용할 Scene은 위와 같다.

실행하면 여지없이 제약 오류에 관련된 로그가 표시되는데,
이번엔 제약과 View에 식별자를 적용해서 가독성을 개선해 본다.

분홍색 View를 선택하고, Identity Inspector로 이동해 Accessbility의 Identifier를 설정한다.

마찬가지로 제약의 Attribute로 이동해 Identifier를 설정한다.

//
//  IdentifierViewController.swift
//  DebuggigAutoLayout Practice
//
//  Created by Martin.Q on 2021/12/21.
//

import UIKit

class IdentifierViewController: UIViewController {

	@IBOutlet weak var pinkView: UIView!
	override func viewDidLoad() {
        super.viewDidLoad()

		let leading = pinkView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
		leading.isActive = true
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

이번엔 Scene에 연결된 코드로 이동해 구현한다.
지금은 Leading 제약을 코드를 사용해 추가하는데, 해당 제약의 Identifier를 codeLaeding으로 설정한다.

//
//  IdentifierViewController.swift
//  DebuggigAutoLayout Practice
//
//  Created by Martin.Q on 2021/12/21.
//

import UIKit

class IdentifierViewController: UIViewController {

	@IBOutlet weak var pinkView: UIView!
	override func viewDidLoad() {
        super.viewDidLoad()

		let leading = pinkView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
		leading.identifier = "codeLeading"
		leading.isActive = true
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

다시 시뮬레이터를 구동해 콘솔을 확인해 보면

마찬가지로 오류가 출력되지만 이전과는 다르게 몇몇 요소들에서 pinkView, codeLeading, ibWidth를 확인할 수 있다.
이처럼 Identifier를 설정해 놓으면 콘솔에서 어떤 요소인지 바로 확인할 수 있다.