프로젝트를 진행하던 중 사용자의 배송 물품을 조회하는 목록을 만들어야 했다.
우선 화면은 다음과 같다.

처음에는 단순히 따로 custom item을 만들지 않고 swift에서 기본으로 제공하는 list와 onDelete등을 사용해서 해당 기능을 구현하고자 했다. 그러나 onDelete()를 활용해서 삭제를 구현하면 각 item의 끝에서 삭제하기 버튼이 등장하지 않고, 리스트의 끝에서 삭제하기 버튼이 나타나는 현상과 여러 UI를 구성하는 데 있어서 제약이 있었다.
그래서 custom item을 활용해 해당 기능을 구현하기로 했다.
MainView
struct PackListTabView: View {
@StateObject var packInfoDatas = PackInfoViewModel()
var body: some View {
VStack {
TopOfTabView(title: "배송 목록")
SearchTextFieldView(title: "검색")
List {
ForEach(packInfoDatas.packInfoModels) { packInfo in
PackListItemView(packInfoModel: $packInfoDatas.packInfoModels[getIndex(packInfoModel: packInfo)], packInfoModels: $packInfoDatas.packInfoModels)
}
.background(
ColorManager.background
.cornerRadius(10)
.shadow(color: .black.opacity(0.05), radius: 10, x: 0, y: 2)
)
.frame(height: 76)
Spacer()
}
.padding(.leading, -17)
.listStyle(PlainListStyle())
Spacer()
}
}
func getIndex(packInfoModel: PackInfoModel) -> Int {
return packInfoDatas.packInfoModels.firstIndex { (packInfoModel1) -> Bool in
return packInfoModel.id == packInfoModel1.id
} ?? 0
}
}
item swipe를 위한 구조체
struct SwipeItemView<Content: View, Right: View>: View {
var content: () -> Content
var right: () -> Right
var itemHeight: CGFloat
init(content: @escaping () -> Content, right: @escaping () -> Right, itemHeight: CGFloat) {
self.content = content
self.right = right
self.itemHeight = itemHeight
}
@State var hoffset: CGFloat = 0
@State var anchor: CGFloat = 0
let screenWidth = UIScreen.main.bounds.width
var anchorWidth: CGFloat { screenWidth / 3 }
var swipeTreshold: CGFloat { screenWidth / 15 }
@State var rightPast = false
var drag: some Gesture {
DragGesture()
.onChanged { value in
withAnimation {
hoffset = anchor + value.translation.width
if abs(hoffset) > anchorWidth {
if rightPast {
hoffset = -anchorWidth
}
}
if anchor < 0 {
rightPast = hoffset < -anchorWidth + swipeTreshold
} else {
rightPast = hoffset < -swipeTreshold
}
}
}
.onEnded { value in
withAnimation {
if rightPast {
anchor = -anchorWidth
} else {
anchor = 0
}
hoffset = anchor
}
}
}
var body: some View {
GeometryReader { geo in
HStack(spacing: 0) {
content()
.frame(width: geo.size.width + 20)
right()
.frame(width: anchorWidth)
.zIndex(1)
.clipped()
}
.offset(x: hoffset)
.frame(maxHeight: 76)
.contentShape(Rectangle())
.gesture(drag)
.clipped()
}
}
}
list의 각각의 item을 위한 view
struct PackListItemView: View {
@Binding var packInfoModel: PackInfoModel
@Binding var packInfoModels: [PackInfoModel]
var body: some View {
SwipeItemView(content: {
VStack {
Button(action: {
}, label: {
HStack {
Spacer()
VStack {
HStack {
Image(systemName: "circle.fill")
.resizable()
.frame(width: 44, height: 44)
VStack(alignment: .leading) {
Text(packInfoModel.packageName)
.font(FontManager.title3)
.padding(.bottom, 2)
HStack {
Text(packInfoModel.packageNumber)
.font(FontManager.caption1)
.foregroundColor(ColorManager.foreground1)
Spacer()
Text(packInfoModel.packageArrvieTime)
.font(FontManager.caption1)
.foregroundColor(ColorManager.foreground1)
.padding(.trailing, 8)
Text(packInfoModel.packageState)
.font(FontManager.caption2)
.foregroundColor(ColorManager.primaryColor)
}
}
.padding(.leading, 16)
Spacer()
}
.padding(16)
}
Spacer()
}
})
.buttonStyle(ListButtonStyle())
}
}, right: {
VStack {
Button(action: {
deletePackInfoModel()
}, label: {
ZStack {
Rectangle()
.fill(.red)
HStack {
Text("삭제하기")
.font(FontManager.body2)
.foregroundColor(.white)
Image(systemName: "trash.fill")
.resizable()
.frame(width: 20, height: 24)
.foregroundColor(.white)
}
}
})
.buttonStyle(CapsuleButtonStyle())
.cornerRadius(10)
}
.padding(.trailing, 20)
}, itemHeight: 76)
.listRowSeparator(.hidden)
}
func deletePackInfoModel() {
packInfoModels.removeAll { (packInfoModel) -> Bool in
return self.packInfoModel.id == packInfoModel.id
}
}
}
content안에 자신이 원하는 view를 구성하면 된다. 그리고 리스트 안에 버튼을 넣으면 그 버튼만 클릭 되는 것이 아니라 리스트의 아이템 전체가 클릭되는 현상이 발생했다. 이럴때는 ButtonStyle을 주어서 해결하면 된다. (custom buttonstyle을 만들거나 기존에 주어진 buttonstyle을 사용해도 된다.)
'iOS 개발 > SwiftUI' 카테고리의 다른 글
[SwiftUI] firebase firstore 데이터 CRUD (0) | 2023.01.31 |
---|---|
[SwiftUI] - Firebase Firestore 데이터 저장하기, Cannot convert value of type 'TrackInfo' to expected argument type '[String : Any] 오류 해결 (1) | 2023.01.23 |
[SwiftUI] - 설정 화면(View) 구현하기 (2) | 2023.01.07 |
[SwiftUI] - TabView, Progress bar를 활용한 메인 화면 구성 (0) | 2023.01.06 |
[SwiftUI] - 화면이 나타났을 때 Action 주기 (0) | 2023.01.03 |
프로젝트를 진행하던 중 사용자의 배송 물품을 조회하는 목록을 만들어야 했다.
우선 화면은 다음과 같다.

