2

There is one viewController. What should i write in AppDelegate.swift file in didFinishLaunchingWithOptions scope ?

PART 1

AppDelegate.swift

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

    // WHAT SHOULD I WRITE IN THIS SCOOPE ?

    return true
}

PART 2

viewController.swift

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}
}

PART 3

ReachabilityHandler.swift

import Foundation
import UIKit
class ReachabilityHandler {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(statusChanged), name: .flagsChanged, object: Network.reachability)
    }

deinit {
    NotificationCenter.default.removeObserver(self)
}

@objc func statusChanged(_ notification: NSNotification) {
    guard
        let isReachable = Network.reachability?.isReachable,
        let visibleViewController = UIApplication.shared.visibleViewController
        else { return }


    if false == isReachable {
        let alert = UIAlertController(title: "Title", message: "Offline", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
        visibleViewController.present(alert, animated: true, completion: nil)
    }
}
}

PART 4

extension_UIApplication.swift

import Foundation
import UIKit

extension UIApplication {
    var visibleViewController: UIViewController? {

    guard let rootViewController = keyWindow?.rootViewController else {
        return nil
    }

    return getVisibleViewController(rootViewController)
}

private func getVisibleViewController(_ rootViewController: UIViewController) -> UIViewController? {

    if let presentedViewController = rootViewController.presentedViewController {
        return getVisibleViewController(presentedViewController)
    }

    if let navigationController = rootViewController as? UINavigationController {
        return navigationController.visibleViewController
    }

    if let tabBarController = rootViewController as? UITabBarController {
        return tabBarController.selectedViewController
    }

    return rootViewController
}
}

PART 5

Reachability.swift


import Foundation
import SystemConfiguration

class Reachability {
    var hostname: String?
    var isRunning = false
    var isReachableOnWWAN: Bool
    var reachability: SCNetworkReachability?
    var reachabilityFlags = SCNetworkReachabilityFlags()
    let reachabilitySerialQueue = DispatchQueue(label: "ReachabilityQueue")
    init?(hostname: String) throws {
        guard let reachability = SCNetworkReachabilityCreateWithName(nil, hostname) else {
            throw Network.Error.failedToCreateWith(hostname)
        }
        self.reachability = reachability
        self.hostname = hostname
        isReachableOnWWAN = true
    }
    init?() throws {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)
        guard let reachability = withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }}) else {
                throw Network.Error.failedToInitializeWith(zeroAddress)
        }
        self.reachability = reachability
        isReachableOnWWAN = true
    }
    var status: Network.Status {
        return  !isConnectedToNetwork ? .unreachable :
            isReachableViaWiFi    ? .wifi :
            isRunningOnDevice     ? .wwan : .unreachable
    }
    var isRunningOnDevice: Bool = {
        #if (arch(i386) || arch(x86_64)) && os(iOS)
        return false
        #else
        return true
        #endif
    }()
    deinit { stop() }
}

extension Reachability {
    func start() throws {
        guard let reachability = reachability, !isRunning else { return }
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged<Reachability>.passUnretained(self).toOpaque()
        guard SCNetworkReachabilitySetCallback(reachability, callout, &context) else { stop()
            throw Network.Error.failedToSetCallout
        }
        guard SCNetworkReachabilitySetDispatchQueue(reachability, reachabilitySerialQueue) else { stop()
            throw Network.Error.failedToSetDispatchQueue
        }
        reachabilitySerialQueue.async { self.flagsChanged() }
        isRunning = true
    }
    func stop() {
        defer { isRunning = false }
        guard let reachability = reachability else { return }
        SCNetworkReachabilitySetCallback(reachability, nil, nil)
        SCNetworkReachabilitySetDispatchQueue(reachability, nil)
        self.reachability = nil
    }
    var isConnectedToNetwork: Bool {
        return isReachable &&
            !isConnectionRequiredAndTransientConnection &&
            !(isRunningOnDevice && isWWAN && !isReachableOnWWAN)
    }
    var isReachableViaWiFi: Bool {
        return isReachable && isRunningOnDevice && !isWWAN
    }

/// Flags that indicate the reachability of a network node name or address, including whether a connection is required, and whether some user intervention might be required when establishing a connection.
var flags: SCNetworkReachabilityFlags? {
    guard let reachability = reachability else { return nil }
    var flags = SCNetworkReachabilityFlags()
    return withUnsafeMutablePointer(to: &flags) {
        SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0))
        } ? flags : nil
}

