How to check if device is having poor internet connection in swift
Asked Answered
O

4

9

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.

Obbligato answered 29/2, 2020 at 10:28 Comment(4)
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: forums.developer.apple.com/thread/107046Titicaca
@Titicaca many are using signal strength to find out connection is poor or not is it not allowed in AppStore. ?????Obbligato
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.Titicaca
Ok i understand your point can we find this all apple policy document of apple.Obbligato
O
8

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.
    }
}
Obbligato answered 5/3, 2020 at 7:29 Comment(3)
Hii can we get the internet speed with your code?Shaddock
@YogeshPatel No it only giving this three status poor good or not connected.Obbligato
ok can you please look at the question I aded yesterday regarding this https://mcmap.net/q/1444199/-multipeer-connectivity-get-file-transfer-internet-speed-and-file-size-in-swift-5/8201581Shaddock
M
1

We can device the network connection strength based on the download and upload speed. Here is the code to check that -

import Foundation

enum SpeedTestError: Error {
    case pageNotFound
    case somethingWendWrong
}

protocol SpeedTesterDelegate {
    func didMeasuredDownloadSpeed(mbps: Double, kbps: Double)
    func didMeasuredUploadSpeed(mbps: Double, kbps: Double)
}

final public class SpeedTester: NSObject {

    /// The shared session
    private var session: URLSession
    /// Configuration
    private var configuration: URLSessionConfiguration
    /// After one minute the api will return timeout error
    private var timeout: TimeInterval = 60
    /// Wait until internet connectivity doesn't come back if gone.
    private let waitsForConnectivity: Bool = true

    var delegate: SpeedTesterDelegate?

    /// This will make this class as the singlton
    init(delegate: SpeedTesterDelegate? = nil) {
        /// ephemeral - a session configuration that uses no persistent storage for caches, cookies, or credentials.
        configuration = URLSessionConfiguration.ephemeral
        configuration.timeoutIntervalForRequest = timeout
        configuration.waitsForConnectivity = waitsForConnectivity
        session = URLSession.init(configuration: configuration)
        self.delegate = delegate

    }

    /// This function will make an api call to check the download spead. The actual speed calculation is happening in the delegate callback
    /// - Parameter url: URL to hit
    public func checkDownloadSpeed(url: String) async throws {

        guard let url = URL.init(string: url) else { throw SpeedTestError.pageNotFound }

        let urlRequest: URLRequest =  URLRequest.init(url: url)

        let _ = try await session.data(for: urlRequest, delegate: self)

    }

    /// This function will make an post api call to check the upload spead. The actual speed calculation is happening in the delegate callback.
    /// - Parameter url: URL to hit
    public func checkUploadSpeed(url: String) async throws {

        guard let url = URL.init(string: url) else { throw SpeedTestError.pageNotFound }

        var urlRequest: URLRequest =  URLRequest.init(url: url)

        urlRequest.httpMethod = "POST"

        /// Add your confuguration for the upload api.

        let dummyData = Data.init(count: 1024 * 1024)

        let _ = try await session.upload(for: urlRequest, from: dummyData, delegate: self)

    }
}

//MARK: - URLSessionTaskDelegate -
extension SpeedTester: URLSessionTaskDelegate {

    public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        /// Calculating the download speed
        if task.countOfBytesReceived > 0 {
            let speed = calculateSpeed(bytes: task.countOfBytesReceived, time: metrics.taskInterval.duration)
            print("Donwload Speed - ", speed.mbps, " Mbps ",  speed.kbps, " Kbps")
            self.delegate?.didMeasuredUploadSpeed(mbps: speed.mbps, kbps: speed.kbps)
        } else {
            print("Nothing have been recieved")
        }
        /// Calculating the upload speed
        if task.countOfBytesSent > 0 {
            let speed = calculateSpeed(bytes: task.countOfBytesSent, time: metrics.taskInterval.duration)
            print("Upload Speed - ", speed.mbps, " Mbps ",  speed.kbps, " Kbps")
            self.delegate?.didMeasuredUploadSpeed(mbps: speed.mbps, kbps: speed.kbps)
        } else {
            print("Nothing have been sent")
        }
    }

    private func calculateSpeed(bytes: Int64, time: TimeInterval) -> (mbps: Double, kbps: Double) {
        let oneSecondBytes = Double(bytes) / time
        /// Kbps - Kilobits per second so conver the bytes to bits by multiplying with 8, then device by 1000.
        let kbps = (oneSecondBytes * 8) / 1000
        /// Mbps - Megabits per second, device Kbps to 1000.
        let mpbs = kbps / 1000
        return (mbps: mpbs, kbps: kbps)
    }
}

You can call the checkDownloadSpeed or checkUploadSpeed functions with your respective api. And based on the speed you may define you strength parameter.

Madelainemadeleine answered 2/5 at 7:33 Comment(0)
R
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
    }
}
Ruiz answered 29/2, 2020 at 11:22 Comment(11)
Thank you for answer Actually from this i can only found if internet disconnected from device but if phone having poor connection than Reachability can't catch the issue and it's always return true that internet is connected.Obbligato
@HardikVyas you can check the signal strength of the connection.Ruiz
Can you please provide and example or link for it.Obbligato
@HardikVyas Modified my code for checking of signal strengthRuiz
my app get crash while i am calling getSignalStrenghtMethodObbligato
@HardikVyas Please tell the error it is displaying.Ruiz
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'App called -statusBar or -statusBarWindow on UIApplication: this code must be changed as there's no longer a status bar or status bar window. Use the statusBarManager object on the window scene instead.Obbligato
@HardikVyas updated the code to access statusBar viewRuiz
It's giving error "Use of unresolved identifier 'tryBlock' " in wifi methodObbligato
and it's still crashing even after add extension of statusbar.Obbligato
Don't do that on iOS because it involves grovelling around in the internal structures of the system’s views, Apple doesn't allow this and your app will be rejected.Meara
T
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
                }
            }
        }
    }
}
Tar answered 25/7, 2023 at 9:9 Comment(2)
@JulliusBahr this code shouldn't be a problem while submitting app in App Store?Sign
no there is no problem with the code within an app distributed via the AppStore.Tar

© 2022 - 2024 — McMap. All rights reserved.