LoginSignup
2

posted at

updated at

【SwiftUI】オフラインになったら画面を切り替える

はじめに

Webの場合、インターネット接続がないと以下のような表示になります。
スクリーンショット 2022-11-25 16.07.01.png

これをモバイルアプリでも再現したいと思います。

サンプルアプリ

RPReplay_Final1669361474_MP4_AdobeExpress.gif

実装

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 として扱えるようにする拡張
これめっちゃ勉強になりました!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2