Cómo manejar la accesibilidad de la conexión a Internet en Swift

La mayoría de las veces, las aplicaciones móviles necesitan una conexión a Internet activa para funcionar correctamente. Sin embargo, es normal que se pierda la conexión a Internet. En casos como estos, depende del desarrollador encontrar formas de hacer que la experiencia sea soportable o, al menos, notificar al usuario.

En este artículo, veremos cómo podemos detectar problemas de conexión a Internet en Swift y algunas formas en que podemos manejarlos.

Aquí está la aplicación de muestra que crearemos y cómo maneja diferentes escenarios de conectividad a Internet:

Requisitos

Para que pueda seguir este artículo, necesitará los siguientes requisitos:

  • Xcode instalado en su máquina.
  • Conocimiento del lenguaje de programación Swift.
  • Cocoapods instalado en su máquina.

Cuando tenga los requisitos anteriores, profundicemos.

Configurando nuestro espacio de trabajo

Antes de comenzar, crearemos un parque infantil. Aquí es donde escribiremos todos nuestros casos de uso y los manejaremos.

Swift viene con su propia implementación de Accesibilidad para detectar problemas de conexión, pero usaremos una biblioteca de terceros. Hacemos esto porque es más fácil y la API es más expresiva que la incorporada.

Abra Xcode y configure un nuevo proyecto.

Este proyecto será un patio de recreo simple con el que podemos experimentar.

Para detectar cuando la conexión se desconecta, usaremos el paquete Reachability.swift ****. Es un "reemplazo de la accesibilidad de Apple reescrito en Swift con cierres".

Abra su terminal y ejecute el siguiente comando:

$ pod init

Esto creará una nueva Podfiledonde podemos declarar las dependencias de Cocoapods. Abra Podfiley reemplace el contenido con el siguiente código:

platform :ios, '9.0'
target 'project_name' do use_frameworks! pod 'ReachabilitySwift' pod 'Alamofire'end
Necesita reemplazar **project_name**con el nombre de su proyecto.

Guarde el archivo y ejecute el siguiente comando para instalar los pods en su proyecto:

$ pod install

Cuando se complete la instalación, abra el *.xcworkspacearchivo en la raíz de su proyecto. Esto lanzará Xcode.

Creación de nuestro administrador de accesibilidad de red

Crea una nueva NetworkManagerclase. Esta clase almacenará el estado de la red y será un proxy simple para el Reachabilitypaquete. En el archivo, pegue el siguiente código:

