4

I want to check if device is having very slow internet connection.

I have used Reachability class to check for internet connection is available or not.

But here i want to check after every few sec that internet speed is not poor.

Is this possible to find and yes than how can i do it.

Hardik Vyas
  • 1,973
  • 1
  • 20
  • 43
  • In iOS - you can't. If you plan to submit your App to the App Store, Apple will reject the approach in the answer given below. Check out the answer from Apple engineer in this link: https://forums.developer.apple.com/thread/107046 – mac_eric Feb 29 '20 at 15:01
  • @mac_eric many are using signal strength to find out connection is poor or not is it not allowed in AppStore. ????? – Hardik Vyas Mar 02 '20 at 04:55
  • 1
    As far as I know, in iOS there is no Apple API that gives you access to the signal strength. The answer below does show a workaround which is also mentioned in the Apple developer forum link I posted earlier. Please read the second last reply from the Apple engineer. I just pointed out that using the answer below might get you problems if you submit your App using the code below to the App Store. – mac_eric Mar 02 '20 at 05:09
  • Ok i understand your point can we find this all apple policy document of apple. – Hardik Vyas Mar 02 '20 at 05:18

3 Answers3

5

I have founded solution and adding it here.

Simply create class and paste it and use it where you want.

protocol NetworkSpeedProviderDelegate: class {
    func callWhileSpeedChange(networkStatus: NetworkStatus)
   }
public enum NetworkStatus :String
{case poor; case good; case disConnected}

class NetworkSpeedTest: UIViewController {
    
    weak var delegate: NetworkSpeedProviderDelegate?
    var startTime = CFAbsoluteTime()
    var stopTime = CFAbsoluteTime()
    var bytesReceived: CGFloat = 0
    var testURL:String?
    var speedTestCompletionHandler: ((_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void)? = nil
    var timerForSpeedTest:Timer?
    
    func networkSpeedTestStart(UrlForTestSpeed:String!){
        testURL = UrlForTestSpeed
        timerForSpeedTest = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(testForSpeed), userInfo: nil, repeats: true)
    }
    func networkSpeedTestStop(){
        timerForSpeedTest?.invalidate()
    }
    @objc func testForSpeed()
    {
        testDownloadSpeed(withTimout: 2.0, completionHandler: {(_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void in
            print("%0.1f; KbPerSec = \(megabytesPerSecond)")
            if (error as NSError?)?.code == -1009
            {
                self.delegate?.callWhileSpeedChange(networkStatus: .disConnected)
            }
            else if megabytesPerSecond == -1.0
            {
                self.delegate?.callWhileSpeedChange(networkStatus: .poor)
            }
            else
            {
                self.delegate?.callWhileSpeedChange(networkStatus: .good)
            }
        })
    }
}
extension NetworkSpeedTest: URLSessionDataDelegate, URLSessionDelegate {

func testDownloadSpeed(withTimout timeout: TimeInterval, completionHandler: @escaping (_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void) {

    // you set any relevant string with any file
    let urlForSpeedTest = URL(string: testURL!)

    startTime = CFAbsoluteTimeGetCurrent()
    stopTime = startTime
    bytesReceived = 0
    speedTestCompletionHandler = completionHandler
    let configuration = URLSessionConfiguration.ephemeral
    configuration.timeoutIntervalForResource = timeout
    let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)

    guard let checkedUrl = urlForSpeedTest else { return }

    session.dataTask(with: checkedUrl).resume()
}

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    bytesReceived += CGFloat(data.count)
    stopTime = CFAbsoluteTimeGetCurrent()
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    let elapsed = (stopTime - startTime) //as? CFAbsoluteTime
    let speed: CGFloat = elapsed != 0 ? bytesReceived / (CGFloat(CFAbsoluteTimeGetCurrent() - startTime)) / 1024.0 : -1.0
    // treat timeout as no error (as we're testing speed, not worried about whether we got entire resource or not
    if error == nil || ((((error as NSError?)?.domain) == NSURLErrorDomain) && (error as NSError?)?.code == NSURLErrorTimedOut) {
        speedTestCompletionHandler?(speed, nil)
    }
    else {
        speedTestCompletionHandler?(speed, error)
    }
  }
}

After That how to use it.So implement delegate and use it.

class ViewController: UIViewController, NetworkSpeedProviderDelegate {
    func callWhileSpeedChange(networkStatus: NetworkStatus) {
        switch networkStatus {
        case .poor:
            break
        case .good:
            break
        case .disConnected:
            break
        }
    }
    
    let test = NetworkSpeedTest()
    override func viewDidLoad() {
        super.viewDidLoad()
        test.delegate = self
        test.networkSpeedTestStop()
        test.networkSpeedTestStart(UrlForTestSpeed: "Paste Your Any Working URL ")
        // Do any additional setup after loading the view.
    }
}
Hardik Vyas
  • 1,973
  • 1
  • 20
  • 43
0

You can call the Reachability class every time after a fixed interval by using method given below:

override func viewDidLoad() {               
       scheduledTimerWithTimeInterval()
   }
   func scheduledTimerWithTimeInterval(){
       // Scheduling timer to Call the function "updateCounting" with the interval of 'x' seconds 
       timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
   }

