Detecting Network Connectivity Changes using Reachability, NSNotification and Network Link Conditioner in Swift
Asked Answered
G

11

45

From iOS 12 you simply use NWPathMonitor which is a line of code (example).

For historic purposes:


I'm trying to integrate network connectivity detection into my app, however it seems that somewhere along the line I have made a mistake as my network changes are not being detected/printed out into the console.

As mentioned in the post, I'm currently using these following classes and tools for the job:

  1. Reachability {.h, .m}
  2. NSNotificationCenter
  3. Network Link Conditioner

Code

In the AppDelegate.Swift, I've set up the NSNotificationCenter to detect changes:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // ... 
    // A: Checks if the device is connected to the internet

    var defaultCenter: Void = NSNotificationCenter().addObserver(self, selector:"checkForReachability", name: kReachabilityChangedNotification, object: nil)
} 

In the same class AppDelegate, I've also created this function to be triggered whenever there is a change:

func checkForReachability () {        
    var networkReachability = Reachability.reachabilityForInternetConnection()
    networkReachability.startNotifier()
    
    var remoteHostStatus = networkReachability.currentReachabilityStatus()
    if (remoteHostStatus.value == NotReachable.value) {
        println("Not Reachable")
    } else if (remoteHostStatus.value == ReachableViaWiFi.value) {
        println("Reachable via Wifi")
    } else {
        println("Reachable")
    }
}

However, when using the Network Link Conditioner to manipulate and simulate changes in conditions, I haven't been able to see any of those changes reflected in the console.

Gules answered 5/12, 2014 at 6:54 Comment(5)
Are you using the Network Link Conditioner available after setting up an iPhone for development or the one available for the Mac and in combination with the iOS simulator?Martyrdom
Both ways. I've used the one available for the Mac in combination with the iOS simulator AND my iPhone for development.Gules
Which Reachability class are you using if you don't mind me asking?Infect
Hello, @kevindeleon. Here's a link to the Reachability class as provided by Apple.Gules
howdy @Gules ! Regarding your famous and excellent question. As you know it is now extremely out of date. For any new Apple programmers googling here, I have just added one line of text up the top of your famous question. Of course, obviously, you can and should change that edit however you like. Thanks again for this great question which has helped 50,000 people over the years, you're famous! :)Sled
M
39

You must create a Reachability object before you can receive notifications from it. Also, be sure to call the startNotifier() method on the Reachability object you create. This would be an example of how to do so inside of your application delegate:

class AppDelegate: UIResponder, UIApplicationDelegate
{
    private var reachability:Reachability!;

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool
    {
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"checkForReachability:", name: kReachabilityChangedNotification, object: nil);

        self.reachability = Reachability.reachabilityForInternetConnection();
        self.reachability.startNotifier();
    }

    @objc func checkForReachability(notification:NSNotification)
    {
        // Remove the next two lines of code. You cannot instantiate the object
        // you want to receive notifications from inside of the notification
        // handler that is meant for the notifications it emits.

        //var networkReachability = Reachability.reachabilityForInternetConnection()
        //networkReachability.startNotifier()

        let networkReachability = notification.object as Reachability;
        var remoteHostStatus = networkReachability.currentReachabilityStatus()

        if (remoteHostStatus.value == NotReachable.value)
        {
            println("Not Reachable")
        }
        else if (remoteHostStatus.value == ReachableViaWiFi.value)
        {
            println("Reachable via Wifi")
        }
        else
        {
            println("Reachable")
        }
    }
}

I recommend you take a look at the documentation for NSNotificationCenter and NSNotification. That way you'll be more familiar with how to work with notifications next time something like this comes up.

Swift 3

NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil)
let reachability: Reachability = Reachability.forInternetConnection()
reachability.startNotifier()
Martyrdom answered 5/12, 2014 at 7:14 Comment(6)
Thanks, I've changed it. However, I'm still unable to detect network changes via printing to the console.Gules
Try setting a breakpoint in checkForReachability() to see if it is getting called at all. If so then you'll know your problem has to do with the either that method or the data it receives.Martyrdom
I've set the breakpoint and it seems that it's not being called at all.Gules
I've implemented the changes and it worked. I had to resort to deploying the iPhone as my Network Link Conditioner wasn't simulating the necessary changes. I'll be sure to dive into the documentation. Thanks!Gules
do we need call stopNotifier in AppDelegate?Clarsach
@User9527 Probably, but the exact delegate method will depend on how you're using it.Martyrdom
O
24

Updated for Swift 4 / Swift 5 according @Hardik.T

1. Import Reachability.swift file from https://github.com/ashleymills/Reachability.swift/archive/master.zip in your XCode project

2. Create a new Swift class : ConnectionManager.swift

