📱 플젝 개발일지

[SwiftUI] LazyVGrid 레이아웃 이슈

dev_zoe 2025. 12. 28. 18:06
반응형

사이드 프로젝트 진행 중, LazyVGrid 레이아웃에서 다음과 같은 문제가 있었다.

문제

아래 사진이 구현해야 하는 피그마 디자인이고

현재 검색하면 아래와 같은 레이아웃 이슈가 발생한다. item끼리 겹쳐보이고, 높이도 일정하지 않다.

 

 

해결 과정

1️⃣ 우선 높이를 고정하기 위해 캐릭터의 이름을 나타내는 Text에 .lineLimit(1) 을 주어 한줄만 보이게 하고,

이미지뷰에 frame height 속성으로 높이를 고정시켰다. 

 

2️⃣ 위와 같이 GridItem 끼리 겹치는 문제는 GridItem의 설정과 GridItem 내부의 View의 어떤 부분이 원인일까?

 

- GridItem 코드

  private let columns: [GridItem] = [
    .init(.flexible(minimum: 50, maximum: .infinity)),
    .init(.flexible(minimum: 50, maximum: .infinity)),
    .init(.flexible(minimum: 50, maximum: .infinity)),
  ]

 

 

GridItem은 "전체 그리드 뷰에서 행과 열 각각 몇개로 구성할 것인지, 한 칸의 너비를 얼마로 계산할 것인지" 이므로
열의 갯수를 고정하고 그 안에서 크기를 유동적으로 결정하는 flexible, 그리고 maximum: .infinity는 문제가 없는 부분으로 보인다.

(GridItem의 adaptive vs flexible 비교: https://eunjin3786.tistory.com/577)

다만 위 피그마 화면같이 GridItem 끼리 차이가 일정하게 존재해야하므로 spacing을 주었다.

 

- 내부 View 코드

      KFImage(URL(string: target.imageURL))
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(height: 125)
        .clipShape(.rect(cornerRadius: .r8))
        .overlay {
          RoundedRectangle(cornerRadius: .r8)
            .strokeBorder(selectedIndex != nil ?.green600 : .gray300, lineWidth: 1)
        }

 

 

그리고 GridItem이 너비를 제안하고, 그 안에서 어떻게 너비를 맞출지는 내부 View에서 결정하게 된다.

여기서 너비를 지정해주어야하는데, "GridItem이 3등분하는 선에서 늘어날 수 있는 무한대까지 늘어날래" 라고 했는데

        .frame(maxWidth: .infinity)


위와 같이 내부 View의 frame도 maxWidth: .infinity 속성을 주게되면 이 둘이 충돌하면서 위와 같이 겹칠 수도 있다.

 

https://developer.apple.com/documentation/swiftui/view/frame(minwidth:idealwidth:maxwidth:minheight:idealheight:maxheight:alignment:)

 

frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) | Apple Developer Documentation

Positions this view within an invisible frame having the specified size constraints.

developer.apple.com

그리고 해당 문서를 읽어보면 "minimum이나 maximum 제약을 주지 않으면 자식 요소의 크기 동작에 의존한다" 라고 되어있다.

 

여기서 minWidth를 0으로 추가적으로 주게되면, 

"부모가 100pt를 주면 "내 내용물은 120pt지만, 내 최소 기준이 0이니까 100pt에 맞출게"와 같이 유연하게 대응하게 된다.

그래서 정리하자면 다음과 같이 수정한다.

    VStack(alignment: .leading, spacing: .zero) {
      KFImage(URL(string: target.imageURL))
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(minWidth: 0, maxWidth: .infinity)    // 추가
        .frame(height: 125)    // 추가
        .clipShape(.rect(cornerRadius: .r8))
        .overlay {
          RoundedRectangle(cornerRadius: .r8)
            .strokeBorder(selectedIndex != nil ?.green600 : .gray300, lineWidth: 1)
        }
      
      Text(target.name)
        .designedFont(.body2, weight: .bold)
        .foregroundStyle(.gray900)
        .lineLimit(1)    // 추가
        .padding(.top, .s6)
        .frame(maxWidth: .infinity, alignment: .leading)
    }
    
    private let columns: [GridItem] = [
    .init(.flexible(minimum: 50, maximum: .infinity), spacing: .s12),    // 수정 (spacing 추가)
    .init(.flexible(minimum: 50, maximum: .infinity), spacing: .s12),
    .init(.flexible(minimum: 50, maximum: .infinity)),
  ]

 

 

주어진 GridItem 사이즈에 맞게 유동적으로 사이즈가 정해져서 나옴을 확인할 수 있다.

반응형