6

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)
}
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

4 Answers4

1

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  
    }
Dialogue
  • 311
  • 3
  • 5
  • added get ssid to answer, for save ssid you can use User​Defaults or your DB or other methods – Dialogue Mar 28 '17 at 12:21
  • thanks for the help. Do I add that function to the Reachability class or SomeVC? How do you call it and test with bool? I see it returns an Optional of type String – Lance Samaria Mar 28 '17 at 12:34
  • add fetchSSIDInfo() to NetworkManager `guard let ssid = fetchSSIDInfo() else {return} //save ssid` – Dialogue Mar 28 '17 at 16:29
  • you can check SSID changed before send some request type or by timer if let currentSSID = fetchSSIDInfo(), currentSSID != //saved SSID { //do ping} – Dialogue Mar 29 '17 at 06:15
  • Thanks for the help. i was busy last week so couldn't work on the project. I'm working on it now. Do I put the guard statement in viewWillAppear in the classes that I will use it in? Sorry for the questions but this is outside my expertise :) – Lance Samaria Apr 04 '17 at 20:26
  • Put first fetchSSIDInfo before first request – Dialogue Apr 06 '17 at 16:02
  • @LanceSamaria was this the correct answer to your question? I'm having trouble resolving this issue myself. fetchSSIDInfo() simply returns the name of the network my phone is connected on and not whether it is connected to. Is there some step that i'm missing? – IvanMih Oct 11 '17 at 14:56
  • @IvanMih hi Ivan he was correct when he said you need to ping someplace like google to make sure you can get data as opposed to just getting a wifi connect. I however couldn't get his code to work with mines. I switched over to AshlyMills instead which is working well. I'll put up a link to the YouTube I followed in 5 min. It's an easy and good tutorial and it works – Lance Samaria Oct 11 '17 at 15:04
  • @IvanMih I posted it – Lance Samaria Oct 11 '17 at 15:21
  • @IvanMih let me know if it works or if your having problems I'll walk you through it. – Lance Samaria Oct 11 '17 at 15:34
  • Hey @Dialogue have you tested this code in the situation stated in the question (connected to wifi without internet)? Using the method you've posted in your answer all we get is an SSID. How does that tell us if the wifi on which we are connected has internet access? – IvanMih Oct 12 '17 at 08:36
  • @IvanMih yes, tested. Check internet connection with Alamofire.NetworkReachabilityManager (below there is an addition), if `case .reachable(. ethernetOrWiFi)` cache SSID, when SSID changed check internet connection again. Or check internet connection by schedule. Also you can set timeout interval for request in Alamofire configuration and check internet connection if time is over. – Dialogue Oct 12 '17 at 08:46
  • Would you be willing to share a sample project perhaps? – IvanMih Oct 12 '17 at 08:54
0

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()
        }
    }
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • Lance thanks for trying to help. I would like some clarification, especially because i think that none of the answers actually answer your question. Your use case is: "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." This is what you've stated, and if you try the code from youtube link and change Alamofire with Reachability, it doesn't answer the question. – IvanMih Oct 12 '17 at 08:15
  • @IvanMih I haven't had any problems with the code from the YouTube video. I've connected to wifi, received no data, and the Ashly Mills reachability showed no connection. Is the YouTube code not working in the way I just described? As for the accepted answer, the reason I picked it was because he was correct in saying connect to a site like google that is guaranteed to always be reachable, if not, then there's a problem. His explanation of how to use it wasn't clear but he was correct. – Lance Samaria Oct 12 '17 at 10:56
  • @IvanMih Ivan I did some googling around, check this answer. I'm in the US and what I didn't realize or think about is if you try to ping google in countries that don't support it like China then it won't work. Also if your on 2G there may be an issue. https://stackoverflow.com/questions/24516748/check-network-status-in-swift . I'll do more research into the different answers and get back to to you. If you find something more concrete let me know, post the answer, and I'll upvote you. If I find it I'll post it for you – Lance Samaria Oct 12 '17 at 11:13
  • one thing between our tests could be different: I was using another phone's hotspot and turned mobile data on and off on that other phone. Later i'll try with a router. I really don't think there should be any difference in the outcome, but who knows. I've made a test project with reachability test + simple ping test (another approach in solving this problem). If you'd like to take a look, and correct me if you see some mistakes: https://github.com/ivanmih/NetworkTest. And i'm in Europe so i have access to google. – IvanMih Oct 12 '17 at 13:01
  • @IvanMih thank, I'll look over your code later tonight. I never tried using another phones hotspot, that's a good idea. I was using a router or I went to areas where I know I can get a wifi connection but no data. I'll have to try someone else's hotspot if I can find someone who has a hotspot on their phone. That was a good test! – Lance Samaria Oct 12 '17 at 13:07
  • I have a feeling that the best way to understand this situation is the R.S. answer to its own question posted in https://stackoverflow.com/questions/13411606/reachability-not-working-when-wi-fi-connected-but-no-internet Thought the code presented in the answer is outdated, and i have a feeling that its not going to work if it was updated. But the point could be in an initial DNS lookup. If that is really the case than we should do a dns lookup on every network request. This is quite out of the scope of my knowledge, but could be the right path. – IvanMih Oct 12 '17 at 13:14
  • 1
    @IvanMih I'm going to an iOS meetup next Wed. There are experts there. I'm not sure how fast you need an answer but I should be able to get some on hands help there. – Lance Samaria Oct 12 '17 at 20:32
  • @IvanMih hi, did you ever come up with an alternative solution? I had to jump around and take care a ton of other bugs but I'm back trying to tackle this now – Lance Samaria Feb 23 '18 at 00:05
  • Hey @Lance Samaria ! I had to move on, so unfortunately there is still the same issue. Whoever i spoke with found the problem interesting, but no one had the solution. If you find the solution please let me know. – IvanMih Feb 23 '18 at 15:02
0

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")
        }
    })
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
0

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")
     }
Ammar Mujeeb
  • 1,222
  • 18
  • 21