Dev.heestory
[SwiftUI/iOS] DragGesture를 이용한 사용자 Custom 화면 본문
멋쟁이 사자처럼 앱스쿨 1기를 수료하는 과정에서 1박2일로 해커톤을 진행하였다.
팀원들과 아이디어를 논의하였는데, 시기가 2023년 1월이여서 새해 목표에 관한 아이디어를 내신 분이 있었다.
버킷리스트를 작성하고 달성률을 보여주는 앱을 만들기로 하였다.
iOS 개발자를 꿈꾸고 있고 동시에 iOS 어플을 사용하는 사용자로써 앱을 사용할 때
기능도 중요하지만 아무리 좋은 기능이 있어도 UI가 별로면 잘 사용하지 않게되고 같은 기능이라면 더 예쁜 앱을 사용하게 된다.
해커톤 목표로 '사용자가 앱에 호감을 가질, 오래 사용할 수 있는 예쁜 UI의 앱'을 만들고자하였다.
앱 기획 단계에서 버킷리스트의 유래를 찾아봤는데 '죽다'라는 의미의 '양동이를 차다(Kick the Bucket)'란 영어 관용어로, 목을 매고 죽을 때 양동이 위에 올라가서 목을 밧줄(노끈)에 걸고 양동이를 발로 차서 죽는 것에서 유래했다고 한다.
유래는 좀 무서운 관계로 단순히 버킷을 양동이로 받아들이고 새해 목표,꿈을 '별'로 표현하기로 했다.

그 결과로 '양동이에 갇힌 별'을 앱 아이콘으로 정했고 양동이를 차서 별을 하늘에 띄워주는 컨셉을 정하였다.
또한 앱 시작화면을 사용자 커스텀 화면으로 하여 사용자가 직접 앱 화면을 꾸미면서 앱에 애정을 느끼고 앱을 쓰고싶게 만들기로 하였다.
버킷리스트를 달성하면 화면에 띄워줄 수 있는 별이 생기고, 제스처를 통하여 사용자가 화면을 커스텀할 수 있게끔 만들기로 하였다.
사용자 커스텀 화면을 만든 과정을 자세하게 살펴보자

위 이미지는 버킷리스트에 관한 구조체이다.
title이 버킷리스트의 내용이고, 만약 사용자가 달성하였음을 체크하게 되면 isCheck가 true값이 되어 사용자가 화면에 별을 띄울 수 있는 상태가 된다.

