Search code examples
iosnetwork-programmingalamofirereachabilitynsnotifications

Swift iOS- What to do when there is a wifi connection but no internet connection?


I'm using Alamofire in my iOS app. I use bool values in viewWillAppear and in AppDelegate with NSNotifications to check if there is an internet connection. If there is no wifi connection a pop up appears to inform the user. If there is a wifi connection the pop up disappears and everything works fine again. I've had no problems as long as wifi is clearly not working.

I was at a meetup and someone explained to me that the way it works is it it looks for a wifi connection and not an internet connection. For e.g.. if I have a wifi router and it's plugged in but the router isn't connected to the internet Alamofire will view this as a successful connection because it actually is connecting to wifi although it doesn't know the wifi can't connect to the internet.

I was just in a situation where I connected to an open network, my app initially responded as if I were actually connected to the internet (no pop up) but I couldn't get connect to anything. The wifi signal was on full. In terminal I ran a ping and it turns out the connection was dead. My app couldn't tell the difference.

enter image description here enter image description here

How do I make a pop up appear in a sitaution like this?

Also what is .case unknown for?

Alamofire.Swift:

import Foundation
import Alamofire

open class NetworkManager {

    open static var sharedManager: NetworkReachabilityManager = {

        let reachabilityManager = NetworkReachabilityManager()

        reachabilityManager?.listener = { (status) in

            switch status {

            case .notReachable:
                print("The network is not reachable")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "unsuccessful"), object: nil)

            case .unknown : //???????
                print("It is unknown wether the network is reachable")
                //I'm not sure whether to put a Notification for successful or unsuccessful???

            case .reachable(.ethernetOrWiFi):
                print("The network is reachable over the WiFi connection")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)

            case .reachable(.wwan):
                print("The network is reachable over the WWAN connection")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)
            }
        }

        reachabilityManager?.startListening()
        return reachabilityManager!
    }()
}

AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        NetworkManager.sharedManager.startListening()

SomeVC:

override func viewWillAppear() {
        super.viewWillAppear()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(successful), name: "successful", object: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(unsuccessful), name: "unsuccessful", object: nil)

       if NetworkManager.sharedManager.isReachable == true{
            self.successful()
       }else{
            self.unsuccessful()
       }

       if NetworkManager.sharedManager.isReachableOnWWAN == true{ 
            self.successful()
       }else{
            self.unsuccessful()
       }

       if NetworkManager.sharedManager.isReachableOnEthernetOrWiFi == true{ 
            self.successful()
       }else{
            self.unsuccessful()
       }
}

func successful(){
    //dismiss pop up
}

func unsuccessful(){
    //show pop up
}

deinit{
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "successful", object: nil)
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "unsuccessful", object: nil)
}
}

