Layout System

hyun·2025년 6월 11일
0

iOS

목록 보기
14/54

 용어

Safe Area

아이폰마다 생김새가 조금씩 다름
어떤 기기는 노치가 있고 어떤 기기는 없고
홈바나 상태 표시줄도 크기가 다를 수 있음

그래서 화면에서 안전하게 보여야 할 영역을 정해둔 것.

한 마디로

앱이 안전하게 내용을 보여줄 수 있는 구역

이걸 지켜서 UI를 만들면 어떤 기기에서 보더라도 잘려나가지 않고 깔끔하게 보여줄 수 있음

AutoLayout

기기 크기마다 화면이 다르게 생겼는데

ex) 작은 아이폰, 큰 아이패드...
그럴 때 각 기기에 맞춰서 UI 크기랑 위치를 자동으로 조정해주는 시스템.

예를 들어 사진을 화면 위쪽에 고정하고 싶을 때
그냥 그림 위치만 맞춰두면 기기 바뀌면 이상해지는데
위쪽에서 몇 만큼 떨어지게 해달라고 설정하면 기기 크기에 맞게 자동으로 조정됨

Constraint

오토 레이아웃은 어떻게 배치할지 규칙이 있어야 작동하는데
이 규칙이 Constraint.

top / bottom / left / right : 화면 위/아래/왼쪽/오른쪽과의 거리
leading / trailing : 왼쪽에서 오른쪽으로 읽는 나라에서는
leading = 왼쪽, trailing = 오른쪽
반대로 읽는 나라는 반대

이걸 설정해두면 어떤 기기든 내가 원하는 모양으로 유지됨

Margin vs Padding

Margin

👉 밖의 여백
요소와 요소 사이 간격을 띄우고 싶을 때 사용

Padding

👉 안의 여백
내용이 너무 테두리에 붙지 않게 안쪽 여백을 넣는 것

ex) 사진이 너무 테두리에 딱 붙어 있다고 가정하면 UI적으로 안 예쁘고 답답하니까 Padding을 넣어서 안쪽 공간을 확보하는 것

 UIKit 구성요소

UITableView

리스트 형태로 보여줄 때 사용

하나하나의 줄은 UITableViewCell

UICollectionView

그리드 형태도 만들 수 있음

하나하나의 박스는 UICollectionViewCell이

UIAlertController

사용자에게 메시지를 띄울 때 사용

정말 삭제할까요? 같은 확인창

UITextView

글을 입력하는 칸

댓글 쓰는 거..?

UIPageViewController

페이지를 넘기듯 옆으로 쓱쓱 넘기는 뷰

인스타그램 스토리 느낌인 듯


사실 지금까지 SwiftUI만 썼어서
UIKit을 다루는 게 처음....인데
UI를 다루게 돼서 좀 당황스러웠다..!!!!!!!
그래도 튜터님의 강의를 듣고 감을 잡아보긴 함
실습!!도 재미있었는데
올려도 되는지 솔직히 잘 모르겠지만
코드 분석만 좀 깔짝거려 보자면

 실습

contentView

contentView.snp.makeConstraints {
  $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(20)
}

상단, 좌우를 safeArea 기준으로 20pt 인셋을 줌

bottom은 설정하지 않아서 내부 내용 높이에 따라 크기 자동 결정

profileImageView

$0.top.leading.trailing.equalToSuperview()
$0.height.equalTo(profileImageView.snp.width).multipliedBy(0.75)

상단, 좌우는 contentView에 꽉 채움

높이는 가로 폭의 0.75배로 설정 → 비율 기반 제약

infoLayoutGuide

let infoLayoutGuide = UILayoutGuide()
infoLayoutGuide.snp.makeConstraints {
  $0.top.equalTo(profileImageView.snp.bottom).offset(20)
  $0.leading.trailing.bottom.equalToSuperview().inset(20)
}

이미지 아래부터 아래 여백까지 텍스트와 버튼들이 들어가는 가이드라인 설정

뷰가 아니라 가이드라인이라 화면에 보이지 않지만 정렬 기준으로 유용함

nameLabel + introLabel

nameLabel.snp.makeConstraints {
  $0.top.leading.equalTo(infoLayoutGuide)
}
introLabel.snp.makeConstraints {
  $0.top.equalTo(nameLabel.snp.bottom).offset(10)
  $0.leading.trailing.equalTo(infoLayoutGuide)
}

nameLabel은 infoLayoutGuide 왼쪽 상단 기준