@objc func updateCounting(){
       \\Do your stuff here(Check Reachabilty Here)
   }

EDIT: This is how you can check signal strength for cellular networks.

func getSignalStrength() -> Int {

let application = UIApplication.shared
let statusBarView = application.value(forKey: "statusBar") as! UIView
let foregroundView = statusBarView.value(forKey: "foregroundView") as! UIView
let foregroundViewSubviews = foregroundView.subviews

var dataNetworkItemView:UIView? = nil

for subview in foregroundViewSubviews {

    if subview.isKind(of: NSClassFromString("UIStatusBarSignalStrengthItemView")!) {
        dataNetworkItemView = subview
        break
    }
}

 if dataNetworkItemView == nil
 {
    return 0
 }
return dataNetworkItemView?.value(forKey: "signalStrengthBars") as! Int

} 

For Wifi Network this is how you can get signal strength

private func getWiFiRSSI() -> Int? {
    let app = UIApplication.shared
    var rssi: Int?
    let exception = tryBlock {
        guard let statusBar = app.value(forKey: "statusBar") as? UIView else { return }
        if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return }

        guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return  }

        for view in foregroundView.subviews {
            if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
                if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
                    rssi = val
                    break
                }
            }
        }
    }
    if let exception = exception {
        print("getWiFiRSSI exception: \(exception)")
    }
    return rssi
}

EDIT 2: Add this extension to access your status bar view

extension UIApplication {
    var statusBarUIView: UIView? {
        if #available(iOS 13.0, *) {
            let tag = 38482458385
            if let statusBar = self.keyWindow?.viewWithTag(tag) {
                return statusBar
            } else {
                let statusBarView = UIView(frame: UIApplication.shared.statusBarFrame)
                statusBarView.tag = tag

                self.keyWindow?.addSubview(statusBarView)
                return statusBarView
            }
        } else {
            if responds(to: Selector(("statusBar"))) {
                return value(forKey: "statusBar") as? UIView
            }
        }
        return nil
    }
}
0

I have updated the top answer for async / await and modern Swift:

import Foundation

protocol NetworkSpeedDelegate: AnyObject {
    func speedDidChange(speed: NetworkSpeed)
}

public enum NetworkSpeed: String {
    case slow
    case fast
    case hostUnreachable
}

/// Class that tests the network quality for a given url
final class NetworkSpeedTester {
    
    private(set) var currentNetworkSpeed = NetworkSpeed.fast
    
    /// Delegate called when the network speed changes
    weak var delegate: NetworkSpeedDelegate?
    
    private let testURL: URL
    
    private var timerForSpeedTest: Timer?
    private let updateInterval: TimeInterval
    private let urlSession: URLSession
    
    
    /// Create a new instance of network speed tester.
    /// You need to call start / stop on this instance.
    ///
    /// - Parameters:
    ///   - updateInterval: the time interval in seconds to elapse between checks
    ///   - testUrl: the test url to check against
    init(updateInterval: TimeInterval, testUrl: URL) {
        self.updateInterval = updateInterval
        self.testURL = testUrl
        
        let urlSessionConfig = URLSessionConfiguration.ephemeral
        urlSessionConfig.timeoutIntervalForRequest = updateInterval - 1.0
        urlSessionConfig.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        self.urlSession = URLSession(configuration: urlSessionConfig)
    }
    
    deinit {
        stop()
    }
    
    /// Starts the check
    func start() {
        timerForSpeedTest = Timer.scheduledTimer(timeInterval: updateInterval,
                                                 target: self,
                                                 selector: #selector(testForSpeed),
                                                 userInfo: nil,
                                                 repeats: true)
    }
    
    /// Stops the check
    func stop(){
        timerForSpeedTest?.invalidate()
        timerForSpeedTest = nil
    }
    
    @objc private func testForSpeed() {
        Task {
            let startTime = Date()
            
            do {
                _ = try await urlSession.data(for: URLRequest(url: testURL))
                let endTime = Date()
                
                let duration = abs(endTime.timeIntervalSince(startTime))
                
                switch duration {
                case 0.0...4.0:
                    currentNetworkSpeed = .fast
                    delegate?.speedDidChange(speed: .fast)
                default:
                    currentNetworkSpeed = .slow
                    delegate?.speedDidChange(speed: .slow)
                }
            } catch let error {
                guard let urlError = error as? URLError else {
                    return
                }
                
                switch urlError.code {
                case    .cannotConnectToHost,
                        .cannotFindHost,
                        .clientCertificateRejected,
                        .dnsLookupFailed,
                        .networkConnectionLost,
                        .notConnectedToInternet,
                        .resourceUnavailable,
                        .serverCertificateHasBadDate,
                        .serverCertificateHasUnknownRoot,
                        .serverCertificateNotYetValid,
                        .serverCertificateUntrusted,
                        .timedOut:
                    currentNetworkSpeed = .hostUnreachable
                    delegate?.speedDidChange(speed: .hostUnreachable)
                default:
                    break
                }
            }
        }
    }
}
JuliusBahr
  • 91
  • 1
  • 3