본문 바로가기
iOS & macOS

[macOS] 이미지가 많은 뷰 성능 최적화 - LazyVGrid

by Bokoo14 2024. 1. 13.

macOS앱 개발을 하며, 뷰에 이미지를 굉장히 많이 로드해야 하는 상황이 발생했습니다. 

앱의 특성상, 저장된 이미지는 무조건 고화질NSImage변수에 저장되어있어야 하며, 모든 이미지들의 위치값을 갖고 있어야 하는 상황입니다. 

 

뷰에 수백개의 이미지들을 ScrollView에 넣고, 이미지들을 드래그하는 기능을 구현했습니다. 

문제는 모든 이미지들의 위치값을 갖고 있어야 하며, 이 위치값을 통해 드래그 기능을 구현하는 방식으로 개발을 진행하는 과정에서 뷰가 너무 많은 이미지를 한꺼번에 로드하는 바람에 앱의 성능이 현저하게 떨어지는 경험을 했습니다. 

 

최적화 전 - 한번에 모든 이미지를 불러와, 각 이미지들의 위치 값을 저장하는 방식

모든 이미지들의 위치값을 저장하기 위해 한번에 모든 이미지를 불러와, 각 이미지들의 위치 값을 저장하는 방식을 채택했습니다. 

 

GeometryReader를 통해 위치값을 저장하는 방식 + 필요할때마다 이미지를 로딩하는 방식으로 개발하면 뷰에 보여지지 않는 영역의 이미지의 위치값은 정확한 위치값을 가져오지 못하는 오류가 발생하였습니다. 

 

따라서, 한번에 모든 이미지를 불러와 위치값을 저장하는 방식으로 변경했습니다. 

 

Rectangles라는 class를 만들어 준 후 GeometryReader 사용하여 사각형들의 프레임 값을 가져와서 rectangles 배열에 저장하는방식입니다. 

이렇게 저장된 이미지들의 프레임 값은 나중에 드래그할때 사용합니다. 

class Rectangles {
	var content: [Int : CGRect] = [:]
}
.coordinateSpace(name: "container") // 좌표 공간 설정


.background( // 하나의 Rectangle의 프레임(CGRect)값을 가져와서 저장
	GeometryReader { gp -> Color in
    	rectangles.content[i] = gp.frame(in: .named("container"))
        return Color.clear
        }
)

 

 

앱 성능 저하 문제

위의 방식은 한번에 모든 이미지를 로딩하여, 앱의 UI가 멈추는 현상을 겪었습니다. 

이미지들의 위치값을 정확히 가져와야 하는 상황이라 어쩔 수 없이 저런 방식으로 구현하였지만, 테스트를 하며 앱의 성능이 너무 안좋고 사용성이 너무 떨어졌습니다. 

 

위치값을 저장하는 방식을 바꾸고, 이미지들을 필요할때만 로드하는 방식으로 바꿔 앱 성능 저하 문제를 해결했습니다. 

 

최적화 후 - 필요할때만 이미지를 불러오고, 각 이미지들의 위치 값을 계산식을 통해 계산하는 방식

계산식이 많으면 좋지 않지만, 수많은 이미지를 로딩하는 방식보다는, 이미지들의 위치값을 이미 계산하여 변수에 넣는 방식이 성능적인 측면에서 훨씬 좋을 것이라는 판단을 내렸습니다. 

 

이미지들의 크기와, spacing값은 항상 고정적이기 때문에 이미지의 개수만 알게 되면, 이미지들의 위치값은 계산식을 통해서도 미리 저장할 수 있었습니다. 

 

Lazy는 스크롤을 할 때 그 화면에 보이는 아이템이 동시에 다운로드 됨. 따라서 광대한 데이터 사용을 막을 수 있었습니다. 

(Lazy를 쓰지 않으면, 이미지가 많거나 고화질일수록 로드하는데 시간이 많이 소모)

뷰 동작 방식 - LazyVGrid

let columns: [GridItem] = [
        GridItem(.fixed(64), spacing: nil, alignment: .center),
        GridItem(.fixed(64), spacing: nil, alignment: .center),
        GridItem(.fixed(64), spacing: nil, alignment: .center)
]


LazyVGrid(columns: columns, alignment: .center, spacing: 20) { 
	ForEach(0..<selectedPDFImages.count, id: \.self) { i in 
    	// 생략
        // 이미지
    }
}

 

위치값 저장

따라서, 피그마에 위아래, 좌우의 spacing값을 변수에 저장해두고, 이미지들의 position을 담을 변수를 설정해주었습니다. 

    var gap = 36 // 위아래
    var space = 9 // 좌우
    @State var imageRects: [CGRect] = [] // 이미지들의 position을 담은 변수

 

 

onAppear될때 imageRects변수에 각각의 이미지들의 위치값을 저장해줄 수 있었습니다. 

뷰에 보여질 이미지의 크기, 행의 수와 spacing값은 항상 고정이라 쉽게 계산할 수 있었습니다. 

 .onAppear { 
 	// 생략
 	/// 이미지 offset관련
	var row = 0
	if selectedPDFImages.count%3 == 0 {
		row = selectedPDFImages.count/3
	} else {
		row = selectedPDFImages.count/3 + 1
	}
    
	imageRects = []
	for i in 0..<row { // 행
		for j in 0..<3 { // 열
			imageRects.append(CGRect(x: j*(Int(64)+space), y: i*(90+gap), width: 64, height: 90))
  		}
    }
}

 

 

 

최적화 후, 해당 뷰에서 스크롤이 UI 끊김없이 부드럽게 동작하고, 이미지 크기 또한 정확히 저장되었습니다.