introLabel은 nameLabel 아래에 10pt 여백으로 위치

introLabel은 좌우 꽉 채움 + 여러 줄 허용 (numberOfLines = 0)

Buttons (StackView)

let buttonStackView = UIStackView().then {
  $0.axis = .horizontal
  $0.spacing = 10
  $0.distribution = .fill
}

두 버튼을 수평으로 나열

spacing은 두 버튼 사이 여백

buttonStackView.snp.makeConstraints {
  $0.top.equalTo(introLabel.snp.bottom).offset(20)
  $0.leading.trailing.bottom.equalTo(infoLayoutGuide)
}

버튼 스택은 introLabel 하단에 위치

좌우는 infoLayoutGuide에 맞춤

bottom도 infoLayoutGuide로 설정해 contentView의 하단이 결정됨

Hugging Priorit

addToPlaylistButton.setContentHuggingPriority(.required, for: .horizontal)

두 버튼 중 addToPlaylistButton이 자신의 콘텐츠 넓이에 딱 맞춰지도록 우선순위 줌 → 버튼 크기 비율 조절에 사용

전체 코드 중 일부

contentView.snp.makeConstraints {
      // 카드 컨테이너 레이아웃
     $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(20)
    }

    profileImageView.snp.makeConstraints {
      // 프로필 이미지 레이아웃
        $0.top.leading.trailing.equalToSuperview()
        $0.height.equalTo(profileImageView.snp.width).multipliedBy(0.75)
    }

    nameLabel.snp.makeConstraints {
      // 이름 레이아웃 구성 (가능하면 infoLayoutGuide 사용해보기)
        $0.top.leading.equalTo(infoLayoutGuide)
    }

      introLabel.snp.makeConstraints {
          //        소개 레이아웃 구성 (가능하면 infoLayoutGuide 사용해보기)
          $0.top.equalTo(nameLabel.snp.bottom).offset(10)
          $0.leading.trailing.equalTo(infoLayoutGuide)
          
      }
      
      let buttonStackView = UIStackView().then {
          $0.axis = .horizontal
          $0.spacing = 10
          $0.alignment = .fill
          $0.distribution = .fill
    }
      buttonStackView.addArrangedSubview(followButton)
      buttonStackView.addArrangedSubview(addToPlaylistButton)
      contentView.addSubview(buttonStackView)
      
      buttonStackView.snp.makeConstraints {
          $0.top.equalTo(introLabel.snp.bottom).offset(20)
          $0.leading.trailing.bottom.equalTo(infoLayoutGuide)
      }
      addToPlaylistButton
          .setContentHuggingPriority(.required, for: .horizontal)
  }

원래 모르는 언어를 배울 땐 코드를 뜯어보며 배우라 하지 않았던가.
코드 분석을 하고 더 효율적인 코드를 생각하면서 학습하는 걸 좋아하는 편이라
이것도.. 해보려 함 솔직히 할 거 없는 거 같긴 한데 더 빠른 적응을 위해ㅐㅑㅐㅐㅐㅐㅐ

contentView.snp.makeConstraints {
    $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(20)
}

profileImageView.snp.makeConstraints {
    $0.top.leading.trailing.equalToSuperview()
    $0.height.equalTo(profileImageView.snp.width).multipliedBy(0.75)
}

nameLabel.snp.makeConstraints {
    $0.top.leading.equalTo(infoLayoutGuide)
}

introLabel.snp.makeConstraints {
    $0.top.equalTo(nameLabel.snp.bottom).offset(10)
    $0.leading.trailing.equalTo(infoLayoutGuide)
}

let buttonStackView = UIStackView().then {
    $0.axis = .horizontal
    $0.spacing = 10
    $0.alignment = .fill
    $0.distribution = .fill
}

[followButton, addToPlaylistButton].forEach {
    buttonStackView.addArrangedSubview($0)
}

contentView.addSubview(buttonStackView)

buttonStackView.snp.makeConstraints {
    $0.top.equalTo(introLabel.snp.bottom).offset(20)
    $0.leading.trailing.bottom.equalTo(infoLayoutGuide)
}

addToPlaylistButton.setContentHuggingPriority(.required, for: .horizontal)

뷰 생성은 then 블록으로 통일했고 (초기화랑 설정이 좀 더 깔끔하지 않을까 함)
배열로 addArrangedSubview 반복 제거함
한 줄에 제약 조건 묶기

0개의 댓글