Solution

  • I followed this AshleyMills Reachability file (added below) and it uses google.com to test for a connection.

    It's important to note that the way I set this is up is it monitors specifically on viewWillAppear and works best when switching tabs and pushing/popping/presenting view controllers. In the actual AshleyMills GitHub repo he uses a DispatchQueue.asyncAfter( .now() + 5) timer and other code to keep constantly monitoring which isn't included inside my answer. For constant monitoring you should use his ViewController file from the link above or my Firebase answer inside this thread

    Add this to viewWillAppear and viewWillDisappear:

    var reachability: Reachability?
    let reachabilityConnection = Reachability()!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
            
        setupReachability("www.google.com") // inside China use www.alibaba.com
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
    
        stopNotifier()
    }
    
    func setupReachability(_ hostName: String) {
        
        self.reachability = Reachability(hostname: hostName)
        
        startNotifier()
    }
    
    func startNotifier() {
        do {
            print("notifier started")
    
            try reachability?.startNotifier()
    
            monitorReachability()
    
        } catch {
            print("*****Could not start notifier*****")
        }
    }
    
    func monitorReachability() {
        
        reachability?.whenReachable = { [weak self] (_) in
            
            self?.reachabilityChanged()
        }
        reachability?.whenUnreachable = { [weak self] (_) in
            
            self?.reachabilityChanged()
        }
    }
    
    func reachabilityChanged() {
        let reachability = reachabilityConnection
        
        switch reachability.connection {
            
        case .wifi:
            print("----Reachable via WiFi")
        case .cellular:
            print("----Reachable via Cellular")
        case .none:
            print("----No Signal")
        }
    }
    
    func stopNotifier() {
        
        reachability?.stopNotifier()
    
        reachability = nil
    
        print("notifier stopped")
    }
    

    For the AshleyMills file create a file and add this to it. I named the file Networkability:

    import SystemConfiguration
    import Foundation
    
    public enum ReachabilityError: Error {
        case FailedToCreateWithAddress(sockaddr_in)
        case FailedToCreateWithHostname(String)
        case UnableToSetCallback
        case UnableToSetDispatchQueue
    }
    
    @available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
    public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
    
    extension Notification.Name {
        public static let reachabilityChanged = Notification.Name("reachabilityChanged")
    }
    
    func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
        guard let info = info else { return }
        
        let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
        reachability.reachabilityChanged()
    }
    
    public class Reachability {
        
        public typealias NetworkReachable = (Reachability) -> ()
        public typealias NetworkUnreachable = (Reachability) -> ()
        
        @available(*, unavailable, renamed: "Connection")
        public enum NetworkStatus: CustomStringConvertible {
            case notReachable, reachableViaWiFi, reachableViaWWAN
            public var description: String {
                switch self {
                case .reachableViaWWAN: return "Cellular"
                case .reachableViaWiFi: return "WiFi"
                case .notReachable: return "No Connection"
                }
            }
        }
        
        public enum Connection: CustomStringConvertible {
            case none, wifi, cellular
            public var description: String {
                switch self {
                case .cellular: return "Cellular"
                case .wifi: return "WiFi"
                case .none: return "No Connection"
                }
            }
        }
        
        public var whenReachable: NetworkReachable?
        public var whenUnreachable: NetworkUnreachable?
        
        @available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
        public let reachableOnWWAN: Bool = true
        
        /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
        public var allowsCellularConnection: Bool
        
        // The notification center on which "reachability changed" events are being posted
        public var notificationCenter: NotificationCenter = NotificationCenter.default
        
        @available(*, deprecated: 4.0, renamed: "connection.description")
        public var currentReachabilityString: String {
            return "\(connection)"
        }
        
        @available(*, unavailable, renamed: "connection")
        public var currentReachabilityStatus: Connection {
            return connection
        }
        
        public var connection: Connection {
            guard isReachableFlagSet else { return .none }
            
            // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
            guard isRunningOnDevice else { return .wifi }
            
            var connection = Connection.none
            
            if !isConnectionRequiredFlagSet {
                connection = .wifi
            }
            
            if isConnectionOnTrafficOrDemandFlagSet {
                if !isInterventionRequiredFlagSet {
                    connection = .wifi
                }
            }
            
            if isOnWWANFlagSet {
                if !allowsCellularConnection {
                    connection = .none
                } else {
                    connection = .cellular
                }
            }
            
            return connection
        }
        
        fileprivate var previousFlags: SCNetworkReachabilityFlags?
        
        fileprivate var isRunningOnDevice: Bool = {
            #if targetEnvironment(simulator)
            return false
            #else
            return true
            #endif
        }()
        
        fileprivate var notifierRunning = false
        fileprivate let reachabilityRef: SCNetworkReachability
        
        fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability")
        
        fileprivate var usingHostname = false
        
        required public init(reachabilityRef: SCNetworkReachability, usingHostname: Bool = false) {
            allowsCellularConnection = true
            self.reachabilityRef = reachabilityRef
            self.usingHostname = usingHostname
        }
        
        public convenience init?(hostname: String) {
            guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
            self.init(reachabilityRef: ref, usingHostname: true)
        }
        
        public convenience init?() {
            var zeroAddress = sockaddr()
            zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
            zeroAddress.sa_family = sa_family_t(AF_INET)
            
            guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
            
            self.init(reachabilityRef: ref)
        }
        
        deinit {
            stopNotifier()
        }
    }
    
    public extension Reachability {
        
        // MARK: - *** Notifier methods ***
        func startNotifier() throws {
            guard !notifierRunning else { return }
            
            var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
            context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
            if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
                stopNotifier()
                throw ReachabilityError.UnableToSetCallback
            }
            
            if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
                stopNotifier()
                throw ReachabilityError.UnableToSetDispatchQueue
            }
            
            // Perform an initial check
            reachabilitySerialQueue.async {
                self.reachabilityChanged()
            }
            
            notifierRunning = true
        }
        
        func stopNotifier() {
            defer { notifierRunning = false }
            
            SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
            SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
        }
        
        // MARK: - *** Connection test methods ***
        @available(*, deprecated: 4.0, message: "Please use `connection != .none`")
        var isReachable: Bool {
            guard isReachableFlagSet else { return false }
            
            if isConnectionRequiredAndTransientFlagSet {
                return false
            }
            
            if isRunningOnDevice {
                if isOnWWANFlagSet && !reachableOnWWAN {
                    // We don't want to connect when on cellular connection
                    return false
                }
            }
            
            return true
        }
        
        @available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
        var isReachableViaWWAN: Bool {
            // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
            return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
        }
        
        @available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
        var isReachableViaWiFi: Bool {
            // Check we're reachable
            guard isReachableFlagSet else { return false }
            
            // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
            guard isRunningOnDevice else { return true }
            
            // Check we're NOT on WWAN
            return !isOnWWANFlagSet
        }
        
        var description: String {
            let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
            let R = isReachableFlagSet ? "R" : "-"
            let c = isConnectionRequiredFlagSet ? "c" : "-"
            let t = isTransientConnectionFlagSet ? "t" : "-"
            let i = isInterventionRequiredFlagSet ? "i" : "-"
            let C = isConnectionOnTrafficFlagSet ? "C" : "-"
            let D = isConnectionOnDemandFlagSet ? "D" : "-"
            let l = isLocalAddressFlagSet ? "l" : "-"
            let d = isDirectFlagSet ? "d" : "-"
            
            return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
        }
    }
    
    fileprivate extension Reachability {
        func reachabilityChanged() {
            guard previousFlags != flags else { return }
            
            guard let reachable = whenReachable else { return }
            guard let unreachable = whenUnreachable else { return }
            print("?????>>>>>>\(reachable)")
            let block = connection != .none ? reachable : unreachable
            
            DispatchQueue.main.async {
                if self.usingHostname {
                    print("USING HOSTNAME ABOUT TO CALL BLOCK")
                }
                block(self)
                self.notificationCenter.post(name: .reachabilityChanged, object:self)
            }
            
            previousFlags = flags
        }
        
        var isOnWWANFlagSet: Bool {
            #if os(iOS)
            return flags.contains(.isWWAN)
            #else
            return false
            #endif
        }
        var isReachableFlagSet: Bool {
            return flags.contains(.reachable)
        }
        var isConnectionRequiredFlagSet: Bool {
            return flags.contains(.connectionRequired)
        }
        var isInterventionRequiredFlagSet: Bool {
            return flags.contains(.interventionRequired)
        }
        var isConnectionOnTrafficFlagSet: Bool {
            return flags.contains(.connectionOnTraffic)
        }
        var isConnectionOnDemandFlagSet: Bool {
            return flags.contains(.connectionOnDemand)
        }
        var isConnectionOnTrafficOrDemandFlagSet: Bool {
            return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
        }
        var isTransientConnectionFlagSet: Bool {
            return flags.contains(.transientConnection)
        }
        var isLocalAddressFlagSet: Bool {
            return flags.contains(.isLocalAddress)
        }
        var isDirectFlagSet: Bool {
            return flags.contains(.isDirect)
        }
        var isConnectionRequiredAndTransientFlagSet: Bool {
            return flags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
        }
        
        var flags: SCNetworkReachabilityFlags {
            var flags = SCNetworkReachabilityFlags()
            if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) {
                print("Returning flags \(flags)")
                return flags
            } else {
                return SCNetworkReachabilityFlags()
            }
        }
    }