class ConnectionManager {

static let sharedInstance = ConnectionManager()
private var reachability : Reachability!

func observeReachability(){
    self.reachability = Reachability()
    NotificationCenter.default.addObserver(self, selector:#selector(self.reachabilityChanged), name: NSNotification.Name.reachabilityChanged, object: nil)
    do {
        try self.reachability.startNotifier()
    }
    catch(let error) {
        print("Error occured while starting reachability notifications : \(error.localizedDescription)")
    }
}

@objc func reachabilityChanged(note: Notification) {
    let reachability = note.object as! Reachability
    switch reachability.connection {
    case .cellular:
        print("Network available via Cellular Data.")
        break
    case .wifi:
        print("Network available via WiFi.")
        break
    case .none:
        print("Network is not available.")
        break
    case .unavailable:
        print("Network is  unavailable.")
        break
    }
  }
}

3. Use it in your AppDelegate file :

func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    ConnectionManager.sharedInstance.observeReachability()
    return true
}
Overstrain answered 30/3, 2018 at 9:26 Comment(1)
Just a question...how to notify a view controller about network changes ?Resinous
P
8

Instead of polluting the AppDelegate.swift with observer callbacks I would recommend adding observers only into the relevant view controllers.

AppDelegate.swift

import ReachabilitySwift


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
    var reachability: Reachability?


    func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool
    {
       self.reachability = Reachability()

       do
       {
          try reachability?.startNotifier()
       }
       catch
       {
          print( "ERROR: Could not start reachability notifier." )
       }

       return true
    }


    class func sharedAppDelegate() -> AppDelegate?
    {
        return UIApplication.shared.delegate as? AppDelegate
    }


    // Remaining functions
}

Example of a ViewController:

class ExampleVC: UIViewController
{
    override func viewDidLoad()
    {
        // Add reachability observer
        if let reachability = AppDelegate.sharedAppDelegate()?.reachability
        {
            NotificationCenter.default.addObserver( self, selector: #selector( self.reachabilityChanged ),name: ReachabilityChangedNotification, object: reachability )
        }
    }


    @objc private func reachabilityChanged( notification: NSNotification )
    {
        guard let reachability = notification.object as? Reachability else
        {
            return
        }

        if reachability.isReachable
        {
            if reachability.isReachableViaWiFi
            {
                print("Reachable via WiFi")
            }
            else
            {
                print("Reachable via Cellular")
            }
        }
        else
        {
            print("Network not reachable")
        }
    }
}
Pharsalus answered 28/1, 2017 at 18:42 Comment(5)
-1! When you want to check for internet connection changed. You don't know in which view the user is. so you need to check in whole app.Swallow
I am checking the whole app. However, only the view controllers interested in an internet connection change, listen to the internet notification.Pharsalus
I am using this method. first time when I a going offline reachabilityChanged() getting called. But again if I am getting connected it is not getting called for any event. Please HelpSprocket
Shouldn't we also call "NotificationCenter.default.removeObserver" when the view is not in use any more?Autobiographical
@KhushbooDhote U don't need it now but still posting answer to help any needy...Simulator works in weird way and calling notifier only once...to get notify for each internet connection change debug on real deviceAlexandrina
A
6

Based on this open source solution Wrapped to class

Swift 5

import Foundation

final class ReachabilityHandler {

  private var reachability: Reachability? = Reachability()

  // MARK: - LifeCycle

  init() {
    configure()
  }

  deinit {
    NotificationCenter.default.removeObserver(self)
    reachability?.stopNotifier()
  }

  // MARK: - Private

  private func configure() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(ReachabilityHandler.checkForReachability(notification:)),
                                           name: Notification.Name.reachabilityChanged,
                                           object: nil)
    try? reachability?.startNotifier()

  }

  @objc private func checkForReachability(notification: NSNotification) {
    let networkReachability = notification.object as? Reachability
    if let remoteHostStatus = networkReachability?.connection {
      switch remoteHostStatus {
        case .none:

        case .wifi,
             .cellular:

      }
    }
  }
}

In AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {

  private var rechabilityObserver: ReachabilityHandler?

  var window: UIWindow?

  // MARK: - LifeCycle

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    rechabilityObserver = ReachabilityHandler()

    return true
  }
}
Anathema answered 31/5, 2019 at 7:35 Comment(0)
C
4

Upadated for swift 2.1 & XCode 7:

try this third party Highly Rated Reachablity Class

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool
    {
 // Allocate a reachability object
        self.reach = Reachability.reachabilityForInternetConnection()

        // Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA
        self.reach!.reachableOnWWAN = false

        // Here we set up a NSNotification observer. The Reachability that caused the notification
        // is passed in the object parameter
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "reachabilityChanged:",
            name: kReachabilityChangedNotification,
            object: nil)

        self.reach!.startNotifier()

return true
}

