Publishers and Subscribers in Combine with a SwiftUI project | Continued Learning #25
Subcribe
한다.subscribe
하기subscribe
한 불리언 변수 값 구현subsribe
하기 private var cancellables = Set<AnyCancellable>()
func addTextFieldSubscriber() {
$textFieldText
.debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
.map { (text) -> Bool in
if text.count > 3 {
return true
}
return false
}
.sink(receiveValue: { [weak self] isValid in
guard let self = self else { return }
self.textIsValid = isValid
})
.store(in: &cancellables)
}
subscribe
, 즉 값의 변화를 듣고 그 값을 확인한다.debounce
를 통해 연속적인 입력이 일어난다면 map
이후의 메소드를 처리하지 않는다. 연속적인 타이핑이 일어나지 않을 때 map
실행하기 때문에 연산 비용을 아낄 수 있다.sink
: 확인 연산 뒤 그 결과에 따라 주어진 데이터를 넣어줄 수 있다. 약한 참조로 선언했기 때문에 nil
로 아웃되면서 이후 유저에 의한 강제 중단 등 이벤트에 대응할 수 있다.store
: Combine
프레임워크가 제공하는 AnyCancellable
오브젝트 집합에 구독을 취소한 데이터를 담아둘 수 있다. 즉 취소 가능함을 알려준다.import SwiftUI
import Combine
class SubscriberViewModel: ObservableObject {
@Published var textFieldText = ""
@Published var count: Int = 0
@Published var textIsValid: Bool = false
@Published var showButton: Bool = false
private var cancellables = Set<AnyCancellable>()
init() {
setUpTimer()
addTextFieldSubscriber()
addButtonSubsriber()
}
func addTextFieldSubscriber() {
$textFieldText
.debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
.map { (text) -> Bool in
if text.count > 3 {
return true
}
return false
}
.sink(receiveValue: { [weak self] isValid in
guard let self = self else { return }
self.textIsValid = isValid
})
.store(in: &cancellables)
}
func addButtonSubsriber() {
$textIsValid
.combineLatest($count)
.sink { [weak self] (isValid, count) in
guard let self = self else { return }
if isValid && count >= 10 {
self.showButton = true
} else {
self.showButton = false
}
}
.store(in: &cancellables)
}
func setUpTimer() {
Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self = self else { return }
self.count += 1
}
.store(in: &cancellables)
}
}
struct SubscriberBootCamp: View {
@StateObject private var viewModel = SubscriberViewModel()
var body: some View {
VStack {
Text("\(viewModel.count)")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.gray)
TextField("Type something here...", text: $viewModel.textFieldText)
.padding(.leading)
.frame(height: 55)
.font(.headline)
.background(Color.gray.opacity(0.3))
.cornerRadius(10)
.overlay(
ZStack {
Image(systemName: "xmark")
.foregroundColor(.red)
.opacity(
viewModel.textFieldText.count < 1 ? 0.0 :
viewModel.textIsValid ? 0.0 : 1.0)
Image(systemName: "checkmark")
.foregroundColor(.green)
.opacity(viewModel.textIsValid ? 1.0 : 0.0)
}
.font(.headline)
.padding(.trailing)
, alignment: .trailing
)
Button(action: {
}, label: {
Text("Submit".uppercased())
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10)
.opacity(viewModel.showButton ? 1.0 : 0.0)
})
.disabled(!viewModel.showButton)
}
.padding()
}
}