본문 바로가기

학습 노트/iOS (2021)

160. Auto Layout Practice #1

Auto Layout Practice

지금까지 배운 내용을 정리하며 카카오톡의 프로필과 같은 UI를 따라 구현해 본다.

UI 분석은 다음과 같다.

  • 전체 화면을 프로필 이미지를 기준으로 두 개의 Container로 구분한다.
  • 하단 Container는 높이를 고정하고, 나머지를 상단 Cotainer가 채우도록 한다.
  • 상단 Container는 이미지를 표시하고, 좌측 상단에는 닫기 버튼을, 오른쪽에는 메뉴 버튼을 배치한다.
  • 상단 Container 중앙에는 메시지를 배치하고, 상태 메시지 위에는 흰색 Bar도 표시한다.
  • 상단 Container의 좌측 하단에 두 개의 버튼을 표시한다.
  • 하단 Container에는 이름과 mail 주소를 가운데 정렬로 배치한다.
  • 사한 Container의 이름과 mail 아래에 버튼 3개를 동일한 간격으로 배치하고, 이미지와 Label을 표시한다.
  • 프로필 이미지는 수평 가운데 정렬하고, Container의 경계에서 약간 위로 올라간 위치에 배치한다.

Scene에 View를 하나 추가하고 leading, trailing, bottom에 각각 0을, 높이는 적당한 값으로 고정하는 제약을 추가한다.
Constrain to margins 속성을 해제해야 하지만 Root View에 기본적으로 추가되는 제약들은 Safe Area를 기준으로 추가되기 때문에
신경 쓰지 않아도 무관하다.

기본 제약들은 Safe Area를 기준으로 추가되기 때문에 왼쪽 사진과 같이 Home Indicator 부분의 공간을 비우고 표시된다.
해당하는 제약을 선택해 대상을 Superview로 변경하고, 값을 0으로 수정해 화면 아래까지 꽉 채우도록 변경한다.

View가 화면의 하단을 채우게 된다.

새로운 View를 추가하고 제약을 추가한다.
하단 Container의 영역을 제외한 나머지를 채우면 되기 때문에 모든 방향으로 0의 제약을 추가한다.
마찬가지로 Root View에 추가되는 제약이므로 Safe Area를 기준으로 하기 때문에 Constrain to margins는 신경 쓰지 않아도 된다.

또한 노치 부분을 채울 수 있도록 top 제약의 기준을 Safe Area에서 Superview로 변경하고, 값을 0으로 설정한다.

2

상단 Container에는 이미지를 표시할 수 있도록 Image View를 추가한다.
Container의 공간을 모두 채우면 되므로 Container를 기준으로 모든 방향에 0의 제약을 추가하되,
이번에는 View의 margin이 개입하므로 Constrain to margins 옵션을 비활성화해야 한다.

배경 이미지를 설정하고,
Image View의 속성들 중 Content Mode를 Aspect Fill로 변경하고, Clips to Bounds를 적용해,
Image View의 Frame 내에서만 꽉 채워 표시할 수 있도록 설정한다.

Portrait 모드에서는 괜찮지만 LandScape에서는 좌우로 여백이 생긴다.
이전에 제약을 추가할 때 Top 제약은 Superview를 기준으로 추가했지만
leading과 trailing 제약은 여전히 Safe Area를 기준으로 사용하고 있다.

해당 제약들을 Superview로 변경하면 Landscape에서도 여백 없이 전체를 채울 수 있다.

Button을 하나 추가하고 Attribute를 수정한다.
Type을 Custom으로, Style을 Default, Title 내용을 전부 지운 뒤, 적당한 이미지를 선택한다.
이후 top, leading에 10pt의 제약을 추가하고, 너비와 높이는 22pt로 고정한다.
이때 Constrain to margins 옵션은 사용하지 않는다.
하지만 예상했던 위치와는 다른 곳에 버튼이 위치하게 된다.
지금 상황은 View의 Frame을 기준으로 제약이 추가된 것으로,
이번엔 반대로 Safe Area를 기준으로 제약을 추가해야 System UI에 가리지 않고 표시될 수 있다.

하지만 제약의 속성에서는 Safe Area를 찾아볼 수 없다.
지금 단계에서는 같은 계층이나 한 계층 위의 대상만을 지정할 수 있다.

원래 존재하던 제약을 삭제하고 Safe Area에 오른쪽 클릭으로 드래그해 top 제약을 추가한다.

그리고 올바른 위치에 올 수 있도록 제약의 값을 수정하면 문제가 해결된다.
같은 방식으로 leading 제약을 수정한다.

오른쪽 상단의 메뉴 아이콘도 같은 방식으로 배치한다.
단, 완전히 동일한 방식으로 진행하면 버튼 각자가 제약을 가져 수정 시 작업의 양이 많아진다.

