iOS-SOPT-iNNovation/iOS_Traning

<4주차> 오토레이아웃을 코드로 작성하는 방법은 무엇인가? (3가지)

Closed this issue · 8 comments

<4주차> 오토레이아웃을 코드로 작성하는 방법은 무엇인가? (3가지)

1. NSLayoutConstraint 사용

NSLayoutConstraint.activate([NSLayoutConstraint(item: UIButton, attribute: .centerX, relatedBy: .equal, toItem: UIView, attribute: .centerX, multiplier: 1.0, constant: 0.0), ...])

2. Visual Format Language 사용

NSLayoutConstraint.activate([NSLayoutConstraint.constraints(withVisualFormat: "H:[button(200)]", options: .alignAllCenterX, metrics: nil, views: ["button": button]), ...)

3. Anchor 사용

NSLayoutConstraint.activate([button.centerXAnchor.constraint(equalTo: view.centerXAnchor), ...])

4. 이 외에 SnapKit과 같은 라이브러리를 사용할 수 있다.

iJoom commented

NSLayoutConstraint

제약 기반 레이아웃 시스템에서 충족해야하는 두 사용자 인터페이스(UI) 개체 간의 관계입니다.

item1.attribute1 = multiplier × item2.attribute2 + constant
button2.leading = 1.0 × button1.trailing + 8.0
//다른 아이템과의 관계로서 layout 정의

Anchor

translatesAutoresizingMastIntoConstraints = false
//기존의 오토리사이징마스크와 충돌을 없애기 위해 설정
var constraintY: NSLayoutConstraint
constraintY = button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)

Visual Format Language (처음 알게 됨)

기호 및 문자열로 레이아웃 관계 표시
Vertical Layout
V:[topField]-10-[bottomField]
Visual Format Guide

  1. NSLayoutAnchor 사용
    원래는 NSLayoutConstraint 자체를 사용해서 오토레이아웃을 적용했다. 그러나 가독성도 좋지 않고 사용법이 조금 어려워서 새롭게 Apple에서 내준 방식이다.

  2. NSLayoutConstaint 사용
    방법을 보면 복잡하다. 한 눈에 보기 편하지 않다!

convenience init(item view1: Any, 
       attribute attr1: NSLayoutConstraint.Attribute, 
       relatedBy relation: NSLayoutConstraint.Relation, 
          toItem view2: Any?, 
       attribute attr2: NSLayoutConstraint.Attribute, 
      multiplier: CGFloat, 
        constant c: CGFloat)
  1. Visual Format Language
    기본적으로 보기좋게 만들려고 쓰는 방법이다.
let views = ["redView": redView,
             "blueView": blueView,
             "greenView": greenView]

let format1 = "V:|-[redView]-8-[greenView]-|"
let format2 = "H:|-[redView]-8-[blueView(==redView)]-|"
let format3 = "H:|-[greenView]-|"

var constraints = NSConstraint.constraints(withVisualFormat: format1,
                    options: alignAllLeft,
                    matrics: nil,
                    views: views)
constraints += NSConstraint.constraints(withVisualFormat: format2,
                    options: alignAllTop,
                    matrics: nil,
                    views: views)
constraints += NSConstraint.constraints(withVisualFormat: format3,
                    options: []
                    matrics: nil,
                    views: views)
NSConstraint.activateConstraints(constraints)

출처 : 블로그

  1. Anchor 사용하기
myView.translatesAutoresizingMastIntoConstraints = false
let margins = view.layoutMarginsGuide
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).active = true
myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).active = true
myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0)
  1. LayoutConstraints 사용하기
    가독성이 떨어지는 단점이 있습니다.
NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .Equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
  1. Visual Format 사용하기
    설명하고자 하는 레이아웃의 시각적인 표현을 제공하는 방식입니다. 읽을 수 있도록 설계되어 있으며 뷰는 대괄호로 표시되고 뷰간의 연결은 하이픈(또는 뷰들을 떨어뜨리는 숫자에 의해 두개의 분리된 하이픈)을 사용합니다