//Reachbality Notification Response

    func reachabilityChanged(notification: NSNotification) {
        if self.reach!.isReachableViaWiFi() || self.reach!.isReachableViaWWAN() {
            print("Service avalaible!!!")
        } else {
            print("No service avalaible!!!")

            AppHelper.showALertWithTag(0, title: constants.AppName.rawValue, message: "Please Check Your Internet Connection!", delegate: self, cancelButtonTitle: "OK", otherButtonTitle: nil)
        }
    }
Colorfast answered 2/12, 2015 at 13:51 Comment(0)
L
3

Updated A. R. Younce answer for Swift 2:

func checkForReachability(notification:NSNotification) {
    if let networkReachability = notification.object as? Reachability {
        let remoteHostStatus = networkReachability.currentReachabilityStatus()

        if (remoteHostStatus == NotReachable) {
            print("Not Reachable")
        }
        else if (remoteHostStatus == ReachableViaWiFi) {
            print("Reachable via Wifi")
        }
        else {
            print("Reachable")
        }
    } else {
        print("Unknown")
    }
}
Lise answered 26/10, 2015 at 22:25 Comment(2)
You should consider using a swift statement as it ensures that the exhaustive list of values are being considered. Other than that some, subjectively consider it easier to read, understand and maintain.Gules
Very true about using Swift switch statement. In my case I just cared if it was reachable or not. However I replicated Younce's code with the fixes for the purposes of the post.Lise
S
1

Swift 2.0 - Check Network Using Reachability, NSNotification

AppDelegate.swift

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 
{
    NSNotificationCenter.defaultCenter().addObserver(self, selector:#selector(self.checkNetworkStatus(_:)), name: "ReachabilityChangedNotification", object: nil);

    do{self.reachability = try Reachability.reachabilityForInternetConnection()}catch{}
    do{try self.reachability.startNotifier()}catch{}
    self.checkNetworkStatus()

    return true
}

Declare networkStatus variable

var networkStatus : Reachability.NetworkStatus!

checkNetworkStatus() Function

func checkNetworkStatus()
{
    networkStatus = reachability.currentReachabilityStatus

    if (networkStatus == Reachability.NetworkStatus.NotReachable)
    {
        print("Not Reachable")
    }
    else
    {
        print("Reachable")
    }
}

OtherClass.Swift

let delegate = UIApplication.sharedApplication().delegate as! AppDelegate

if (delegate.networkStatus!=Reachability.NetworkStatus.NotReachable)
{
   // Call Webservice     
}
else
{
   delegate.checkNetworkStatus()  //Not Reachable print  
}
Stertorous answered 23/1, 2016 at 9:14 Comment(0)
G
1

1) Install pod or add ReachabilitySwift in your project

2) in AppDelegate.swift

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSNotificationCenter.defaultCenter().addObserver(self, selector:#selector(self.checkForReachability(_:)), name: "ReachabilityChangedNotification", object: nil);

        do {
            try self.reachability = Reachability.reachabilityForInternetConnection()
        }
        catch {
            print(error)
        }

        do {
            try self.reachability.startNotifier()
        }
        catch {
            print(error)
        }

        return true
    }

3)

func checkForReachability(notification:NSNotification) {
        let networkReachability = notification.object as! Reachability;
        let remoteHostStatus = networkReachability.currentReachabilityStatus

        if (remoteHostStatus == .NotReachable) {
            print("Not Reachable")
        }
        else if (remoteHostStatus == .ReachableViaWiFi || remoteHostStatus == .ReachableViaWWAN) {
            print("Reachable via Wifi or via WWAN")
        }
    }
Gewirtz answered 27/10, 2016 at 11:52 Comment(1)
Hi can you please show example how to pass a NSNotification to checkForReachability method to show a notification to UILabel in a ViewControllerSpiritualize
T
0