오른쪽 버튼을 오른쪽 클릭으로 드래그해 왼쪽 버튼과 연결한다.

이후 표시되는 팝업에서 top을 선택해 왼쪽 버튼의 top 제약과 동일한 제약을 가지도록 추가한다.
이렇게 하면 top에 관련된 영역이 수정됐을 때 왼쪽 버튼의 top 제약 수정만으로 두 버튼을 동시에 조정할 수 있다.
trailing 제약은 왼쪽 버튼과 같은 방식으로 Safe Area를 기준으로 추가한다.

버튼 두 개가 원하는 위치에 표시된다.

3

좌측 하단의 버튼은 Stack view를 사용해 만들 수도 있다.

bottom에 10pt 제약을 추가하고 너비와 높이를 적당히 조절한다.
추가하지 않은 leading 제약은 좌측 상단 버튼과 연결해 leading 제약을 동기화한다.

Stack View의 Attribute에서 Distribution을 Fill Equally로 변경하고,
Spacing을 10pt로 설정한다.
이렇게 되면 두 버튼은 10의 간격으로 동일한 크기로 표시되게 된다.

각각의 버튼을 다른 버튼들과 마찬가지로 필요한 이미지로 교체한다.

4

상단 Container에서 필요한 마지막 요소인 상태 메시지를 구현한다.
상태 메시지는 항상 Container의 중앙에 표시된다.

상단 Container에 Label을 하나 추가하고, Attribute를 수정한다.
폰트 색을 적절히 변경하고, 가운데 정렬을 적용했으며, Lines를 3으로 설정했다.
이 경우 최대 3줄까지만 표시되는 것으로 가정했다.
Line Break를 Word Wrap으로 설정해 단어 단위로 줄 바꿈이 일어나도록 한다.
상단 컨테이너의 중앙에 표시될 수 있도록 수직, 수평 가운데 정렬 제약을 추가한다.

지금 상태로도 문제는 없어 보이지만 Label의 최대 너비가 존재하지 않아 긴 문자열을 입력하면 화면을 벗어나는 경우가 생긴다.
따라서 너비를 제한하고, 다양한 기기에 대응할 수 있게끔 제약을 추가한다.

적당한 leading과 trailing 제약을 추가한다.
이 시점에서 수평방향 가운데 정렬은 의미가 없어지지만 중복돼도 상관없다.
top과 bottom 제약도 추가하는 것이 좋지만 지금은 최대 3줄까지만 출력하는 것을 가정해 다른 UI에 가릴 가능성이 적다.
따라서 생략해도 무관하다.

Label의 위에 표시되는 하얀 선 선을 표시하기 위해 이미지를 사용할 수도 있지만,
View를 사용해 구현하는 것도 가능하다.

View를 하나 추가하고 오른쪽 클릭으로 드래그해 Label과 연결한다.

표시되는 팝업에서 Vertical Spacing과 Center Horizontally를 적용한다.

너비를 20pt로, 높이를 1pt로 제한하도록 제약을 추가하면 Label 위에 하얀 선 선이 배치된다.

마지막으로 View의 User Interaction Enabled 속성을 해제해 터치가 발생하지 않도록 한다.

5

지금과 같이 밝은 배경에서는 상태 메시지가 흰색일 경우 이를 확인하기가 어렵다.
따라서 배경화면을 조금 어둡게 처리해 이를 해결해야 한다.

배경을 표시하는 View 위에 새로운 View를 추가한다.

배경을 어둡게 하기 위해 배경색을 검은색으로 설정하고, Alpha를 변경해 투명도를 정한다.
해당 View는 이벤트를 처리할 필요가 없으므로 User Interaction Enabled도 해제한다.

View를 오른쪽 클릭으로 드래그해 Image View와 연결한다.

표시되는 팝업에서 Center Vartivally, Center Horizontally, Equal Widths, Equal Heights를 적용해
Image View와 같은 조건으로 표시되도록 제약을 추가한다.

여기까지 하면 상단 Container는 모습을 갖추기 시작한다.

6

하단 Container에 이름과 메일 주소를 출력하는 레이블을 구성한다.

이름을 표시할 Label을 추가하고 top, leading, trailing 제약을 추가한다.
이때 top에는 프로필 사진이 추가 될 것을 가정하여 적당한 값을 부여해야 한다.
만약 이름이 절대로 화면 밖으로 나가지 않는다면 leading과 trailing 제약 없이 Intrinsic Size를 사용하고,
수평 가운데 정렬만 써도 무관하다.

mail 주소는 터치할 수 있을 것을 가정하고 Button으로 표현했다.
top 제약만 8pt를 부여해 이름과의 간격을 지정하고, 수평 방향 가운데 정렬을 적용했다.
email의 테두리는 interface builder에서 그리지 않고 코드를 통해 구현한다.

지금 상황에서 테두리를 그리면 Intrinsic Size를 사용하기 때문에 텍스트에 너무 붙어 부자연스럽다.