let views = ["myView": myView]
let formatString = "|-[myView]-|"
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(formatString, 
    options: .AlignAllTop, 
    metrics: nil, 
    views: views)

NSLayoutConstraint.activateConstraints(constraints)
Parameter Description
Format 제약 조건을 시각적 형식으로 나타낸 문자열
options 객체들의 속성, 레이아웃 방향 설명
metrics 상수 집합. 키는 문자열, 값은 NSNumber 객체
views 뷰 집합. 키는 문자열, 값은 뷰 객체

내가 보려고 정리하는 오토 레이아웃 속성

제목 내용
Width 너비
Height 높이
Top 정렬 사각형의 상단
Bottom 정렬 사각형의 하단
Baseline 텍스트의 하단
Leading 텍스트 시작
Trailing 텍스트 끝
CenterX 수평 중심
CenterY 수평 하단

3가지가 존재하고
NSLayoutConstraint, Visual Format Language, NSLayoutAnchor를 사용한 레이아웃구현

NSLayoutConstraint
인터페이스 객체간에 레이아웃 관계를 나타냄

item1.attribute1 = multiplier × item2.attribute2 + constant

Visual Format Language
레이아웃의 시각적 표현
뷰는 대괄호 뷰간연결은 하이픈을 사용
NSLayoutConstraint를 이용해서 생성

NSLayoutAnchor
NSLayoutConstraint가 복잡하고 사용법이 어려워서 새로나온 클래스
NSLayoutConstraint 객체를 만들어내는 팩토리 클래스
간결하고 명확하게 사용가능
NSLayoutXAxisAnchor - 수평 제약
NSLayoutYAxisAnchor - 수직 제약
NSLayoutDimension - 너비, 높이

aView.leadingAnchor.constraint(equalTo: leftLabel.leadingAnchor,constant: 10).isActive = true

Visual Format Language, NSLayoutAnchor는 결과적으로 NSLayoutConstraint를 사용

참고링크

비교코드

    func layout() {
        view.addSubview(aView)
        view.addSubview(bView)
        aView.translatesAutoresizingMaskIntoConstraints = false
        bView.translatesAutoresizingMaskIntoConstraints = false
        aView.backgroundColor = .blue
        bView.backgroundColor = .red
        
        // Visual Format
        let views: [String : Any] = ["a": aView,
                                     "b": bView]
        let format1 = "H:|-8-[a]-8-|"
        let format2 = "H:|-30-[b]-30-|"
        let format3 = "V:|-20-[a(100)]"
        let format4 = "V:[a]-20-[b(100)]"

        var constraint = NSLayoutConstraint.constraints(withVisualFormat: format1,
                                                        options: [],
                                                        metrics: nil,
                                                        views: views)
        constraint += NSLayoutConstraint.constraints(withVisualFormat: format2,
                                                     options: [],
                                                     metrics: nil,
                                                     views: views)
        constraint += NSLayoutConstraint.constraints(withVisualFormat: format3,
                                                     options: [],
                                                     metrics: nil,
                                                     views: views)
        constraint += NSLayoutConstraint.constraints(withVisualFormat: format4,
                                                     options: [],
                                                     metrics: nil,
                                                     views: views)
        view.addConstraints(constraint)
        
        //NSLayoutConstraint
        NSLayoutConstraint.init(item: aView,
                                attribute: .leading,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .leading,
                                multiplier: 1.0,
                                constant: 8).isActive = true
        NSLayoutConstraint.init(item: aView,
                                attribute: .top,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .top,
                                multiplier: 1.0,
                                constant: 20).isActive = true
        NSLayoutConstraint.init(item: aView,
                                attribute: .trailing,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .trailing,
                                multiplier: 1.0,
                                constant: -8).isActive = true
        NSLayoutConstraint.init(item: aView,
                                attribute: .height,
                                relatedBy: .equal,
                                toItem: nil,
                                attribute: .height,
                                multiplier: 1.0,
                                constant: 100).isActive = true

        NSLayoutConstraint.init(item: bView,
                                attribute: .leading,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .leading,
                                multiplier: 1.0,
                                constant: 30).isActive = true
        NSLayoutConstraint.init(item: bView,
                                attribute: .top,
                                relatedBy: .equal,
                                toItem: aView,
                                attribute: .bottom,
                                multiplier: 1.0,
                                constant: 20).isActive = true
        NSLayoutConstraint.init(item: bView,
                                attribute: .trailing,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .trailing,
                                multiplier: 1.0,
                                constant: -30).isActive = true
        NSLayoutConstraint.init(item: bView,
                                attribute: .height,
                                relatedBy: .equal,
                                toItem: nil,
                                attribute: .height,
                                multiplier: 1.0,
                                constant: 100).isActive = true
        
        
        //NSLayoutAnchor
        aView.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 8).isActive = true
        aView.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -8).isActive = true
        aView.topAnchor.constraint(equalTo: view.topAnchor,constant: 20).isActive = true
        aView.heightAnchor.constraint(equalToConstant: 100).isActive = true

        bView.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 30).isActive = true
        bView.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -30).isActive = true
        bView.topAnchor.constraint(equalTo: aView.bottomAnchor,constant: 20).isActive = true
        bView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    }

