I'm newbie in iOS development(SwiftUI).
Recently, I have encountered a problem about connecting to WiFi by capturing QR-Code.
I wonder that if is there any possible solution to connect to WiFi directly by using QR-Code.
After I did a lot of research, I still cannot find any references about this issue.
There are my keywords: SwiftUI, Swift, QR-Code, Wi-Fi, iOS.
What do I have now:
ContentView
struct ContentView: View {
#State private var isShowingScanner = false
#State private var resultOfScanning: String = "Result will be shown here..."
var body: some View {
VStack {
Button(action: { self.isShowingScanner = true }, label: { Text("Scan Button") })
.sheet(isPresented: self.$isShowingScanner) {
CodeScannerView(codeTypes: [.qr], simulatedData: "www.opgg.com", completion: self.handleScan)
}
Text("\(resultOfScanning)")
}
}
func handleScan(result: Result<String, CodeScannerView.ScanError>) {
self.isShowingScanner = false
switch result {
case .success(let code):
let codeStr = code as! String
self.resultOfScanning = codeStr
case .failure(let error):
print("Scanning failed")
}
}
}
CodeScannerView (Reference: An article by Paul Hudson)
What do I wanna build:
I want to use this App, connecting to the specific WiFi automatically by capturing QR-Code(It contains SSID & Password of WiFi).
What is my problem:
I cannot find a way to connect to the specific WiFi automatically by using QR-Code. It seems that no one have talked about this issue.
May someone know that how to solve the problems like above-mentioned?
Thanks for comments and answers.
We have four steps to deal with this issue.
1.Capture the QR-Code which contains the specific WiFi hotspot information(SSID, Password, Encryption Type).
2.Convert JSON data of QR-Code to Dictionary.
3.Get the SSID, Password and Encryption type value from before-mentioned Dictionary.
4.Use NEHotspotConfiguration of Apple API to set our SSID, Password and Encryption type and connect to the specific WiFi Hotspot.
p.s. Your app needs the signing certificate to active some function of your project and Apple API.
Let's see what have I done here:
func handleScan(result: Result<String, CodeScannerView.ScanError>) {
switch result {
case .success(let code):
let data_code = code.data(using: .utf8)
do {
let dict_code = try JSONSerialization.jsonObject(with: data_code!, options: .allowFragments) as! [String : Any]
let wifi_ssid = dict_code["S"] as! String
let wifi_pwd = dict_code["P"] as! String
let wifi_type = dict_code["T"] as! String
let configuration = NEHotspotConfiguration.init(ssid: wifi_ssid, passphrase: wifi_pwd, isWEP: self.checkWifiType(type: wifi_type))
configuration.joinOnce = true
NEHotspotConfigurationManager.shared.apply(configuration) {
(error) in
if error != nil {
if let errorStr = error?.localizedDescription {
print("Error Information:\(errorStr)")
}
if (error?.localizedDescription == "already associated.") {
print("Connected!")
} else {
print("No Connected!")
}
} else {
print("Connected!")
}
}
print("Dict_Code:\(dict_code)")
} catch (let error) {
print("JSONSerial... Convert Error:\(error.localizedDescription)")
}
case .failure(let error):
self.connectionStatus = "Scanning failed!"
}
}
After doing this, I finally can scan my own QR-Code and connect to the specific WiFi Hotspot.
Related
The two conversion methods below for mapping between the PHPhoto localIdentifier to the corresponding cloudIdentifier work but it feels too heavy. Do you have suggestions on how to rewrite to a more elegant (easier to read) form?
The sample code in the Apple documentation found in PHCloudIdentifier https://developer.apple.com/documentation/photokit/phcloudidentifier/ does not compile in xCode 13.2.1.
It was difficult to rewrite the sample code because I made the mistake of interpreting the Result type as a tuple. Result type is really an enum.
func localId2CloudId(localIdentifiers: [String]) -> [String] {
var mappedIdentifiers = [String]()
let library = PHPhotoLibrary.shared()
let iCloudIDs = library.cloudIdentifierMappings(forLocalIdentifiers: localIdentifiers)
for aCloudID in iCloudIDs {
//'Dictionary<String, Result<PHCloudIdentifier, Error>>.Element' (aka '(key: String, value: Result<PHCloudIdentifier, Error>)')
let cloudResult: Result = aCloudID.value
// Result is an enum .. not a tuple
switch cloudResult {
case .success(let success):
let newValue = success.stringValue
mappedIdentifiers.append(newValue)
case .failure(let failure):
// do error notify to user
let iCloudError = savePhotoError.otherSaveError // need to notify user
}
}
return mappedIdentifiers
}
func cloudId2LocalId(assetCloudIdentifiers: [PHCloudIdentifier]) -> [String] {
// patterned error handling per documentation
var localIDs = [String]()
let localIdentifiers: [PHCloudIdentifier: Result<String, Error>]
= PHPhotoLibrary
.shared()
.localIdentifierMappings(
for: assetCloudIdentifiers)
for cloudIdentifier in assetCloudIdentifiers {
guard let identifierMapping = localIdentifiers[cloudIdentifier] else {
print("Failed to find a mapping for \(cloudIdentifier).")
continue
}
switch identifierMapping {
case .success(let success):
localIDs.append(success)
case .failure(let failure) :
let thisError = failure as? PHPhotosError
switch thisError?.code {
case .identifierNotFound:
// Skip the missing or deleted assets.
print("Failed to find the local identifier for \(cloudIdentifier). \(String(describing: thisError?.localizedDescription)))")
case .multipleIdentifiersFound:
// Prompt the user to resolve the cloud identifier that matched multiple assets.
default:
print("Encountered an unexpected error looking up the local identifier for \(cloudIdentifier). \(String(describing: thisError?.localizedDescription))")
}
}
}
return localIDs
}
I am using pubnub SDk for socket stuffs. pubnub provides a feature to know the online/offline users with it's method hereNow. But it's not getting called when the user gets online or goes offline as this should be involked like socket listener. If anybody has some idea about this please help me out.
func registerForConnectRoom(roomId: String) {
pubnub.subscribe(to: ["ch:callroom:" + roomId], withPresence: true)
let listener = SubscriptionListener()
listener.didReceiveSubscription = { event in
switch event {
case let .messageReceived(message):
print("heuuu \(message)")
case let .connectionStatusChanged(status):
print("Status Received: \(status)")
case let .presenceChanged(presence):
print("Presence Received: \(presence)")
case let .subscribeError(error):
print("Subscription Error \(error)")
default:
break
}
}
}
func hereNow(roomId: String) {
pubnub.hereNow(on: ["ch:callroom:" + roomId], includeState: true) { result in
switch result {
case let .success(presenceByChannel):
print("Total channels \(presenceByChannel.totalChannels)")
print("Total occupancy across all channels \(presenceByChannel.totalOccupancy)")
if let myChannelPresence = presenceByChannel["ch:callroom:" + roomId] {
print("The occupancy for `my_channel` is \(myChannelPresence.occupancy)")
print("The list of occupants for `my_channel` are \(myChannelPresence.occupants)")
}
case let .failure(error):
print("Failed hereNow Response: \(error.localizedDescription)")
}
}
}
calling these methods in viewDidLoad
I want to check if the wifi is off then show alert to the user to check his/her connectivity.
I find code like this but it checks if there is an internet connection, not checking if the wifi is on or off:
func isConnectionAvailble()->Bool{
var rechability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "www.apple.com").takeRetainedValue()
var flags : SCNetworkReachabilityFlags = 0
if SCNetworkReachabilityGetFlags(rechability, &flags) == 0
{
return false
}
let isReachable = (flags & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
You can't.
With Apple's reachability class, you can distinguish three things according to the NetworkStatus struct:
typedef enum : NSInteger {
NotReachable = 0, // 1
ReachableViaWiFi, // 2
ReachableViaWWAN // 3
} NetworkStatus;
You have neither WiFi nor mobile data connection.
You have a WiFi connection, but you may or may not have a mobile data connection.
You have a mobile data connection, but no WiFi connection.
You can't check whether WiFi is turned off, or whether WiFi is turned on but there is no WiFi network nearby, or whether Airplane mode has been turned on.
For mobile data, you can use the telephony class to find whether your device is capable of mobile data connections (iPhone and not iPad, and SIM card plugged in), and you can detect whether mobile data is disabled in the preferences of your application.
Found the following, which was really helpful for me (found on the Apple Developer Forums). The below code works with Swift 4.
func fetchSSIDInfo() -> String {
var currentSSID = ""
if let interfaces:CFArray = 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 unsafeInterfaceData != nil {
let interfaceData = unsafeInterfaceData! as Dictionary!
for dictData in interfaceData! {
if dictData.key as! String == "SSID" {
currentSSID = dictData.value as! String
}
}
}
}
}
return currentSSID
}
You can then check if a device is connected to Wi-Fi by the following:
if fetchSSIDInfo() != nil {
/* Wi-Fi is Connected */
}
Not perfect, but if the device is not connected to a Wi-Fi Network, you could then ask the user to connect to a Wi-Fi Network:
let wifiNotifcation = UIAlertController(title: "Please Connect to Wi-Fi", message: "Please connect to your standard Wi-Fi Network", preferredStyle: .alert)
wifiNotifcation.addAction(UIAlertAction(title: "Open Wi-Fi", style: .default, handler: { (nil) in
let url = URL(string: "App-Prefs:root=WIFI")
if UIApplication.shared.canOpenURL(url!){
UIApplication.shared.openURL(url!)
self.navigationController?.popViewController(animated: false)
}
}))
self.present(wifiNotifcation, animated: true, completion: nil)
Tested with swift 4 and swift 5
let nwPathMonitor = NWPathMonitor()
nwPathMonitor.pathUpdateHandler = { path in
if path.usesInterfaceType(.wifi) {
print("Path is Wi-Fi")
} else if path.usesInterfaceType(.cellular) {
print("Path is Cellular")
} else if path.usesInterfaceType(.wiredEthernet) {
print("Path is Wired Ethernet")
} else if path.usesInterfaceType(.loopback) {
print("Path is Loopback")
} else if path.usesInterfaceType(.other) {
print("Path is other")
}
}
nwPathMonitor.start(queue: .main)
As already #abba_de_bo mentioned: you could fetch the current SSID and check if it's set or nil.
This is the answer Apple's Eskimo gave to this question:
The trick with using CF-based APIs from Swift is to get the data into ‘Swift space’ as quickly as possible.
func currentSSIDs() -> [String] {
guard let interfaceNames = CNCopySupportedInterfaces() as? [String] else {
return []
}
return interfaceNames.flatMap { name in
guard let info = CNCopyCurrentNetworkInfo(name as CFString) as? [String:AnyObject] else {
return nil
}
guard let ssid = info[kCNNetworkInfoKeySSID as String] as? String else {
return nil
}
return ssid
}
}
Note that this returns an array of names; how you handle the non-standard cases (no elements, more than one element) is up to you.
Make sure you import SystemConfiguration.CaptiveNetwork. Otherwise the build will fail with on of those error messages:
Use of unresolved identifier 'CNCopySupportedInterfaces'
Use of unresolved identifier 'CNCopyCurrentNetworkInfo'
Use of unresolved identifier 'kCNNetworkInfoKeySSID'
You can take a look at the official Apple sample for Reachability:
https://developer.apple.com/library/content/samplecode/Reachability/Introduction/Intro.html
var netStatus = reachability.currentReachabilityStatus()
var connectionRequired = reachability.connectionRequired()
var statusString = ""
switch netStatus {
case NotReachable:
break
case ReachableViaWWAN:
//DATA
break
case ReachableViaWiFi:
//WIFI
break
}
You can use this method to check:
First you import this framework:
import SystemConfiguration.CaptiveNetwork
func isWifiEnabled() -> Bool {
var hasWiFiNetwork: Bool = false
let interfaces: NSArray = CFBridgingRetain(CNCopySupportedInterfaces()) as! NSArray
for interface in interfaces {
// let networkInfo = (CFBridgingRetain(CNCopyCurrentNetworkInfo(((interface) as! CFString))) as! NSDictionary)
let networkInfo: [AnyHashable: Any]? = CFBridgingRetain(CNCopyCurrentNetworkInfo(((interface) as! CFString))) as? [AnyHashable : Any]
if (networkInfo != nil) {
hasWiFiNetwork = true
break
}
}
return hasWiFiNetwork;
}
Trying to get the SSID of current device. I have found plenty of examples on how to do it however I am struggling with getting the CNCopySupportedInterfaces to autocomplete. I have 'import SystemConfiguration' at the top of my swift file but no success. Can't seem to figure out what I am doing wrong.
iOS 12
You must enable Access WiFi Information from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
You need: import SystemConfiguration.CaptiveNetwork
Underneath the covers, CaptiveNetwork is a C header file (.h) that is within the SystemConfiguration framework:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SystemConfiguration.framework/Headers/CaptiveNetwork.h
If you know Objective-C, this goes into more depth:
iPhone get SSID without private library
You have to use the awkward syntax to bridge from any pure C API, so the following is required:
for interface in CNCopySupportedInterfaces().takeRetainedValue() as! [String] {
println("Looking up SSID info for \(interface)") // en0
let SSIDDict = CNCopyCurrentNetworkInfo(interface).takeRetainedValue() as! [String : AnyObject]
for d in SSIDDict.keys {
println("\(d): \(SSIDDict[d]!)")
}
}
ADDENDUM FOR SWIFT 2.2 and 3.0
The CFxxx datatypes are now bridged to native Objective-C runtime, eliminating the head-scratching retain calls. However, nullable pointers give rise to Optionals, so things don't get any shorter. At least, it's fairly clear what's going on, plus the nil helps us identify the simulator. The other answer uses an awful lot of bit-casting and unsafe operations which seems non-Swiftian, so I offer this.
func getInterfaces() -> Bool {
guard let unwrappedCFArrayInterfaces = CNCopySupportedInterfaces() else {
print("this must be a simulator, no interfaces found")
return false
}
guard let swiftInterfaces = (unwrappedCFArrayInterfaces as NSArray) as? [String] else {
print("System error: did not come back as array of Strings")
return false
}
for interface in swiftInterfaces {
print("Looking up SSID info for \(interface)") // en0
guard let unwrappedCFDictionaryForInterface = CNCopyCurrentNetworkInfo(interface) else {
print("System error: \(interface) has no information")
return false
}
guard let SSIDDict = (unwrappedCFDictionaryForInterface as NSDictionary) as? [String: AnyObject] else {
print("System error: interface information is not a string-keyed dictionary")
return false
}
for d in SSIDDict.keys {
print("\(d): \(SSIDDict[d]!)")
}
}
return true
}
Output on success:
SSIDDATA: <57696c6d 79>
BSSID: 12:34:56:78:9a:bc
SSID: YourSSIDHere
In Swift 2.0 / iOS 9 the API CaptiveNetwork is (nearly) gone or depreciated. I contacted Apple regarding this problem and I thought we could (or should) use the NEHotspotHelper instead. I got a respond from Apple today: One should continue to use CaptiveNetwork and the two relevant APIs (even tough there marked depreciated):
CNCopySupportedInterfaces
CNCopyCurrentNetworkInfo
The user braime posted an updated code-snippet for this problem on Ray Wenderlich forums:
let interfaces:CFArray! = CNCopySupportedInterfaces()
for i in 0..<CFArrayGetCount(interfaces){
let interfaceName: UnsafePointer<Void>
= CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, AnyObject.self)
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)")
if unsafeInterfaceData != nil {
let interfaceData = unsafeInterfaceData! as Dictionary!
currentSSID = interfaceData["SSID"] as! String
} else {
currentSSID = ""
}
}
Works perfect for me.
Swift:
import SystemConfiguration.CaptiveNetwork
func currentSSIDs() -> [String] {
guard let interfaceNames = CNCopySupportedInterfaces() as? [String] else {
return []
}
return interfaceNames.flatMap { name in
guard let info = CNCopyCurrentNetworkInfo(name as CFString) as? [String:AnyObject] else {
return nil
}
guard let ssid = info[kCNNetworkInfoKeySSID as String] as? String else {
return nil
}
return ssid
}
}
Then print(currentSSIDs()), not working on simulator, only real devices.
Taken from https://forums.developer.apple.com/thread/50302
func getInterfaces() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}
In iOS 12 and up you will need to enable the Access WiFi Information capability for your app in order to get the ssid
I want to get all the WiFi networks available in a region and their SSID value. But the problem is how to get the SSID of all the WiFi network available even if I am not connected to one.
iOS 12
You must enable Access WiFi Information from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
First;
import SystemConfiguration.CaptiveNetwork
Then;
func getInterfaces() -> Bool {
guard let unwrappedCFArrayInterfaces = CNCopySupportedInterfaces() else {
print("this must be a simulator, no interfaces found")
return false
}
guard let swiftInterfaces = (unwrappedCFArrayInterfaces as NSArray) as? [String] else {
print("System error: did not come back as array of Strings")
return false
}
for interface in swiftInterfaces {
print("Looking up SSID info for \(interface)") // en0
guard let unwrappedCFDictionaryForInterface = CNCopyCurrentNetworkInfo(interface) else {
print("System error: \(interface) has no information")
return false
}
guard let SSIDDict = (unwrappedCFDictionaryForInterface as NSDictionary) as? [String: AnyObject] else {
print("System error: interface information is not a string-keyed dictionary")
return false
}
for d in SSIDDict.keys {
print("\(d): \(SSIDDict[d]!)")
}
}
return true
}
Here my class that prints the WIFI network name
import UIKit
import Foundation
import SystemConfiguration.CaptiveNetwork
class FirstView: UIViewController
{
#IBOutlet weak var label: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
let ssid = self.getWiFiName()
print("SSID: \(ssid)")
}
func getWiFiName() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}
}
Yes it is possible to list all nearby WiFi networks.You need to complete a questionnaire at https://developer.apple.com/contact/network-extension, and then you can use NEHotspotHelper to return a list of hotspots. Technical Q&A https://developer.apple.com/library/archive/qa/qa1942/_index.html