Skip to content

Combine을 이용힌 chaining (feat. SearchView)

강병민 (Byungmin Kang) edited this page Dec 5, 2020 · 2 revisions

검색화면을 개발하면서...

검색화면에서 가장 신경 써야하는 부분은 SearchBar/TextField일 것 같아요

물론... 사용자가 "검색"이라는 버튼을 누를때, 혹은 keyboard에서 return을 누르는 순간에만 검색을 위한 네트워킹을 하는게 가장 쉬운 해결책일 것 같아요#

그런데 이렇게하면 자동완성/ 검색어 추천과 같은 기능은 추가할수가 없습니다

그렇다면

현재 User가 입력받은 String의 바뀜을 어떻게 감지하고,

바뀐 값을 어떻게 조절하며, 어떻게 넘겨야 할까요???

원하는것

Binding

먼저 현재 입력받은 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까지 해야할 차례입니다.

Combine Chaining

@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)
    }
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을 네트워킹을 제외하고는 제대로 쓴적이 없는데, 이번에 기회에 조금 맛보기를 한것 같아요

검색화면에서는 search text가 자주 바뀌게 되는데 매번 바꿀때 마다 네트워킹을 하는건 너무 낭비가 될것 같아요 그래서 debounce operator를 걸어서 0.5초가 지날때만 보내게 만들었어요

            .map({ (string) -> String? in
                self.validate(string)
            })
            .compactMap { $0 }

이부분은 비어 있는 string은 아래에 있는 sink로 넘어갈수 없게 막는 부분입니다.

UIKit으로 작업을했으면, 값을 전달하고, 알려주고, 다시 전달하고.... 신경써야 할 부분이 상당히 많아졌을텐데 combine의 operator를 이용하니, 간결하고, 한눈에 흐름도 볼수 있어서 좋았습니다.

Clone this wiki locally