Why does my code result in a 'no internet' outcome when the device is connected to a 3G network?

77 views Asked by At

I'm testing if the user is connected to the internet. After running some tests, I discovered weird behavior:

  • If I'm connected to WiFi or 4G -> I get HomeView which is fine.
  • If I'm on airplane mode -> I get ErrorView which is also fine.
  • But if I'm connected to 3G -> I get HomeView which isn't fine at all because, for a reason, 3G seems to mean no internet. For example, when I'm on 3G, the App Store app tells me I'm not connected.

Why is that, and how can I fix it?

import SwiftUI

struct ContentView: View {
    @StateObject private var networkManager = NetworkManager()

    var body: some View {
        Group {
            if networkManager.isConnected {
                HomeView()
            } else {
                ErrorView()
            }
        }
    }
}
import Foundation
import Network

class NetworkManager: ObservableObject {
    private let monitor = NWPathMonitor()
    private let queue = DispatchQueue(label: "Monitor")
    var isConnected = false

    init() {
        monitor.pathUpdateHandler = { path in
            self.isConnected = path.status == .satisfied

            Task {
                await MainActor.run { self.objectWillChange.send() }
            }
        }

        monitor.start(queue: queue)
    }
}
2

There are 2 answers

0
Rob Napier On

3G does not mean "no internet." It means the system believes it is connected to a 3G network, which generally connect to "the internet."

I use quotes here because there's no strict meaning of "the internet." It is not any particular thing. It is a network of computers, some of which you may be able to reach, some of which you may not be able to reach. Which computers those are changes constantly. There is nothing in particular that you are "connected to" except the cell tower (your first hop). There is not even a strict meaning of what "connected to the cell tower" means.

What we generally mean by "connected" is that if we were to send a packet, it would arrive at its destination and we would get some response. There is no way to know if you're in that state without sending a packet and receiving a response.

Networks are not "pipes" where we both hold onto the ends and can tell when the other side has let go. They're like postal mail. You put a message in your mailbox and later you look in your mailbox and see if there's a response.

What NWPathMonitor is telling you is whether, if you were to try to send a packet anywhere (not just "the internet," but to any other server at all, possibly on a local private network), would it even try to send the packet. If, for example, the radio is turned off, then it clearly is not connected. It is describing the first hop. It is impossible for the system to know whether that packet could actually arrive at its destination. But it can tell you if it would try.

The only way to determine if you're connected to something is to try to send data to it, and see if it responds (this doesn't tell if you if the next packet you send will succeed, but it's the best hint you can get). This is what AppStore is doing. It sends a message to its server (not to "the internet"), and if it fails, then it says you're not connected. Likely the 3G network in question drops a lot of packets, so the phone may be connected to the network, but its timing out.

You need to do the same thing. You can use NSPathMonitor as a hint that you are definitely offline. And it gives you a good hint of when you might try again if you were previously offline. But the only way to know that you're definitely online is to send something and get a response.

1
Alexnnd On

I have finally found what I was looking for. Instead of using NWPathMonitor to check if the user is connected or not to the Internet, I'm now using a simple URLSessionDataTask and it's way better.

Here's something useful: https://developer.apple.com/documentation/foundation/urlsessiondatatask

And also, here's the code I'm using.

import Foundation

class NetworkManager: ObservableObject {
    @Published var isActive = false

    init() {
        checkConnection()
    }

    func checkConnection() {
        guard let url = URL(string: "http://www.apple.com") else { return }

        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            DispatchQueue.main.async { self.isActive = (error == nil) }
        }

        task.resume()
    }
}