처음에는 단순히 따로 custom item을 만들지 않고 swift에서 기본으로 제공하는 list와 onDelete등을 사용해서 해당 기능을 구현하고자 했다. 그러나 onDelete()를 활용해서 삭제를 구현하면 각 item의 끝에서 삭제하기 버튼이 등장하지 않고, 리스트의 끝에서 삭제하기 버튼이 나타나는 현상과 여러 UI를 구성하는 데 있어서 제약이 있었다.
그래서 custom item을 활용해 해당 기능을 구현하기로 했다.
MainView
struct PackListTabView: View {
@StateObject var packInfoDatas = PackInfoViewModel()
var body: some View {
VStack {
TopOfTabView(title: "배송 목록")
SearchTextFieldView(title: "검색")
List {
ForEach(packInfoDatas.packInfoModels) { packInfo in
PackListItemView(packInfoModel: $packInfoDatas.packInfoModels[getIndex(packInfoModel: packInfo)], packInfoModels: $packInfoDatas.packInfoModels)
}
.background(
ColorManager.background
.cornerRadius(10)
.shadow(color: .black.opacity(0.05), radius: 10, x: 0, y: 2)
)
.frame(height: 76)
Spacer()
}
.padding(.leading, -17)
.listStyle(PlainListStyle())
Spacer()
}
}
func getIndex(packInfoModel: PackInfoModel) -> Int {
return packInfoDatas.packInfoModels.firstIndex { (packInfoModel1) -> Bool in
return packInfoModel.id == packInfoModel1.id
} ?? 0
}
}
item swipe를 위한 구조체
struct SwipeItemView<Content: View, Right: View>: View {
var content: () -> Content
var right: () -> Right
var itemHeight: CGFloat
init(content: @escaping () -> Content, right: @escaping () -> Right, itemHeight: CGFloat) {
self.content = content
self.right = right
self.itemHeight = itemHeight
}
@State var hoffset: CGFloat = 0
@State var anchor: CGFloat = 0
let screenWidth = UIScreen.main.bounds.width
var anchorWidth: CGFloat { screenWidth / 3 }
var swipeTreshold: CGFloat { screenWidth / 15 }
@State var rightPast = false
var drag: some Gesture {
DragGesture()
.onChanged { value in
withAnimation {
hoffset = anchor + value.translation.width
if abs(hoffset) > anchorWidth {
if rightPast {
hoffset = -anchorWidth
}
}
if anchor < 0 {
rightPast = hoffset < -anchorWidth + swipeTreshold
} else {
rightPast = hoffset < -swipeTreshold
}
}
}
.onEnded { value in
withAnimation {
if rightPast {
anchor = -anchorWidth
} else {
anchor = 0
}
hoffset = anchor
}
}
}
var body: some View {
GeometryReader { geo in
HStack(spacing: 0) {
content()
.frame(width: geo.size.width + 20)
right()
.frame(width: anchorWidth)
.zIndex(1)
.clipped()
}
.offset(x: hoffset)
.frame(maxHeight: 76)
.contentShape(Rectangle())
.gesture(drag)
.clipped()
}
}
}
list의 각각의 item을 위한 view
struct PackListItemView: View {
@Binding var packInfoModel: PackInfoModel
@Binding var packInfoModels: [PackInfoModel]
var body: some View {
SwipeItemView(content: {
VStack {
Button(action: {
}, label: {
HStack {
Spacer()
VStack {
HStack {
Image(systemName: "circle.fill")
.resizable()
.frame(width: 44, height: 44)
VStack(alignment: .leading) {
Text(packInfoModel.packageName)
.font(FontManager.title3)
.padding(.bottom, 2)
HStack {
Text(packInfoModel.packageNumber)
.font(FontManager.caption1)
.foregroundColor(ColorManager.foreground1)
Spacer()
Text(packInfoModel.packageArrvieTime)
.font(FontManager.caption1)
.foregroundColor(ColorManager.foreground1)
.padding(.trailing, 8)
Text(packInfoModel.packageState)
.font(FontManager.caption2)
.foregroundColor(ColorManager.primaryColor)
}
}
.padding(.leading, 16)
Spacer()
}
.padding(16)
}
Spacer()
}
})
.buttonStyle(ListButtonStyle())
}
}, right: {
VStack {
Button(action: {
deletePackInfoModel()
}, label: {
ZStack {
Rectangle()
.fill(.red)
HStack {
Text("삭제하기")
.font(FontManager.body2)
.foregroundColor(.white)
Image(systemName: "trash.fill")
.resizable()
.frame(width: 20, height: 24)
.foregroundColor(.white)
}
}
})
.buttonStyle(CapsuleButtonStyle())
.cornerRadius(10)
}
.padding(.trailing, 20)
}, itemHeight: 76)
.listRowSeparator(.hidden)
}
func deletePackInfoModel() {
packInfoModels.removeAll { (packInfoModel) -> Bool in
return self.packInfoModel.id == packInfoModel.id
}
}
}
content안에 자신이 원하는 view를 구성하면 된다. 그리고 리스트 안에 버튼을 넣으면 그 버튼만 클릭 되는 것이 아니라 리스트의 아이템 전체가 클릭되는 현상이 발생했다. 이럴때는 ButtonStyle을 주어서 해결하면 된다. (custom buttonstyle을 만들거나 기존에 주어진 buttonstyle을 사용해도 된다.)
'iOS 개발 > SwiftUI' 카테고리의 다른 글
[SwiftUI] firebase firstore 데이터 CRUD (0) | 2023.01.31 |
---|---|
[SwiftUI] - Firebase Firestore 데이터 저장하기, Cannot convert value of type 'TrackInfo' to expected argument type '[String : Any] 오류 해결 (1) | 2023.01.23 |
[SwiftUI] - 설정 화면(View) 구현하기 (2) | 2023.01.07 |
[SwiftUI] - TabView, Progress bar를 활용한 메인 화면 구성 (0) | 2023.01.06 |
[SwiftUI] - 화면이 나타났을 때 Action 주기 (0) | 2023.01.03 |