세가지가 존재함니다

  1. Anchor 타입
  2. NSLayoutConstraint
  3. VFL

우선
스토리보드에 올라갈때는 translatesAutoresizingMaskIntoConstraints 속성이 자동으로 false로 반영됩니당
하지만 코드로 짤때는 손수 translatesAutoresizingMaskIntoConstraints = fase를 해 주어야 해요

  1. Anchor 타입
  NSLayoutConstraint.activate([
        alertView.centerYAnchor.constraint(equalTo: window.centerYAnchor),
        alertView.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: 20),
        alertView.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: -20),
])

특정 Anchor를 지정해서 오토레이아웃을 잡는 방식인데, 배열 바깥에서 알 수 있듯 NSLayoutConstraint를 사용해서 active 시킵니당

특징

  • 사용하기에 편하고 좋다
  • 제일 많이 쓰는듯

단점

  • 코드베이스 기반의 한계겠지만 가독성이 뛰어난 편은 절대 아님
  1. NSLayoutConstraint 타입
    일단 코드를 보면
  NSLayoutConstraint(item: alertView,
                               attribute: .leading,
                               relatedBy: .equal,
                               toItem: messageLabel,
                               attribute: .leading,
                               multiplier: 1.0,
                               constant: 8).isActive = true

이런식으로 item, toItem을 지정해서 짭니다. 특징 고고

특징

  • 얘도 역시 짤때는 나쁘지 않습니다
  • 가독성은 그래도 Anchor보다 나음 ( 슥 읽으면 됨 )

단점

  • 조금 길고 multiplier 이거는 잘 안 쓰잖아요?

그래서 저는 이런 커스텀메소드를 만들어서 쓰는뎅 좋은듯해요

extension UIView {
   // 1
  func constraint(to view: UIView,
                  attribute: NSLayoutConstraint.Attribute,
                  secondAttribute: NSLayoutConstraint.Attribute,
                  inset: CGFloat = 0) {
    self.translatesAutoresizingMaskIntoConstraints = false
    let c = NSLayoutConstraint(item: self,
                               attribute: attribute,
                               relatedBy: .equal,
                               toItem: view,
                               attribute: secondAttribute,
                               multiplier: 1,
                               constant: inset)
    c.isActive = true
  }
   // 2
  func constraint(_ anchor: NSLayoutDimension, constant: CGFloat) {
    self.translatesAutoresizingMaskIntoConstraints = false
    anchor.constraint(equalToConstant: constant).isActive = true
  }

이렇게 해놓고