import Foundationimport Reachability
class NetworkManager: NSObject {
 var reachability: Reachability!
 static let sharedInstance: NetworkManager = { return NetworkManager() }()
 override init() { super.init()
 // Initialise reachability reachability = Reachability()!
 // Register an observer for the network status NotificationCenter.default.addObserver( self, selector: #selector(networkStatusChanged(_:)), name: .reachabilityChanged, object: reachability )
 do { // Start the network status notifier try reachability.startNotifier() } catch { print("Unable to start notifier") } }
 @objc func networkStatusChanged(_ notification: Notification) { // Do something globally here! }
 static func stopNotifier() -> Void { do { // Stop the network status notifier try (NetworkManager.sharedInstance.reachability).startNotifier() } catch { print("Error stopping notifier") } }
 // Network is reachable static func isReachable(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection != .none { completed(NetworkManager.sharedInstance) } }
 // Network is unreachable static func isUnreachable(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .none { completed(NetworkManager.sharedInstance) } }
 // Network is reachable via WWAN/Cellular static func isReachableViaWWAN(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .cellular { completed(NetworkManager.sharedInstance) } }
 // Network is reachable via WiFi static func isReachableViaWiFi(completed: @escaping (NetworkManager) -> Void) { if (NetworkManager.sharedInstance.reachability).connection == .wifi { completed(NetworkManager.sharedInstance) } }]

En la clase anterior, hemos definido un par de funciones auxiliares que nos ayudarán a comenzar con el monitoreo del estado de la red. Tenemos sharedInstanceque es un singleton y podemos llamarlo si no queremos crear varias instancias de la NetworkManagerclase.

En el initmétodo, creamos una instancia de Reachabilityy luego registramos una notificación usando la NotificationCenterclase. Ahora, cada vez que cambie el estado de la red, se llamará a la devolución de llamada especificada por NotificationCenter(que es networkStatusChanged). Podemos usar esto para hacer algo global que se activa cuando la red es inaccesible.

Hemos definido otras funciones de ayuda que generalmente harán que ejecutar código, dependiendo del estado de nuestra conexión a Internet, sea muy sencillo. Tenemos *isReachable*, *isUnreachable*, *isReachableViaWWAN*y *isReachableViaWiFi*.

El uso de uno de estos ayudantes generalmente se verá así:

NetworkManager.isReachable { networkManagerInstance in print("Network is available")}
NetworkManager.isUnreachable { networkManagerInstance in print("Network is Unavailable")}
Este no es un detector de eventos y solo se ejecutará una vez. Para usar un oyente para captar cambios de red en tiempo real, necesitará usarNetworkManager.sharedInstance.reachability.whenReachable **. Mostraremos un ejemplo más adelante en el artículo. **

Ahora que tenemos una clase de administrador, veamos cómo podemos usar esto en una aplicación.

Manejo de la disponibilidad de la red en el lanzamiento de la aplicación

A veces, su aplicación depende en gran medida de una conexión a Internet y necesita detectar el estado al iniciarse. Veamos cómo podemos manejar esto usando la NetworkManagerclase.

Crea un nuevo controlador llamado LaunchViewController. Trataremos la primera vista del controlador en el guión gráfico como el controlador de inicio. Intentaremos detectar si el dispositivo del usuario está en línea y, en caso contrario, crearemos una página fuera de línea para manejar esto para que el usuario no ingrese a la aplicación en absoluto.

En LaunchController, reemplace el contenido con el siguiente código:

import UIKit
class LaunchViewController: UIViewController { let network: NetworkManager = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad()
 NetworkManager.isUnreachable { _ in self.showOfflinePage() } }
 private func showOfflinePage() -> Void { DispatchQueue.main.async { self.performSegue( withIdentifier: "NetworkUnavailable", sender: self ) } }}

En esta clase, utilizamos nuestra NetworkManager's *isUnreachable*método para disparar el showOfflinemétodo cuando la red no está disponible. Creemos ese controlador de vista. Cree un nuevo controlador de vista llamado OfflineViewController.

Abra el Main.storyboardarchivo y establezca la clase personalizada de la primera vista en LaunchViewController.

Next, create a new view controller in the storyboard. Set the OfflineViewController as the custom class for this new view controller. Now create a manual segue called NetworkUnavailable between the new view controller and the LaunchViewController. When you are done you should have something similar to this:

Now let’s run the application. Note, though, that before you run your application, your development machine should be offline as the iOS simulator uses the machine’s internet connection. When you run the application, you should get the offline page we created.

Now let us create a view controller that shows up when there is a connection.

Handling Events When the Device Comes Online

Now that we have created an Offline View Controller and it works when the device is offline, let us handle what happens when the device is back online.

Create a new navigation view controller on the storyboard below the Offline View Controller. We will create a controller that displays the latest Reddit posts. Create a new view controller class called PostsTableViewController. Now make this the custom class for the view controller attached to the Navigation View Controller.

Now create a manual segue called MainController from the Navigation View Controller to the Launch View Controller and the Offline View Controller. You should have something similar to this:

Now, open the LaunchViewController class and at the bottom of the viewDidLoad method add the following:

NetworkManager.isReachable { _ in self.showMainPage()}

Then add the method below to the controller:

private func showMainPage() -> Void { DispatchQueue.main.async { self.performSegue( withIdentifier: "MainController", sender: self ) }}

This will make sure that when the app is launched, it will check for connectivity and then, if the connection is available, it will present the PostsTableViewController. Otherwise it will present the OfflineViewController.

Great! But what happens when the user has hit the OfflineViewController and then the network comes back online? Let’s handle that scenario.

Open the OfflineViewController and replace the code with the code below:

import UIKit 
class OfflineViewController: UIViewController { let network = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad()
 // If the network is reachable show the main controller network.reachability.whenReachable = { _ in self.showMainController() } }
 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated)
 navigationController?.setNavigationBarHidden(true, animated: animated) }
 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated)
 navigationController?.setNavigationBarHidden(false, animated: animated) }
 private func showMainController() -> Void { DispatchQueue.main.async { self.performSegue(withIdentifier: "MainController", sender: self) } }}

In the controller above, you can see, in the viewDidLoad method, that we set the whenReachable completion to show the main controller. This means that, as long as its offline, you watch for when the device comes back online. When it does, present the PostsTableViewController.

We also override the viewWillAppear and viewWillDisappear methods to ensure the navigation bar does not show on the Offline View Controller.

Fetching Posts from Reddit API in Swift

Now let us add the logic that’ll fetch data from Reddit and display on our PostsTableViewController. Open the file and replace the contents with the code below:

import UIKitimport Alamofire
struct RedditPost { let title: String! let subreddit: String!}
class PostsTableViewController: UITableViewController { var posts = [RedditPost]()
 let network = NetworkManager.sharedInstance
 override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Latest Posts"
 // Fetch the posts and then reload the table fetchPosts { posts in self.posts = posts self.tableView.reloadData() } }
 private func fetchPosts(completion: @escaping (_ posts: [RedditPost]) -> Void) -> Void { // Send a request to the Reddit API Alamofire.request("//api.reddit.com").validate().responseJSON { response in switch response.result { case .success(let JSON): let data = JSON as! [String:AnyObject] guard let children = data["data"]!["children"] as? [AnyObject] else { return } var posts = [RedditPost]()
 // Loop through the Reddit posts and then assign a post to the posts array for child in 0...children.count-1 { let post = children[child]["data"] as! [String: AnyObject]
 posts.append(RedditPost( title: post["title"] as! String, subreddit: "/r/" + (post["subreddit"] as! String) )) }
 DispatchQueue.main.async { completion(posts) } case .failure(let error): print(error) } } }
 override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }
 // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
 // Return the number of posts available override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.posts.count }
 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) let post = posts[indexPath.row] as RedditPost cell.textLabel?.text = post.title cell.detailTextLabel?.text = post.subreddit return cell }}

In the fetchPosts method, we use Alamofire to send a GET request to the Reddit API. We then parse the response and add it to the RedditPost struct we created at the top of the file. This makes the data we are passing to the tableView consistent.

Handling Events when the Device Goes Offline

Now, let us handle one more scenario. Imagine while viewing the latest Reddit posts, you lose connectivity. What happens? Let’s show the offline page again when that happens.

As was previously done, create a manual segue called NetworkUnavailable from the PostsTableViewController to the OfflineViewController. Now add this code to the bottom of the viewDidLoad method:

network.reachability.whenUnreachable = { reachability in self.showOfflinePage()}

Now add the method below to the controller:

private func showOfflinePage() -> Void { DispatchQueue.main.async { self.performSegue(withIdentifier: "NetworkUnavailable", sender: self) }}

This will listen for when the device goes offline and, if that happens, it will showOfflinePage.

That’s all! We have been able to handle offline and online events using our NetworkManager in Swift.

Conclusion

In this article, we considered how to make sure your application can handle online and offline events when they happen. You can always implement this any way you wish. If you have any questions or feedback, leave them below in the comments.

The source code to this playground is available on GitHub.

This article was first published on Pusher.