Swift iOS-Что делать, когда есть подключение Wi-Fi, но нет подключения к интернету?
Я использую Alamofire в своем iOS-приложении. Я использую значения bool в viewWillAppear и в AppDelegate с NSNotifications, чтобы проверить, есть ли подключение к интернету. Если нет подключения Wi-Fi всплывающее окно появляется, чтобы сообщить пользователю. Если есть подключение Wi-Fi всплывающее окно исчезает, и все снова работает нормально. У меня не было никаких проблем, пока Wi-Fi явно не работает.
Я был на встрече, и кто-то объяснил мне, что это работает так, что он ищет соединение Wi-Fi и нет подключения к интернету. Например:. если у меня есть маршрутизатор Wi-Fi, и он подключен, но маршрутизатор не подключен к интернету, Alamofire будет рассматривать это как успешное соединение, потому что он на самом деле подключается к Wi-Fi, хотя он не знает, что Wi-Fi не может подключиться к интернету.
Я был просто в ситуации, когда я подключился к открытой сети, мое приложение первоначально реагировало, как если бы я действительно был подключен к интернету (нет всплывающего окна), но я не мог подключиться ни к чему. Интернет сигнал был включен на полную мощность. В терминале я запустил пинг, и оказалось, что связь была мертва. Мое приложение не могло определить разницу.
Как я могу заставить всплывающее окно появиться в таком месте?
И то, что есть .случай неизвестен для чего?
Alamofire.Свифт:
import Foundation
import Alamofire
open class NetworkManager {
open static var sharedManager: NetworkReachabilityManager = {
let reachabilityManager = NetworkReachabilityManager()
reachabilityManager?.listener = { (status) in
switch status {
case .notReachable:
print("The network is not reachable")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "unsuccessful"), object: nil)
case .unknown : //???????
print("It is unknown wether the network is reachable")
//I'm not sure whether to put a Notification for successful or unsuccessful???
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)
}
}
reachabilityManager?.startListening()
return reachabilityManager!
}()
}
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
NetworkManager.sharedManager.startListening()
SomeVC:
override func viewWillAppear() {
super.viewWillAppear()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(successful), name: "successful", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(unsuccessful), name: "unsuccessful", object: nil)
if NetworkManager.sharedManager.isReachable == true{
self.successful()
}else{
self.unsuccessful()
}
if NetworkManager.sharedManager.isReachableOnWWAN == true{
self.successful()
}else{
self.unsuccessful()
}
if NetworkManager.sharedManager.isReachableOnEthernetOrWiFi == true{
self.successful()
}else{
self.unsuccessful()
}
}
func successful(){
//dismiss pop up
}
func unsuccessful(){
//show pop up
}
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self, name: "successful", object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "unsuccessful", object: nil)
}
}
3 ответа:
Вы можете init
NetworkReachabilityManager
с хостом, например, Google host, потому что по умолчанию это0.0.0.0
let reachabilityManager = Alamofire.NetworkReachabilityManager(host: "www.google.com")
Когда вы начинаете слушать reachability manager, делающий ping хосту. Если сеть доступна, вы можете кэшировать SSID и пинговать снова, когда SSID изменился.
Для
case .unknown
лучше поставить уведомление о неудачном.Пример get SSID (он не работает в симуляторе):
func fetchSSIDInfo() -> String? { if let interfaces = CNCopySupportedInterfaces() { for i in 0..<CFArrayGetCount(interfaces){ let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i) let rec = unsafeBitCast(interfaceName, to: AnyObject.self) let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString) if let unsafeInterfaceData = unsafeInterfaceData as? Dictionary<AnyHashable, Any> { return unsafeInterfaceData["SSID"] as? String } } } return nil }
Вот ответ, состоящий из этих 2 решений: Павле Миятович и Ясин Угурлу
Создайте новый класс и назовите его Connection, а затем скопируйте и вставьте в него код, приведенный ниже:
import Foundation import SystemConfiguration class Connection { class func isConnectedToNetwork() -> Bool { var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { SCNetworkReachabilityCreateWithAddress(nil, $0) } }) else { return false } var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) if SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) == false { return false } let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 return isReachable && !needsConnection } class func isInternetAvailable(webSiteToPing: String?, completionHandler: @escaping (Bool) -> Void) { // 1. Check the WiFi Connection guard isConnectedToNetwork() else { completionHandler(false) return } // 2. Check the Internet Connection but possibly use www.apple.com or www.alibaba.com instead of google.com because it's not available in China var webAddress = "https://www.google.com" // Default Web Site if let _ = webSiteToPing { webAddress = webSiteToPing! } guard let url = URL(string: webAddress) else { completionHandler(false) print("could not create url from: \(webAddress)") return } let urlRequest = URLRequest(url: url) let session = URLSession.shared let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in if error != nil || response == nil { completionHandler(false) } else { completionHandler(true) } }) task.resume() } }
В любом контроллере вида, который вы используете в inside viewDidLoad, назовите его так:
override func viewDidLoad() { super.viewDidLoad() // if in china usewww.apple.com or www.alibaba.com instead Connection.isInternetAvailable(webSiteToPing: "https://www.google.com", completionHandler: { (bool) in if bool{ print("Internet connection is good") }else{ print("No internet connection can be found") } }) }
Пара вещей, которые я обнаружил, это, кажется, работает только один раз, потому что если соединение доступно
"Internet connection is good"
печатает, но если я иду в режим полета"No internet connection can be found"
не печатает. Это не активно слушает, что заставляет меня думать, что я должен подключиться к уведомлению? Другое дело, если ваш в Китае, то это не будет работать, потому что google не доступен в Китае. Может быть, лучший сайт для проверки будет www.apple.com или www.alibaba.com как кажется, они доступны везде.Connection.isInternetAvailable(webSiteToPing: "https://www.apple.com"...
Или
Connection.isInternetAvailable(webSiteToPing: "https://www.alibaba.com"...
Я следовал этому файлу AshleyMills Reachability, и он использует
google.com
для проверки соединения. Вы можете просто скопировать и вставить нижеприведенное в новый файл, чтобы проверить, как это работает. Если вы используете это в Китае, то используйтеalibaba.com
, потому что google недоступен в Китае.// everywhere outside China let hostNames = [nil, "google.com", "invalidhost"] // everywhere including China let hostNames = [nil, "alibaba.com", "invalidhost"]
При первом запуске приложения вы увидите, что метки меняются каждые 5 секунд, потому что внутри есть асинхронный таймер:
func startHost(at index: Int)
Если вы хотите остановить это и взять все под свой контроль самостоятельно затем прокомментируйте этот код:
// comment this out to stop the connection from changing DispatchQueue.main.asyncAfter(deadline: .now() + 5) { //self.startHost(at: (index + 1) % 3) }
Как только вы прокомментируете это, вы должны переключить соединение между Wifi, сотовой связью и режимом полета. Цвета меток будут меняться от зеленого до синего и красного в зависимости от соединения.
Создайте новый проект и добавьте его в файл
ViewController
:import UIKit class ViewController: UIViewController { let networkStatus: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.systemFont(ofSize: 21) label.textColor = .black label.numberOfLines = 0 label.sizeToFit() label.text = "Status" label.textAlignment = .center return label }() let hostNameLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.systemFont(ofSize: 21) label.textColor = .black label.numberOfLines = 0 label.sizeToFit() label.text = "Host" label.textAlignment = .center return label }() var reachability: Reachability? let hostNames = [nil, "google.com", "invalidhost"] var hostIndex = 0 override func viewDidLoad() { super.viewDidLoad() setConstraints() startHost(at: 1) } func startHost(at index: Int) { stopNotifier() setupReachability(hostNames[index], useClosures: true) startNotifier() // this loops the connection every 5 seconds DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.startHost(at: (index + 1) % 3) } } func setupReachability(_ hostName: String?, useClosures: Bool) { let reachability: Reachability? if let hostName = hostName { reachability = Reachability(hostname: hostName) hostNameLabel.text = hostName } else { reachability = Reachability() hostNameLabel.text = "No host name" } self.reachability = reachability print("--- set up with host name: \(hostNameLabel.text!)") if useClosures { reachability?.whenReachable = { reachability in self.updateLabelColourWhenReachable(reachability) } reachability?.whenUnreachable = { reachability in self.updateLabelColourWhenNotReachable(reachability) } } else { NotificationCenter.default.addObserver( self, selector: #selector(reachabilityChanged(_:)), name: .reachabilityChanged, object: reachability ) } } func startNotifier() { print("--- start notifier") do { try reachability?.startNotifier() } catch { networkStatus.textColor = .red networkStatus.text = "Unable to start\nnotifier" return } } func stopNotifier() { print("--- stop notifier") reachability?.stopNotifier() NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: nil) reachability = nil } func updateLabelColourWhenReachable(_ reachability: Reachability) { print("\(reachability.description) - \(reachability.connection)") if reachability.connection == .wifi { self.networkStatus.textColor = .green } else { self.networkStatus.textColor = .blue } self.networkStatus.text = "\(reachability.connection)" } func updateLabelColourWhenNotReachable(_ reachability: Reachability) { print("\(reachability.description) - \(reachability.connection)") self.networkStatus.textColor = .red self.networkStatus.text = "\(reachability.connection)" } @objc func reachabilityChanged(_ note: Notification) { let reachability = note.object as! Reachability if reachability.connection != .none { updateLabelColourWhenReachable(reachability) } else { updateLabelColourWhenNotReachable(reachability) } } func setConstraints(){ view.addSubview(networkStatus) view.addSubview(hostNameLabel) networkStatus.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true networkStatus.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true networkStatus.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true hostNameLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true hostNameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true hostNameLabel.topAnchor.constraint(equalTo: networkStatus.bottomAnchor, constant: 20).isActive = true } deinit { stopNotifier() } }
Создайте файл с именем Reachability и добавьте в него следующее:
import SystemConfiguration import Foundation public enum ReachabilityError: Error { case FailedToCreateWithAddress(sockaddr_in) case FailedToCreateWithHostname(String) case UnableToSetCallback case UnableToSetDispatchQueue } @available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") extension Notification.Name { public static let reachabilityChanged = Notification.Name("reachabilityChanged") } func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { guard let info = info else { return } let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue() reachability.reachabilityChanged() } public class Reachability { public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () @available(*, unavailable, renamed: "Connection") public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN public var description: String { switch self { case .reachableViaWWAN: return "Cellular" case .reachableViaWiFi: return "WiFi" case .notReachable: return "No Connection" } } } public enum Connection: CustomStringConvertible { case none, wifi, cellular public var description: String { switch self { case .cellular: return "Cellular" case .wifi: return "WiFi" case .none: return "No Connection" } } } public var whenReachable: NetworkReachable? public var whenUnreachable: NetworkUnreachable? @available(*, deprecated: 4.0, renamed: "allowsCellularConnection") public let reachableOnWWAN: Bool = true /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) public var allowsCellularConnection: Bool // The notification center on which "reachability changed" events are being posted public var notificationCenter: NotificationCenter = NotificationCenter.default @available(*, deprecated: 4.0, renamed: "connection.description") public var currentReachabilityString: String { return "\(connection)" } @available(*, unavailable, renamed: "connection") public var currentReachabilityStatus: Connection { return connection } public var connection: Connection { guard isReachableFlagSet else { return .none } // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi guard isRunningOnDevice else { return .wifi } var connection = Connection.none if !isConnectionRequiredFlagSet { connection = .wifi } if isConnectionOnTrafficOrDemandFlagSet { if !isInterventionRequiredFlagSet { connection = .wifi } } if isOnWWANFlagSet { if !allowsCellularConnection { connection = .none } else { connection = .cellular } } return connection } fileprivate var previousFlags: SCNetworkReachabilityFlags? fileprivate var isRunningOnDevice: Bool = { #if targetEnvironment(simulator) return false #else return true #endif }() fileprivate var notifierRunning = false fileprivate let reachabilityRef: SCNetworkReachability fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability") fileprivate var usingHostname = false required public init(reachabilityRef: SCNetworkReachability, usingHostname: Bool = false) { allowsCellularConnection = true self.reachabilityRef = reachabilityRef self.usingHostname = usingHostname } public convenience init?(hostname: String) { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil } self.init(reachabilityRef: ref, usingHostname: true) } public convenience init?() { var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size) zeroAddress.sa_family = sa_family_t(AF_INET) guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil } self.init(reachabilityRef: ref) } deinit { stopNotifier() } } public extension Reachability { // MARK: - *** Notifier methods *** func startNotifier() throws { guard !notifierRunning else { return } var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque()) if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { stopNotifier() throw ReachabilityError.UnableToSetCallback } if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { stopNotifier() throw ReachabilityError.UnableToSetDispatchQueue } // Perform an initial check reachabilitySerialQueue.async { self.reachabilityChanged() } notifierRunning = true } func stopNotifier() { defer { notifierRunning = false } SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) } // MARK: - *** Connection test methods *** @available(*, deprecated: 4.0, message: "Please use `connection != .none`") var isReachable: Bool { guard isReachableFlagSet else { return false } if isConnectionRequiredAndTransientFlagSet { return false } if isRunningOnDevice { if isOnWWANFlagSet && !reachableOnWWAN { // We don't want to connect when on cellular connection return false } } return true } @available(*, deprecated: 4.0, message: "Please use `connection == .cellular`") var isReachableViaWWAN: Bool { // Check we're not on the simulator, we're REACHABLE and check we're on WWAN return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet } @available(*, deprecated: 4.0, message: "Please use `connection == .wifi`") var isReachableViaWiFi: Bool { // Check we're reachable guard isReachableFlagSet else { return false } // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi guard isRunningOnDevice else { return true } // Check we're NOT on WWAN return !isOnWWANFlagSet } var description: String { let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" let t = isTransientConnectionFlagSet ? "t" : "-" let i = isInterventionRequiredFlagSet ? "i" : "-" let C = isConnectionOnTrafficFlagSet ? "C" : "-" let D = isConnectionOnDemandFlagSet ? "D" : "-" let l = isLocalAddressFlagSet ? "l" : "-" let d = isDirectFlagSet ? "d" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" } } fileprivate extension Reachability { func reachabilityChanged() { guard previousFlags != flags else { return } let block = connection != .none ? whenReachable : whenUnreachable DispatchQueue.main.async { if self.usingHostname { print("USING HOSTNAME ABOUT TO CALL BLOCK") } block?(self) self.notificationCenter.post(name: .reachabilityChanged, object:self) } previousFlags = flags } var isOnWWANFlagSet: Bool { #if os(iOS) return flags.contains(.isWWAN) #else return false #endif } var isReachableFlagSet: Bool { return flags.contains(.reachable) } var isConnectionRequiredFlagSet: Bool { return flags.contains(.connectionRequired) } var isInterventionRequiredFlagSet: Bool { return flags.contains(.interventionRequired) } var isConnectionOnTrafficFlagSet: Bool { return flags.contains(.connectionOnTraffic) } var isConnectionOnDemandFlagSet: Bool { return flags.contains(.connectionOnDemand) } var isConnectionOnTrafficOrDemandFlagSet: Bool { return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } var isTransientConnectionFlagSet: Bool { return flags.contains(.transientConnection) } var isLocalAddressFlagSet: Bool { return flags.contains(.isLocalAddress) } var isDirectFlagSet: Bool { return flags.contains(.isDirect) } var isConnectionRequiredAndTransientFlagSet: Bool { return flags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] } var flags: SCNetworkReachabilityFlags { var flags = SCNetworkReachabilityFlags() if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) { print("Returning flags \(flags)") return flags } else { return SCNetworkReachabilityFlags() } } }
Если вы хотите использовать это внутри вашего фактического проекта, то измените массив
hostNames
кому:let hostNames = ["google.com"] // or alibaba.com // move it to viewWillAppear because the reachability class property gets set to nil in stopNotifier when it runs inside deinit override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) startHost(at: 0) } // remove the dispatch async timer func startHost(at index: Int) { setupReachability(hostNames[index], useClosures: true) startNotifier() } // add everything else that's inside the ViewController File above