Lance Samaria
Lance Samaria

Reputation: 19572

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)
}
}

Upvotes: 6

Views: 7665

Answers (4)

Lance Samaria
Lance Samaria

Reputation: 19572

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()
        }
    }
}

Upvotes: 0

Lance Samaria
Lance Samaria

Reputation: 19572

Here's an answer using Firebase. You have to install the Firebase pod beforehand. Add this to viewWillAppear and viewWillDisappear:

import Firebase

let connectedRef = Database.database().reference(withPath: ".info/connected")

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    checkForAFirebaseConnection()   
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    connectedRef.removeAllObservers()
}

func checkForAFirebaseConnection() {

    connectedRef.observe(.value, with: { [weak self](connected) in
        if let boolean = connected.value as? Bool, boolean == true {
            print("Firebase is connected")
        } else {
            print("Firebase is NOT connected")
        }
    })
}

Upvotes: 0

Ammar Mujeeb
Ammar Mujeeb

Reputation: 1311

I was facing the same issue so with the help of some answers on stackoverflow i created a code that simply hit google.com asynchronously and return true in completion handler if response status is 200.

Code in Swift 4:

class func checkInternet(showLoader: Bool = true, completionHandler:@escaping (_ internet:Bool) -> Void)
{
    UIApplication.shared.isNetworkActivityIndicatorVisible = true

    let url = URL(string: "http://www.google.com/")
    var req = URLRequest.init(url: url!)
    req.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
    req.timeoutInterval = 10.0

    if showLoader {
        Loader.startLoading()
    }
    let task = URLSession.shared.dataTask(with: req) { (data, response, error) in

        if showLoader {
            Loader.stopLoading()
        }

        if error != nil  {
            completionHandler(false)
        } else {
            if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == 200 {
                    completionHandler(true)
                } else {
                    completionHandler(false)
                }
            } else {
                completionHandler(false)
            }
        }
    }
    task.resume() 
  }

Now you can use it like:

     InternetCheck.checkInternet(completionHandler: { (available) in
         if available {
              print("Net available")
         } else {
              print("Net not available")
     }

Upvotes: 0

Dialogue
Dialogue

Reputation: 331

You can init NetworkReachabilityManagerwith host, for example, google host, because default is 0.0.0.0

let reachabilityManager = Alamofire.NetworkReachabilityManager(host: "www.google.com")

When you start listening reachability manager doing ping to host. If network is available you can cache SSID and ping again when SSID changed.

For case .unknown better put a Notification for unsuccessful.

Example get SSID (it doesn't work in Simulator):

func fetchSSIDInfo() ->  String? {  
        if let interfaces = CNCopySupportedInterfaces() {  
            for i in 0..<CFArrayGetCount(interfaces){  
                let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i)  
                let rec = unsafeBitCast(interfaceName, to: AnyObject.self)  
                let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString)  

                if let unsafeInterfaceData = unsafeInterfaceData as? Dictionary<AnyHashable, Any> {  
                    return unsafeInterfaceData["SSID"] as? String  
                }  
            }  
        }  
        return nil  
    }

Upvotes: 2

Related Questions