Button의 Size Inspector에서 Contant Insets을 조절한다.

//
//  ViewController.swift
//  Auto Layout Copycat
//
//  Created by Martin.Q on 2021/12/14.
//

import UIKit

class ViewController: UIViewController {
	
	@IBOutlet weak var mailBtn: UIButton!
	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		mailBtn.layer.cornerRadius = 3
		mailBtn.layer.borderColor = UIColor.lightGray.cgColor
		mailBtn.layer.borderWidth = 1
		
	}


}

email 버튼을 코드에 outlet으로 연결하고,
모서리를 둥글게 하여 테두리를 그린다.

의도한 대로 표시되는 것을 볼 수 있다.
보통의 메일이 화면을 벗어나는 경우는 잘 없지만 가능성이 있다면 좌우의 제약을 추가해 주는 것이 좋다.

이를 위해 위와 같이 제약을 추가하면 해당 값으로 고정이 되기 때문에 email의 길이와는 무관하게 너무 커지게 된다.

따라서 두 제약을 Equal에서 Greater than or Equal로 변경한다.

둘의 차이는 위와 같다.

7

하단 버튼을 추가한다.

Stack View를 추가하고 제약을 추가한다.
이때 하단 Container의 Margin에 맞춰 제약을 추가했다.

버튼을 추가하기 위해 기본 버튼을 사용하면 디자인의 제약이 생긴다.
따라서 버튼들을 각각 View로 구성해 이미지와 이름을 별도로 구성한다.

즉 Stack View 아래에 View를 하나 더 추가하고,
해당 View 아래에 Image View와 Label을 추가한다.

각각의 제약은 위와 같다.

이후 해당 View가 Button의 역할을 할 수 있도록 View 영역 전체에 Button을 추가한다.
추가한 버튼은 Title을 지우고, 모든 방향의 제약을 0으로 설정한다.

만들어진 버튼을 복사해 세 개의 버튼을 만든다.
Stack의 방식을 Fill Equally로 변경하고 Spacing을 20pt로 공백을 생성한다.

이제 버튼을 꾸미면 되는데 오류가 발생한다.
Image View와 Label은 둘 다 Intrinsic Size를 사용한다.
이미지가 지정되어있지 않으면 Image View의 높이를 줄이면 되니 문제가 없지만,
지정하는 순간 Intrinsic Size가 생겨 충돌이 일어난다.
즉, 크기에 따라 둘 중 하나는 크기가 축소되어야 한다.
그렇다. CHCR이 필요하다.

순서대로 Image View, Label인데 둘이 완전히 동일해 자동으로 줄어들지 못한다.

즉 CHCR을 조정하거나 Label의 높이를 고정해 필수 제약을 생성하는 것이다.
이번에는 Image View의 CH를 조정한다.

같은 방식으로 버튼 3개를 전부 수정하면 된다.

8

프로필 이미지는 Container의 경계에 위치한다.

따라서 어떤 Container에도 포함되지 않도록 Root View에 추가해야 한다.

원하는 이미지와 배경색을 지정한다.

높이와 너비를 고정하고, 수평 방향에서 가운데 정렬을 적용한다.

Image View를 상단 Container에 드래그 해 연결하고

Bottom 제약을 추가한다.

이렇게 되면 Image View의 bottom이 상단 Container의 bottom과 일치하도록 배치된다.
이제는 제약을 수정해 위치를 조금 조정해 주면 된다.

제약의 Constant 값을 수정해 위치를 조정하면 배치는 완료됐다.

//
//  ViewController.swift
//  Auto Layout Copycat
//
//  Created by Martin.Q on 2021/12/14.
//

import UIKit

class ViewController: UIViewController {
	
	@IBOutlet weak var mailBtn: UIButton!
	@IBOutlet weak var profileImg: UIImageView!
	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		mailBtn.layer.cornerRadius = 3
		mailBtn.layer.borderColor = UIColor.lightGray.cgColor
		mailBtn.layer.borderWidth = 1
		
		profileImg.layer.cornerRadius = profileImg.bounds.width * 0.5
		
	}


}

Image View를 코드에 연결하고, cornerRadius를 Image View의 절반으로 지정한다.

그러면 요구사항을 만족한 화면을 볼 수 있다.

지금 상태에서는 Landscape 모드에서 상태 메시지를 가리게 되고,
좌우로 공백이 지나치게 많다.
이를 위해선 Adaptive Layout이 필요하다.

'학습 노트 > iOS (2021)' 카테고리의 다른 글

162. Auto Layout Practice #2  (0) 2021.12.16
161. Adaptive Layout  (0) 2021.12.16
159. Layout Margin & Layout Guide  (0) 2021.12.14
157 ~ 158. Constraint  (0) 2021.12.12
156. Auto Layout Interface Builder Technique  (0) 2021.12.08