              // 1
            dismissButton.constraint(to: alertView,
                                     attribute: .trailing,
                                     secondAttribute: .trailing,
                                     inset: -12)
            dismissButton.constraint(to: alertView,
                                     attribute: .top,
                                     secondAttribute: .top,
                                     inset: 12)
              // 2
            dismissButton.constraint(widthAnchor, constant: 44)
            dismissButton.constraint(heightAnchor, constant: 44)

이런식으로 씁니당. 나쁘지 않아요~~

  1. Visual Format Language
    그.. VFL은 올해 초에 한참 많이 봤던 유튜바 Brian Voong씨가 자주 사용해서 제가 카톡방에 무러본적이 있는데
    다양한 반응이 있었습니다

image
image
밑에분이 특이한거고 거의 안쓰시는듯 이름을 가려야되나 나중에 가리겟습니다

또한 오토레이아웃 잘못 잡으면 디버깅창에 뜨는 오토레이아웃 에러메시지가 VFL로 뜹니다!!! 그래서 배워놓으면 좋을듯 한데 저는 모름
이번 기회에 한번 써봤습니다~!

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html

H:|-[find]-[findNext]-[findField(>=20)]-|
이런 코드를 뜯어볼게요

H: (Horizontal) // V는 vertical
| (pipe) // superview
- (dash) // 표준 스페이싱 (8 points)
[] (brackets) // 대상 객체 (UILabel, UIButton, uiview ~)
() (parentheses) // 객체의 사이즈 제약

특징

  1. 사용하시는분 말로는 이만큼 편한게 없다고 합니다
  2. 가독성도 꽤 높습니다

단점

  1. 너무나 많은 준비물 코드
    2 .팀원중 한명만 사용할 줄 알면 못 씀

https://iosexample.com/ios-auto-layout-with-json-and-vfl/
JSONLayout이라는 라이브러리가 있는데 이게 되네...

2019년도 Let's Swift 강의

https://www.youtube.com/watch?v=EzjyuEf61Vo&list=PLAHa1zfLtLiOiVcIUwWZbOgN9x3cTjRqN&index=9

를 기반으로 작성합니당~

