はじめに
Webの場合、インターネット接続がないと以下のような表示になります。
これをモバイルアプリでも再現したいと思います。
サンプルアプリ
実装
「NWPathMonitor を Combine の Publisher として扱えるようにする拡張」を使用してCombineで使えるように拡張します。
NWPathMonitor
import Combine
import Network
extension NWPathMonitor {
func publisher(queue: DispatchQueue = DispatchQueue.main) -> NWPathMonitor.Publisher {
Publisher(monitor: self, queue: queue)
}
final class Subscription<S: Subscriber>: Combine.Subscription where S.Input == NWPath {
private let subscriber: S
private let monitor: NWPathMonitor
private let queue: DispatchQueue
private var isStarted = false
init(subscriber: S, monitor: NWPathMonitor, queue: DispatchQueue) {
self.subscriber = subscriber
self.monitor = monitor
self.queue = queue
}
func request(_ demand: Subscribers.Demand) {
precondition(demand == .unlimited, "This subscription is only supported to `Demand.unlimited`.")
guard !isStarted else { return }
isStarted = true
monitor.pathUpdateHandler = { [unowned self] path in
_ = self.subscriber.receive(path)
}
monitor.start(queue: queue)
}
func cancel() {
monitor.cancel()
}
}
struct Publisher: Combine.Publisher {
typealias Output = NWPath
typealias Failure = Never
private let monitor: NWPathMonitor
private let queue: DispatchQueue
init(monitor: NWPathMonitor, queue: DispatchQueue) {
self.monitor = monitor
self.queue = queue
}
func receive<S>(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let subscription = Subscription(subscriber: subscriber, monitor: monitor, queue: queue)
subscriber.receive(subscription: subscription)
}
}
}
NetworkMonitor
import Combine
import Network
import SwiftUI
fileprivate final class NetworkMonitorViewModel: ObservableObject {
@Published var status: NWPath.Status = .satisfied
private let monitor = NWPathMonitor()
init() {
monitor.publisher()
.map(\.status)
.assign(to: &$status)
}
}
struct NetworkMonitorView<T: View>: View {
@StateObject private var viewModel = NetworkMonitorViewModel()
private let online: () -> T
private let offline: () -> T
init(@ViewBuilder online: @escaping () -> T,
@ViewBuilder offline: @escaping () -> T
) {
self.online = online
self.offline = offline
}
var body: some View {
switch viewModel.status {
case .satisfied:
online()
case .unsatisfied:
offline()
case .requiresConnection:
offline()
@unknown default:
fatalError()
}
}
}
使い方
ContentView
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
NetworkMonitorView {
Image(systemName: "wifi")
.foregroundColor(.green)
.font(.system(size: 50))
Text("オンライン")
.foregroundColor(.green)
.font(.system(size: 50))
} offline: {
Image(systemName: "wifi.slash")
.foregroundColor(.red)
.font(.system(size: 50))
Text("オフライン")
.foregroundColor(.red)
.font(.system(size: 50))
}
}
}
}
おわり
「NWPathMonitor を Combine の Publisher として扱えるようにする拡張」
これめっちゃ勉強になりました!