위 이미지가 화면에 별을 추가하는 뷰 이다. 추가할 수 있는 별의 개수가 나오고 추가할 수 있는 별의 유무에 따라 버튼의 텍스트를 다르게 주었다. 별을 추가하는 뷰에 대한 코드를 자세하게 살펴보자
화면에 띄울 별 추가
이 뷰에서는 아래와 같은 프로퍼티들이 사용된다.
@Environment(\.presentationMode) var addingStar // 창을 닫아주기 위해 사용한 프로퍼티
//커스텀 화면 View의 이벤트를 처리하는 메소드들을 모아놓은 ViweModel
@StateObject var bucketStore: BucketStore
//현재 화면에 떠 있는 별들
@Binding var starList: [Bucket]
//화면에 띄울 수 있지만 아직 떠있지않는 별들
@Binding var waitingStarList: [Bucket]
//별들의 위치 좌표 정보 - 드래그한 만큼 별이 움직이도록 binding에 사용됨
@Binding var starPosArr: [CGSize]
// 지금까지 드래그 된 값을 기록하고 있는 Property
@Binding var accumlatedOffset: [CGSize]
// 선택된 별의 모양에 대한 Int값
@State private var selectedStar: Int?
추가/닫기 버튼을 눌렀을 때 동작하는 코드를 살펴보자
Button {
// 추가할 별이 있는 경우
if waitingStarList.count > 0 {
// 화면에 띄울 별의 초기 위치 좌표를 0,0으로 설정
starPosArr.append(.zero)
// 드래그 되기 전이므로 0,0으로 초기화
accumlatedOffset.append(.zero)
// 화면에 띄웠으므로 화면에 떠있는 별들 배열에 추가
starList.append(waitingStarList[0])
// 화면에 띄울 별 모양 선택된 값으로 넣어주기
waitingStarList[0].shape = selectedStar ?? 0
// DB 업데이트
bucketStore.updateStar(waitingStarList[0].id, isFloat: true, shape: selectedStar ?? 0)
// waitingStarList에서 삭제
waitingStarList.removeFirst()
addingStar.wrappedValue.dismiss()
}else{
addingStar.wrappedValue.dismiss()
}
} label: {
waitingStarList.count == 0 ? Text("닫기") : Text("추가")
}
추가될 별의 배열의 요소 개수를 활용하여 0보다 클 때와 아닐때를 나누어 코드를 작성하였다.
0보다 작거나 같을때는 추가할 수 있는 별이 없으므로 창을 닫아주게 되고
0보다 클 경우 사용자 커스텀 화면에 별이 추가된다. 이때 아래의 4가지를 고려해줘야한다.
1. 별의 초기 위치 좌표값 -> 0,0 으로 지정
2. 별의 좌표 개수와 맞춰주기 위해 드래그 된 값을 저장하는 배열에 0,0 append
3. isFloat true로, 별의 shape 선택한 모양으로 DB 업데이트
4. starList(떠 있는 별 배열)에 append, waitingList(달성 O, 화면에 떠있음X 별들 배열)에서 remove
이들을 고려해서 위의 코드를 작성하였다.
별을 추가하였으니 사용자가 직접 드래그를 통해 별의 위치를 조정해 화면을 커스텀 할 수 있도록 만들어보자
사용할 제스처는 DragGesture이다.
제스처 사용법
해당 뷰에 제스처 인식기를 추가하면 제스처가 감지된다.
제스처 인식기는 gesture() 수정자를 사용하여 뷰에 추가되고, 추가될 제스처 인식기가 수정자에 전달된다.
.gesture()(
DragGesture()
.onChanged { _ in self.isDragging = true }
.onEnded { _ in self.isDragging = false }
}
onChanged는 제스처가 처음 인식되었을 때 호출되고 제스처가 끝날때까지 제스처의 값이 변할 때마다 호출된다.
onEnded는 제스처가 성공적으로 끝날 때 수행될 코드를 담도록 구현한다.
사용자 커스텀 화면에 사용될 Drag 제스처를 통해 별의 위치를 옮기는 코드를 살펴보자.
드래그를 통해 별을 이동시켜야했으므로 별 이미지에 gesture를 달아주었다.
ForEach(Array(starList.enumerated()), id:\.offset) { idx, star in
Image("\(star.shape)")
.resizable()
.frame(width: 50, height: 50)
.offset(bucketStore.starPosArr[idx])
.gesture(
DragGesture()
.onChanged { value in
bucketStore.starPosArr[idx].width = accumlatedOffset[idx].width + value.translation.width
bucketStore.starPosArr[idx].height = accumlatedOffset[idx].height + value.translation.height
}
.onEnded { value in
accumlatedOffset[idx].width += value.translation.width
accumlatedOffset[idx].height += value.translation.height
bucketStore.updateStarPos(star.id , bucketStore.starPosArr[idx].width, bucketStore.starPosArr[idx].height)
}
)
}
별의 위치를 좌표는 bucketStore.starPosArr배열에 저장되어있으므로 .offset(bucketStore.starPosArr[idx])로 별의 위치를 잡아주었다.
다음으로 드래그할때 별이 같이 움직여야하므로 .onChanged 콜백에서 bucketStore.starPosArr[idx]에 변화하는 값을 넣어주었다.
제스처가 끝나면 변화한 별의 위치 좌표를 업데이트해주고 서버에 저장해주었다.
완성 화면

'iOS' 카테고리의 다른 글
| [Swift/iOS] MVVM 패턴을 공부하며 간단한 어플리케이션 만들기 (0) | 2023.07.29 |
|---|---|
| [iOS/Swift] MVC 패턴을 공부하며 간단한 어플리케이션 만들기 (0) | 2023.07.13 |
| [iOS] UI/UX 개선 과정 (0) | 2023.02.06 |
| [UIKit/iOS] 게시판을 만들며 알아보는 Alamofire (0) | 2023.01.25 |