Using ReachabilitySwift framework which is a replacement for Apple's Reachability re-written in Swift with closures

  1. Install ReachabilitySwift Cocoapod

  2. Create NetworkReachability wrapper class for observing the reachability changes

    //
    //  NetworkReachability.swift
    //  NetworkMonitor
    //
    //  Created by MANNAM on 02/03/23.
    //
    
    import Foundation
    import Reachability
    
    extension Notification.Name {
      static let ReachabilityStatusChanged = Notification.Name("ReachabilityStatusChangedNotification")
    }
    
    //MARK: NetworkReachability
    final class NetworkReachability {
    
    enum ReachabilityStatus: Equatable {
      case connected
      case disconnected
    }
    
    static let shared = NetworkReachability()
    private let reachability = try! Reachability()
    var reachabilityObserver: ((ReachabilityStatus) -> Void)?
    private(set) var reachabilityStatus: ReachabilityStatus = .connected
    
    private init() {
    setupReachability()
    }
    
    /// setup observer to detect reachability changes
    private func setupReachability() {
      let reachabilityStatusObserver: ((Reachability) -> ()) = { [unowned self] (reachability: Reachability) in
        self.updateReachabilityStatus(reachability.connection)
      }
      reachability.whenReachable = reachabilityStatusObserver
      reachability.whenUnreachable = reachabilityStatusObserver
    }
    
    /// Start observing reachability changes
    func startNotifier() {
    do {
        try reachability.startNotifier()
    } catch {
        print(error.localizedDescription)
     }
    }
    
    /// Stop observing reachability changes
    func stopNotifier() {
    reachability.stopNotifier()
    }
    
    /// Updated ReachabilityStatus status based on connectivity status
    ///
    /// - Parameter status: Reachability.Connection enum containing reachability status
    private func updateReachabilityStatus(_ status: Reachability.Connection) {
    switch status {
    case .unavailable, .none:
        notifyReachabilityStatus(.disconnected)
    case .cellular, .wifi:
        notifyReachabilityStatus(.connected)
    }
    }
    
    /// Notifies observers about reachability status change
    ///
    /// - Parameter status: ReachabilityStatus enum indicating status eg. .connected/.disconnected
    private func notifyReachabilityStatus(_ status: ReachabilityStatus) {
    reachabilityStatus = status
    reachabilityObserver?(status)
    NotificationCenter.default.post(
        name: Notification.Name.ReachabilityStatusChanged,
        object: nil,
        userInfo: ["ReachabilityStatus": status]
    )
    }
    
    /// returns current reachability status
    var isReachable: Bool {
    return reachability.connection != .unavailable
    }
    
    /// returns if connected via cellular or wifi
    var isConnectedViaCellularOrWifi: Bool {
    return isConnectedViaCellular || isConnectedViaWiFi
    }
    
    /// returns if connected via cellular
    var isConnectedViaCellular: Bool {
    return reachability.connection == .cellular
    }
    
    /// returns if connected via cellular
    var isConnectedViaWiFi: Bool {
    return reachability.connection == .wifi
    }
    
    deinit {
     stopNotifier()
     }
    }
    

in AppDelagete.Swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    NetworkReachability.shared.startNotifier()
    reachabilityObserver()
    return true
}

func reachabilityObserver() {
    NetworkReachability.shared.reachabilityObserver = { [weak self] status in
        switch status {
        case .connected:
            print("Reachability: Network available 😃")
        case .disconnected:
            print("Reachability: Network unavailable 😟")
        }
    }
}
Tumbrel answered 15/1, 2020 at 7:38 Comment(0)
T
0

Swift 5.0 , Xcode 11.3

create a new file called NetworkListner.swift

class NetworkListner : NSObject {

static  let shared = NetworkListner()

var reachabilityStatus: Reachability.Connection = .unavailable
let reachability = try! Reachability()

var isNetworkAvailable : Bool {
    return reachabilityStatus != .unavailable
}



func startNWListner() {
    
    NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
    
    reachability.whenReachable = { reachability in
        if reachability.connection == .wifi {
            print("Reachable via WiFi")
        } else {
            print("Reachable via Cellular")
        }
    }
    reachability.whenUnreachable = { _ in
        print("Not reachable")
    }
    
    do {
        try reachability.startNotifier()
    } catch {
        print("Unable to start notifier")
    }
}

@objc func reachabilityChanged(note: Notification) {
    
    let reachability = note.object as! Reachability
    
    switch reachability.connection {
    case .wifi:
        print("Reachable via WiFi")
    case .cellular:
        print("Reachable via Cellular")
    case .unavailable:
        print("Network not reachable")
    case .none:
        print("Network none")
    }
 }


}

Now in your appDelegate method.

NetworkListner.shared.startNWListner()

Read carefully

on simulator

  • when you run the application when internet is turned on notification will get triggered
  • then you turn off the internet - notification will get triggered
  • And then if you turn on the internet again it won't get triggered

Found this issue on simulator.

Works pretty well on real device so no worries

Taphouse answered 15/7, 2020 at 11:5 Comment(0)
H
0

for ios 12 and above;

import Network

create a class

@available(iOS 12.0, *)
class NetworkListener{
    let monitor = NWPathMonitor()
    init() {
     
        
        monitor.pathUpdateHandler = { path in
            if path.status == .satisfied {
                print("network is now connected")
                // Put your logic code 
   
            }else {
               print("No connection.")
               // Put your logic code 
           }
        }
        let queue = DispatchQueue.main // if your logic works on background edit accordingly 
        monitor.start(queue: queue)
    }
}

init class where you need

 if #available(iOS 12.0, *) {
            let net = NetworkListener()
        }

you can monitor specific interface also

let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
Hydrocephalus answered 16/6, 2021 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.