-
Notifications
You must be signed in to change notification settings - Fork 3
Combine을 이용힌 chaining (feat. SearchView)
검색화면에서 가장 신경 써야하는 부분은 SearchBar/TextField일 것 같아요
물론... 사용자가 "검색"이라는 버튼을 누를때, 혹은 keyboard에서 return을 누르는 순간에만 검색을 위한 네트워킹을 하는게 가장 쉬운 해결책일 것 같아요
그런데 이렇게하면 자동완성/ 검색어 추천 과 같은 기능은 추가할수가 없습니다
그렇다면
현재 User가 입력받은 String의 바뀜을 어떻게 감지하고,
바뀐 값을 어떻게 조절하며, 어떻게 넘겨야 할까요???
원하는것
먼저 현재 입력받은 string과 네트워킹을 담당한 ViewModel이 필요하겠네요 그리고 string에 published property wrapper를 씌울게요
class SearchViewModel: ObservableObject {
@Published var searchText = ""
그리고 SearchView가 stateobject로 해당 viewmodel의 주인으로써 갖게 합니다
struct SearchView: View {
@StateObject private var viewModel = SearchViewModel()
private let layout = [GridItem(.flexible())]
var body: some View {
ScrollView(showsIndicators: false) {
LazyVGrid(columns: layout,
spacing: 20,
pinnedViews: [.sectionHeaders]) {
Section(header: SearchBarView(viewModel: viewModel)) {
if viewModel.isEditing {
SearchAfterView(viewModel: viewModel)
.animation(.easeIn)
.transition(.slide)
} else {
SearchBeforeView()
}
}
}
}.navigationTitle("검색")
.padding()
}
}
subview에게는 observedObject로 viewmodel을 공통적으로 '갖게' 하겠습니다
struct SearchBarView: View {
@ObservedObject var viewModel: SearchViewModel
var body: some View {
HStack {
TextField("검색어를 입력해 주세요.", text: $viewModel.searchText) { isEditing in
viewModel.isEditing = isEditing
}
'
'
'
다행히 SwiftUI에서 제공하는 TextField는 Binding이 매우 편리하게 됩니다.
TextField("검색어를 입력해 주세요.", text: $viewModel.searchText)
그려면 binding을 통해서 textfield에 있는 text가 변하면, 자동으로viewmodel에 있는, searchtext도 업데이트가 됩니다 이제는 변한 searchText를 변형하며, 값을 넘겨서 fetch까지 해야할 차례입니다.
@Published property wrapper가 붙여진 searchText는 바뀔때, 현재 string값의 "알림"을 보내게 되는데,
저는 그 string을 받아서 fetch
를 하고싶습니다
var subscription: Set<AnyCancellable> = []
override init() {
super.init()
$searchText
.sink { (_) in
} receiveValue: { searchText in
self.fetch(searchText)
}
.store(in: &subscription)
}
이떄 조심해야 할 점은, subscription을 store해주는것을 잊으면 안됩니다!
If you create a custom Subscriber, the publisher sends a Subscription object when you first subscribe to it. Store this subscription, and then call its cancel() method when you want to cancel publishing. (애플 공식 문서)
combine을 이용하면 마치 swiftui에서 view에게 modifier를 적용했던것과 비슷한 흐름으로 operator를 chaining 할 수 있습니다.
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
검색화면에서는 search text가 자주 바뀌게 되는데 매번 바꿀때 마다 네트워킹을 하는건 너무 낭비가 될것 같네요 그래서 debounce operator를 걸어서 0.5초가 지날때만 보내게 만들었어요
.map({ (string) -> String? in
self.validate(string)
})
.compactMap { $0 }
이부분은 비어 있는 string은 아래에 있는 sink로 넘어갈수 없게 막는 부분입니다.
class SearchViewModel: ObservableObject {
@Published var searchText = ""
var subscription: Set<AnyCancellable> = []
override init() {
super.init()
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.removeDuplicates()
.map({ (string) -> String? in
self.validate(string)
})
.compactMap { $0 }
.sink { (_) in
} receiveValue: { searchText in
self.fetch(searchText)
}
.store(in: &subscription)
}
여태껏 combine을 네트워킹을 제외하고는 제대로 쓴적이 없는데, 이번에 기회에 조금 맛보기를 한것 같습니다
UIKit으로 작업을했으면, 값을 전달하고, 알려주고, 다시 전달하고.... 신경써야 할 부분이 상당히 많아졌을텐데 combine의 operator를 이용하니, 간결하고, 한눈에 흐름도 볼수 있어서 좋았습니다.