TextField을 publisher로 활용하기

SteadySlower·2023년 4월 13일
0

iOS Development

목록 보기
15/38

이번 포스팅에서는 Combine과 TextField를 조합해서 사용해보도록 하겠습니다. TextField에 URL 주소를 입력하면 해당 URL로 네트워크 통신을 하는 코드를 만들어보겠습니다.

UIKit

UIKit에서 UITextField를 사용하는 방법부터 알아보겠습니다. UIKit 같은 경우는 Combine을 활용하고 있지 않으므로 NotificationCenter에서 Observer를 등록하고 publisher를 만들어야 합니다. 자세한 코드는 아래와 같습니다.

// 텍스트 필트의 text 변화를 감지하는 publisher (notification center에서 가져옴)
let publisher = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: self.someTextField)
    
publisher
    .compactMap { ($0.object as? UITextField)?.text } // publisher의 발행물에서 UITextField의 text를 가져옴
    .debounce(for: .seconds(1), scheduler: RunLoop.main) // 500ms 동안 기다렸다가 추가 발행물이 없으면 발행
    .flatMap { URLSession.shared.dataTaskPublisher(for: URL(string: $0)!) } // text를 URL로 바꾸어서 네트워크 통신
    .sink { completion in
        print(completion)
    } receiveValue: { output in
        print(output.data)
    }
    .store(in: &subscriptions)

.flatMap

.flatMap은 발행물을 받아서 Publisher를 리턴하는 연산자입니다. map이 발행물을 받아서 다른 발행물을 발행하는 것과는 비교하면 이해하기 쉬울 것 같습니다. 위에서는 flatmap을 통해서 publisher에서 받은 텍스트 받아서 URLSession.DataTaskPublisher를 리턴했습니다. 따라서 sink를 통해 원래 publisher가 아니라 새로 리턴한 URLSession.DataTaskPublisher를 구독하는 것이죠.

.debounce

.debounce는 이벤트를 지연해서 발행해주는 operator입니다. 위 코드처럼 1초를 설정해두면 publisher에서 발행이 되었을 때 그 발행물을 바로 발행하지 않고 1초간 기다립니다. 그리고 1초 후에 다른 발행이 없다면 해당 발행물을 발행합니다.

만약에 debounce가 없다면 사용자가 textField에 입력하는대로 바로바로 네트워크 통신이 이뤄질 것입니다. 그런 경우 불필요한 통신이 많이 이루어질 것입니다. 이를 방지하기 위해 사용자가 입력하고 1초간 아무 입력이 없을 때 네트워크 통신을 하도록 합니다.

SwiftUI

SwiftUI의 경우에는 기반에 Combine이 깔려있기 때문에 좀 더 쉽게 할 수 있습니다. TextField와 연결된 @Published 변수를 활용하면 됩니다.

import SwiftUI
import Combine

struct SwiftUIView: View {
    
    @ObservedObject private var vm = ViewModel()
    
    var body: some View {
        TextField("", text: $vm.text)
            .onAppear { vm.configure() }
    }
    

}

class ViewModel: ObservableObject {
    
    @Published var text: String = ""
    private var subscriptions = [AnyCancellable]()
    
    func configure() {
        $text
            .debounce(for: .seconds(1), scheduler: RunLoop.main)
            .flatMap { URLSession.shared.dataTaskPublisher(for: URL(string: $0)!) }
            .sink { completion in
                print(completion)
            } receiveValue: { output in
                print(output.data)
            }
            .store(in: &subscriptions)
    }
    
}
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글