Section 10/161 menit
10. Integrasi dengan SwiftUI
10. Integrasi dengan SwiftUI
@Observable + AsyncChannel
swift
// ViewModel dengan async pipeline
import AsyncAlgorithms
import Observation
@Observable
final class SearchViewModel {
var results: [SearchResult] = []
var isLoading = false
var errorMessage: String?
private let queryChannel = AsyncChannel<String>()
private var searchTask: Task<Void, Never>?
init() {
startPipeline()
}
private func startPipeline() {
searchTask = Task { [weak self] in
guard let self else { return }
// Pipeline: debounce → deduplicate → search
let pipeline = queryChannel
.debounce(for: .milliseconds(300))
.removeDuplicates()
for await query in pipeline {
guard !Task.isCancelled else { break }
await performSearch(query: query)
}
}
}
func search(_ query: String) async {
await queryChannel.send(query)
}
@MainActor
private func performSearch(query: String) async {
guard !query.isEmpty else {
results = []
return
}
isLoading = true
do {
results = try await SearchAPI.search(query: query)
errorMessage = nil
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
deinit {
searchTask?.cancel()
queryChannel.finish()
}
}
// View
struct SearchView: View {
@State private var viewModel = SearchViewModel()
@State private var query = ""
var body: some View {
NavigationStack {
List(viewModel.results) { result in
SearchResultRow(result: result)
}
.overlay {
if viewModel.isLoading {
ProgressView()
} else if let error = viewModel.errorMessage {
ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
description: Text(error))
}
}
.searchable(text: $query)
.onChange(of: query) { _, newValue in
Task { await viewModel.search(newValue) }
}
.navigationTitle("Cari")
}
}
}
AsyncSequence ke SwiftUI via AsyncStream Bridge
Untuk feed data realtime ke SwiftUI dari async sequence:
swift
// Bridge AsyncSequence ke @Published / Binding
@Observable
final class LiveDataViewModel {
var latestValue: Double = 0
var history: [Double] = []
private var streamTask: Task<Void, Never>?
func startMonitoring(stream: some AsyncSequence<Double, Never> & Sendable) {
streamTask = Task { @MainActor in
// throttle untuk tidak update UI terlalu sering
for await value in stream.throttle(for: .milliseconds(100), reducing: { _, v in v }) {
latestValue = value
history.append(value)
if history.count > 100 {
history.removeFirst()
}
}
}
}
func stopMonitoring() {
streamTask?.cancel()
}
}