  1. NSLayoutConstraint

Layout을 code로 작성하기 위해서 나온 레이아웃 작성 방식

아래의 예시로 보면 알수있겠지만 코드 한줄 한줄 의미하는게 한눈에 보이고 좋지만

코드의 가독성이 좋지 않다.

NSLayoutConstraint.activate([
    NSLayoutConstraint(item: button,
                       attribute: .centerX,
                       relatedBy: .equal,
                       toItem: view,
                       attribute: .centerX,
                       multiplier: 1.0,
                       constant: 0.0),
    NSLayoutConstraint(item: button,
                       attribute: .width,
                       relatedBy: .equal,
                       toItem: nil,
                       attribute: .height,
                       multiplier: 1.0,
                       constant: 200.0),
    NSLayoutConstraint(item: button,
                       attribute: .top,
                       relatedBy: .equal,
                       toItem: view,
                       attribute: .topMargin,
                       multiplier: 1.0,
                       constant: 100.0),
    NSLayoutConstraint(item: button,
                       attribute: .height,
                       relatedBy: .equal,
                       toItem: view,
                       attribute: .height,
                       multiplier: 1.0,
                       constant: 80.0)
])
  1. Visual Format Language

아직 사용해본적은 없는 레이아웃 구조를 잡는 방식이지만, 익숙해지면... 간편할것만 같은 작성 방법...

문법에 대한 공부가 필요함... *** 제일 큰 단점

NSLayoutConstraint.activate([
    NSLayoutConstraint.constraints(withVisualFormat: "H:[button(200)]",
                                   options: .alignAllCenterX,
                                   metrics: nil,
                                   views: ["button": button]),
    NSLayoutConstraint.constraints(withVisualFormat: "V:[sv]-(<=1.0)-[button(80)]",
                                   options: .alignAllCenterX,
                                   metrics: nil,
                                   views: ["sv": view!, "button": button]),
    NSLayoutConstraint.constraints(withVisualFormat: "V:|-(100)-[button(80)]",
                                   options: .alignAllTop,
                                   metrics: nil,
                                   views: ["button": button])
    ].flatMap({ $0 }))
  1. Anchor

NSLayoutConstraint으로 작성한 코드 방식이 굉장히 가독성이 떨어지기 때문에 나온 레이아웃 코드 방식
하지만 이것 또한 코드가 매우매우 길어짐...

위 두개에 비해서는 가독성이 나쁘지 않는 듯!

NSLayoutConstraint.activate([
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    button.widthAnchor.constraint(equalToConstant: 200.0),
    button.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
    button.heightAnchor.constraint(equalToConstant: 80.0)
])
  1. NSLayoutAnchor

Anchor라는 개념을 통해 NSLayoutConstraint를 생성시킬 수 있다.

Anchor와 관련된 프로퍼티

var constraints: [NSLayoutConstrint] // 뷰에 부여한 제약사항들을 담은 배열
var bottomAnchor: NSLayoutYAxisAnchor { get } // 뷰 프레임의 하단부 레이아웃 앵커
var centerXAnchor: NSLayoutXAxisAnchor { get } // 뷰 프레임의 수평 중심부 레이아웃 앵커

var centerYAnchor: NSLayoutYAxisAnchor { get } // 뷰 프레임의 수직 중심부 레이아웃 앵커
var heightAnchor: NSLayoutDimension { get }// 뷰 프레임의 높이를 가리키는 레이아웃 앵커
var leadingAnchor: NSLayoutXAxisAnchor { get } // 뷰 프레임의 리딩을 가리키는 레이아웃 앵커
var topAnchor: NSLayoutYAnchor { get } // 뷰 프레임의 상단부 레이아웃 앵커
var trailingAnchor: NSLayoytXAxisAnchor { get } // 뷰 프레임의 트레일링을 가리키는 레이아웃 앵커
var widthAnchor: NSLayoutDimension { get }// 뷰 프레임의 넓이를 가리키는 레이아웃 앵커

직접 사용한 예제

// Set Label’s width
var widthConstraint: NSLayoutConstraint
widthConstraint = label.widthAnchor.constraint(equalTo: Button.widthAnchor, multiplier: 2.0)
        
widthConstraint.isActive = true

다음을 통해 NSLayoutConstraint를 생성하여 isActive 해주거나

NSLayoutConstraint.activate([
    label.widthAnchor.constraint(equalTo: Button.widthAnchor, multiplier: 2.0)
])

다음과 같이 배열을 통해 전체를 active 시켜주는 방법이 있다.

  1. NSLayoutConstraint
    직접적으로 NSLayoutConstraint인스턴스를 생성하는 방법
centerY = NSLayoutConstraint(item: button,
                             attribute: .centerY,
                             relatedBy: .equal,
                             toItem: self.view,
                             attribute: .centerY,
                             multiplier: 0.8,
                             constant: 0)

다음과 같이 오토레이아웃 방정식의 각 속성들을 지정하여 생성할 수 있다. active해주는 방식은 anchor방식과 유사하다고 볼 수 있다.

  1. Visual Format Language

VFL에서 사용 가능한 기호 및 문자열의 의미
‘|’ : superView 입니다.
‘-‘ : 표준 간격입니다. 기본은 8포인트 입니다.
‘==‘ : 같은 너비 입니다.
-10- : 사이의 간격이 10포인트입니다.
‘<=50’ : 50보다 작거나 같습니다.
‘>=50’ : 50보다 크거나 같습니다.
@750 : 우선도를 지정할 수 있다.
‘H’ : 수평 방향 입니다.(가로)
‘V’ : 수직 방향 입니다.(세로)