/// compares the current flags with the previous flags and if changed posts a flagsChanged notification
func flagsChanged() {
    guard let flags = flags, flags != reachabilityFlags else { return }
    reachabilityFlags = flags
    NotificationCenter.default.post(name: .flagsChanged, object: self)
}

/// The specified node name or address can be reached via a transient connection, such as PPP.
var transientConnection: Bool { return flags?.contains(.transientConnection) == true }

/// The specified node name or address can be reached using the current network configuration.
var isReachable: Bool { return flags?.contains(.reachable) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. If this flag is set, the kSCNetworkReachabilityFlagsConnectionOnTraffic flag, kSCNetworkReachabilityFlagsConnectionOnDemand flag, or kSCNetworkReachabilityFlagsIsWWAN flag is also typically set to indicate the type of connection required. If the user must manually make the connection, the kSCNetworkReachabilityFlagsInterventionRequired flag is also set.
var connectionRequired: Bool { return flags?.contains(.connectionRequired) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. Any traffic directed to the specified name or address will initiate the connection.
var connectionOnTraffic: Bool { return flags?.contains(.connectionOnTraffic) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established.
var interventionRequired: Bool { return flags?.contains(.interventionRequired) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. The connection will be established "On Demand" by the CFSocketStream programming interface (see CFStream Socket Additions for information on this). Other functions will not establish the connection.
var connectionOnDemand: Bool { return flags?.contains(.connectionOnDemand) == true }

/// The specified node name or address is one that is associated with a network interface on the current system.
var isLocalAddress: Bool { return flags?.contains(.isLocalAddress) == true }

/// Network traffic to the specified node name or address will not go through a gateway, but is routed directly to one of the interfaces in the system.
var isDirect: Bool { return flags?.contains(.isDirect) == true }

/// The specified node name or address can be reached via a cellular connection, such as EDGE or GPRS.
var isWWAN: Bool { return flags?.contains(.isWWAN) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. If this flag is set
/// The specified node name or address can be reached via a transient connection, such as PPP.
var isConnectionRequiredAndTransientConnection: Bool {
    return (flags?.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]) == true
}
}

func callout(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
    guard let info = info else { return }
    DispatchQueue.main.async {
        Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue().flagsChanged()
    }
}

extension Notification.Name {
    static let flagsChanged = Notification.Name("FlagsChanged")
}

struct Network {
    static var reachability: Reachability?
    enum Status: String, CustomStringConvertible {
        case unreachable, wifi, wwan
        var description: String { return rawValue }
    }
    enum Error: Swift.Error {
        case failedToSetCallout
        case failedToSetDispatchQueue
        case failedToCreateWith(String)
        case failedToInitializeWith(sockaddr_in)
    }
}
mannyCalavera
  • 593
  • 1
  • 4
  • 23

2 Answers2

1

That's a good question. For your issue i recommend you to use Singleton Pattern that's a better solution. Cause we use anywhere in our whole app. You can read this example from medium that is a good article. https://medium.com/@sauvik_dolui/network-status-monitoring-on-ios-part-1-9a22276933dc

seyha
  • 554
  • 5
  • 16
  • I am working on it but pod install takes big time. Still installing.. Is it normal ? @seyha – mannyCalavera May 25 '18 at 11:38
  • I have Reachability class already. Am i installing same thing with writing this line in to terminal (pod ‘ReachabilitySwift’, ‘~> 3’ # Adds the library to your project) @seyha – mannyCalavera May 25 '18 at 11:44
  • yes you must install ReachabilitySwift from cocoapod. If it take so long time, i think it is normal if you use cocoapod frist time cause it is fetching repository from server. – seyha May 26 '18 at 06:47
0

You could get current visible view controller and present alert on it. Here is extension from this answer:

extension UIApplication {
    var visibleViewController: UIViewController? {

        guard let rootViewController = keyWindow?.rootViewController else {
            return nil
        }

        return getVisibleViewController(rootViewController)
    }

    private func getVisibleViewController(_ rootViewController: UIViewController) -> UIViewController? {

        if let presentedViewController = rootViewController.presentedViewController {
            return getVisibleViewController(presentedViewController)
        }

        if let navigationController = rootViewController as? UINavigationController {
            return navigationController.visibleViewController
        }

        if let tabBarController = rootViewController as? UITabBarController {
            return tabBarController.selectedViewController
        }

        return rootViewController
    }
}

After that you need any class subscribed on reachability event, for example ReachabilityHandler. Don't forget to initialize it somewhere.

class ReachabilityHandler {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(statusChanged), name: .flagsChanged, object: Network.reachability)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc func statusChanged(_ notification: NSNotification) {
        guard
            let isReachable = Network.reachability?.isReachable,
            let visibleViewController = UIApplication.shared.visibleViewController
        else { return }


        if false == isReachable {
            let alert = UIAlertController(title: "Title", message: "Offline", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
            visibleViewController.present(alert, animated: true, completion: nil)
        }
    }
}

You could check reachability inside of statusChanged and show alert on visibleViewController if isReachable is false. Of course you can replace alert with more convenient way of showing information, for example with on of numberless toast-showing library like Toast-Swift.

Update 1

AppDelegate.swift

private var rHandler: ReachabilityHandler?

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

    rHandler = ReachabilityHandler()

    return true
}
Leo
  • 3,003
  • 5
  • 38
  • 61
  • Let's assume I have 2 pages (homepageViewController, and GiveAlertViewController) I have created, extension UIApplication and ReachabilityHandler.swift as you wrote. And then i wrote these 3 lines in did load in homePageViewController : var f = ReachabilityHandler() var n = NSNotification() f.statusChanged(n) But it crushes when i run the app. @Leo Thanks. – mannyCalavera May 25 '18 at 11:06
  • @mannyCalavera you have to init ReachabilityHandler just once, in your `AppDelegate`. If crash still appears, please edit your question and post crash log. – Leo May 25 '18 at 12:43
  • Let me start from beginning. I create new single view app. (1) Is one view controller enough for your scenario ? (2) i create an extension swift file for extension UIApplication. right ? (3) i create ReachabilityHandler.swift file for 'class ReachabilityHandler' right? (4) and then am i creating an initialize for 'ReachabilityHandler' in AppDelegate.swift in 'didFinishLaunchingWithOptions' function ? (5) and then everything is done for your scenario rihght i run the app, right ? @Leo – mannyCalavera May 25 '18 at 13:03
  • @mannyCalavera 1-5 yes. It will work even if you add second view controller. – Leo May 25 '18 at 13:26
  • It does not work. Thread 1: signal SIGABRT 2018-05-25 16:59:21.693228+0300 internetKontrol3[1827:914263] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** -[NSConcreteNotification init]: should never be used' *** First throw call stack: (0x183576d8c 0x1827305ec ...) libc++abi.dylib: terminating with uncaught exception of type NSException (lldb) – mannyCalavera May 25 '18 at 14:02
  • @mannyCalavera please edit your original question and post crash log. – Leo May 25 '18 at 14:09
  • I have edited it. Please check. What should i write in AppDelegate.swift file and should it be in didFinishLaunchingWithOptions function ? @Leo – mannyCalavera May 25 '18 at 14:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/171801/discussion-between-leo-and-mannycalavera). – Leo May 25 '18 at 20:48