How to get SSID of currently connected Wifi Network in swift | iOS 14? - swift5

I need to get ssid of currently connected network. The reason I need this is to enable my app to perform certain functions when connected to a specific network. Now I cant seem to figure it out as in how to get the ssid? I've read online and implemented following things.
-> Allowed user location
-> Logged in to Apple dev account and enabled Wifi access.
The function I am using is
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedAlways {
NEHotspotNetwork.fetchCurrent { hotspotNetwork in
if let ssid = hotspotNetwork?.ssid {
print("SSID is \(ssid)")
}
}
}
}
But it is giving the following error
NEHotspotNetwork nehelper sent invalid result code [5] for Wi-Fi information request
What else am I missing here? Do i need to add anything else? Appreciate any help!

I have sorted out the way to get SSID of currently connected Wifi. Following are the pre-requisites to follow before writing the code.
-> You must have a paid developer account.
-> You must have a physical Device
-> You must enable Wifi-Entitlement by going to Target->Signing & Capabilities and adding Access WiFi Information or adding
<key>com.apple.developer.networking.wifi-info</key> <true/> directly to your entitlements file.
-> Allow location usage access from user
Then use this code in your class to get SSID.
import UIKit
import SystemConfiguration.CaptiveNetwork
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var currentNetworkInfos: Array<NetworkInfo>? {
get {
return SSID.fetchNetworkInfo()
}
}
let ssidLabel:UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
return lbl
}()
let bssidLabel:UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(ssidLabel)
view.addSubview(bssidLabel)
NSLayoutConstraint.activate([
ssidLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
ssidLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
bssidLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
bssidLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 20),
])
if #available(iOS 13.0, *) {
let status = CLLocationManager.authorizationStatus()
if status == .authorizedWhenInUse {
updateWiFi()
} else {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
} else {
updateWiFi()
}
}
func updateWiFi() {
print("SSID: \(currentNetworkInfos?.first?.ssid ?? "")")
if let ssid = currentNetworkInfos?.first?.ssid {
ssidLabel.text = "SSID: \(ssid)"
}
if let bssid = currentNetworkInfos?.first?.bssid {
bssidLabel.text = "BSSID: \(bssid)"
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
updateWiFi()
}
}
}
public class SSID {
class func fetchNetworkInfo() -> [NetworkInfo]? {
if let interfaces: NSArray = CNCopySupportedInterfaces() {
var networkInfos = [NetworkInfo]()
for interface in interfaces {
let interfaceName = interface as! String
var networkInfo = NetworkInfo(interface: interfaceName,
success: false,
ssid: nil,
bssid: nil)
if let dict = CNCopyCurrentNetworkInfo(interfaceName as CFString) as NSDictionary? {
networkInfo.success = true
networkInfo.ssid = dict[kCNNetworkInfoKeySSID as String] as? String
networkInfo.bssid = dict[kCNNetworkInfoKeyBSSID as String] as? String
}
networkInfos.append(networkInfo)
}
return networkInfos
}
return nil
}
}
struct NetworkInfo {
var interface: String
var success: Bool = false
var ssid: String?
var bssid: String?
}

check https://developer.apple.com/contact/request/hotspot-helper/
Before using NEHotspotHelper, you must first be
granted a special entitlement
(com.apple.developer.networking.HotspotHelper)

Related

How do I modify function to be used in MVP architecture?

I have the function below. It works properly.
When a user types any character it validates the user input and hides some imageView based on the input.
#IBAction func onEmailValueChanged(_ sender: UITextField) {
let hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: sender.text!)
passLengthCheckmarkImageView.isHidden = hasMinimumLength ? false : true
let hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: sender.text!)
passHasUppercaseCheckmarkImageView.isHidden = hasCapitalLetter ? false : true
let hasNumber = TextValidationHelper.validateHasNumber(password: sender.text!)
passHasNumberCheckmarkImageView.isHidden = hasNumber ? false : true
let hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: sender.text!)
passHasSymbolCheckmarkImageView.isHidden = hasSpecialCharacter ? false : true
resetButton.isHidden = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
}
But now I want to apply an MVP model on this function to remove the function from the ViewController file.
How can I do that?
Do I need to publish more code to make it possible to create an answer for this question?
It is not a good practice to use any architectural pattern only for method. So assuming you are having a complete app with many classes or files.
An important thing is that it is not fixed/compulsory to use any specific pattern. It actually depends on the code, sometimes you end up writing much code just to handle a method. So try to think the optimal approach to make the code more testable and scalable.
But for your reference, you can check the following code:
On ViewController:
lazy var presenter:Presenter = Presenter(view:self)
#IBAction func onEmailValueChanged(_ sender: UITextField) {
presenter.validateHasMinimumLength(password: sender.text!)
presenter.validateHasCapitalLetter(password: sender.text!)
presenter.validateHasNumber(password: sender.text!)
presenter.validateHasSpecialCharacter(password: sender.text!)
}
//Adopting ViewController:PrensenterViewProtocol on ViewController
extension ViewController:PrensenterViewProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool) {
passLengthCheckmarkImageView.isHidden = isHidden
}
func updateUpperCaseCheckmarkImageView(isHidden:Bool) {
passHasUppercaseCheckmarkImageView.isHidden = isHidden
}
func updateNumberCheckmarkImageView(isHidden:Bool) {
passHasNumberCheckmarkImageView.isHidden = isHidden
}
func updateSymbolCheckmarkImageView(isHidden:Bool) {
passHasSymbolCheckmarkImageView.isHidden = isHidden
}
func updateResetButton(isHidden:Bool) {
resetButton.isHidden = isHidden
}
}
PresenterView protocol as:
protocol PrensenterViewProtocol:NSObjectProtocol {
func updateLengthCheckmarkImageView(isHidden:Bool)
func updateUpperCaseCheckmarkImageView(isHidden:Bool)
func updateNumberCheckmarkImageView(isHidden:Bool)
func updateSymbolCheckmarkImageView(isHidden:Bool)
func updateResetButton(isHidden:Bool)
}
Presenter as:
class Presenter {
weak var view:PrensenterViewProtocol!
private var hasMinimumLength:Bool = false
private var hasCapitalLetter:Bool = false
private var hasNumber:Bool = false
private var hasSpecialCharacter:Bool = false
init(view:PrensenterViewProtocol) {
self.view = view
}
func validateHasMinimumLength(password:String?) {
hasMinimumLength = TextValidationHelper.validateHasMinimumLength(password: password)
self.view.updateLengthCheckmarkImageView(isHidden: hasMinimumLength)
checkAllValidations()
}
func validateHasCapitalLetter(password:String?) {
hasCapitalLetter = TextValidationHelper.validateHasCapitalLetter(password: password)
self.view.updateUpperCaseCheckmarkImageView(isHidden:hasCapitalLetter )
checkAllValidations()
}
func validateHasNumber(password:String?) {
hasNumber = TextValidationHelper.validateHasNumber(password: password)
self.view.updateNumberCheckmarkImageView(isHidden: hasNumber)
checkAllValidations()
}
func validateHasSpecialCharacter(password:String?) {
hasSpecialCharacter = TextValidationHelper.validateHasSpecialCharacter(password: password)
self.view.updateSymbolCheckmarkImageView(isHidden: hasSpecialCharacter)
checkAllValidations()
}
func checkAllValidations() {
let areAllValid:Bool = hasMinimumLength && hasCapitalLetter && hasNumber && hasSpecialCharacter ? false : true
self.view.updateResetButton(isHidden: areAllValid)
}
}

Swift: Get Current User Coordinates and Store them into a Variable

I am currently trying to get the current coordinates of the user and ultimately store those values into variables.
I have created the following class to define the users current location and set up functions to pull data.
import Foundation
import CoreLocation
class MyCurrentCoordinate: NSObject {
private var currentLocation: CLLocation!
var myLatitude = 0.0
var myLongitude = 0.0
var myAltitude = 0.0
override init() {
super.init()
}
func getLat() {
myLatitude = currentLocation.coordinate.latitude
}
func getLong() {
myLongitude = currentLocation.coordinate.longitude
}
func getAlt() {
myAltitude = currentLocation.altitude
}
}
This does not show any errors. However, when I go to call any function (getLat, getLong, or getAlt) to pull a piece of the users location data, the app crashes due the value being nil. Does anyone have any insight as to why the actual user lat, long, or altitude is not being passed?
I have the location permission and info.plist updated to allow the user to give location tracking permission.
import Foundation
import CoreLocation
import UIKit
public protocol LocalizationHelperDelegate: class {
func didUpdateLocation(_ sender: CLLocation)
}
public class LocalizationHelper: NSObject {
public weak var delegate: LocalizationHelperDelegate?
public static var shared = LocalizationHelper()
private lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBest
return locationManager
}()
private var currentLocation: CLLocationCoordinate2D?
public func startUpdatingLocation() {
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
public func stopUpdatingLocation() {
locationManager.stopUpdatingLocation()
}
public func getCurrentLocation() -> CLLocationCoordinate2D? {
return currentLocation
}
public func getLat() -> Double{
return currentLocation?.latitude ?? 0.0
}
public func getLon() -> Double{
return currentLocation?.longitude ?? 0.0
}
}
extension LocalizationHelper: CLLocationManagerDelegate {
public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = locations.first else { return }
currentLocation = location.coordinate
print("[Update location at - \(Date())] with - lat: \(currentLocation!.latitude), lng: \(currentLocation!.longitude)")
delegate?.didUpdateLocation(location)
}
}
How to use
LocalizationHelper.shared.Start()
...
let lat = LocalizationHelper.shared.getLat()
let lon = LocalizationHelper.shared.getLon()
...
LocalizationHelper.shared.Stop()
You can use CLLocationManager
Add app capability, you can open your info.plist like source code and add:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>App requires allways tracking</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>App requires background tracking</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>App requires tracking when be in use</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>remote-notification</string>
</array>
Ask for authorization like locationManager.requestAlwaysAuthorization() and manage if have the correct access... CLLocationManager.authorizationStatus() == .authorizedAlways
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
locationManager.allowsBackgroundLocationUpdates = false
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
}
extension ViewController: CLLocationManagerDelegate {
public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = locations.first else { return }
print(location)
}
public func locationManager(_: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
print("Autorization status did change \(status)")
case .authorizedWhenInUse:
print("Autorization status did change \(status)")
case .authorizedAlways:
print("Autorization status did change \(status)")
case .restricted:
print("Autorization status did change \(status)")
case .denied:
print("Autorization status did change \(status)")
#unknown default:
fatalError()
}
}
}
Don't forget to stop it in some place locationManager.stopUpdatingLocation()

Why is this Singleton Location Manager class returning a nil Location?

I am trying to use the below LocationSingleton Class from this blog in my project. I like the simplicity of its usage.
You start updating location by simply calling:
LocationSingleton.sharedInstance.startUpdatingLocation()
Get the last location by simply calling:
LocationSingleton.sharedInstance.lastLocation
My intention is to start location services, get the last location so that I can then fetch users from Firebase using the location returned.
The issue is that if I call lastLocation straight after startUpdatingLocation it returns nil.
After some debugging I've found the reason is because location services are slow to start on the device and therefore when lastLocation is called the devices hasn't acquired the location yet. I would like to execute the next command as soon as the lastLocation has been recorded. How can I achieve that?
I would like to understand how the Protocol is being used?
import UIKit
import CoreLocation
protocol LocationServiceDelegate {
func locationDidUpdateToLocation(currentLocation: CLLocation)
func locationUpdateDidFailWithError(error: NSError)
}
class LocationSingleton: NSObject,CLLocationManagerDelegate {
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
static let sharedInstance:LocationSingleton = {
let instance = LocationSingleton()
return instance
}()
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManagers=self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
//locationManagers.requestAlwaysAuthorization()
locationManagers.requestWhenInUseAuthorization()
}
if #available(iOS 9.0, *) {
// locationManagers.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
}
locationManagers.desiredAccuracy = kCLLocationAccuracyBest
locationManagers.pausesLocationUpdatesAutomatically = false
locationManagers.distanceFilter = 0.1
locationManagers.delegate = self
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.lastLocation = location
updateLocation(currentLocation: location)
}
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager?.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
locationManager?.startUpdatingLocation()
break
case .authorizedAlways:
locationManager?.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.locationDidUpdateToLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.locationUpdateDidFailWithError(error: error)
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
// self.locationManager?.startMonitoringSignificantLocationChanges()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
func startMonitoringSignificantLocationChanges() {
self.locationManager?.startMonitoringSignificantLocationChanges()
}
}
The location manager works asynchronously and provides delegate methods to get the result.
In your class adopt LocationServiceDelegate, implement the delegate methods and set the delegate for example in viewDidLoad
func locationDidUpdateToLocation(currentLocation: CLLocation)
{
print(LocationSingleton.sharedInstance.lastLocation)
}
func locationUpdateDidFailWithError(error: NSError)
{
print(error)
}
func viewDidLoad()
super viewDidLoad()
let locationSingleton = LocationSingleton.sharedInstance
locationSingleton.delegate = self
locationSingleton.startUpdatingLocation()
}
When a location is detected, one of the delegate methods is called

RxSwift Driver calling twice on first time

I have a CoreLocation manager that should handle all CLLocationManager by offering observable properties through RxSwift (and its Extensions and DelegateProxies). LocationRepository looks like this:
class LocationRepository {
static let sharedInstance = LocationRepository()
var locationManager: CLLocationManager = CLLocationManager()
private (set) var supportsRequiredLocationServices: Driver<Bool>
private (set) var location: Driver<CLLocationCoordinate2D>
private (set) var authorized: Driver<Bool>
private init() {
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
supportsRequiredLocationServices = Observable.deferred {
let support = CLLocationManager.locationServicesEnabled() && CLLocationManager.significantLocationChangeMonitoringAvailable() && CLLocationManager.isMonitoringAvailable(for:CLCircularRegion.self)
return Observable.just(support)
}
.asDriver(onErrorJustReturn: false)
authorized = Observable.deferred { [weak locationManager] in
let status = CLLocationManager.authorizationStatus()
guard let locationManager = locationManager else {
return Observable.just(status)
}
return locationManager.rx.didChangeAuthorizationStatus.startWith(status)
}
.asDriver(onErrorJustReturn: CLAuthorizationStatus.notDetermined)
.map {
switch $0 {
case .authorizedAlways:
return true
default:
return false
}
}
location = locationManager.rx.didUpdateLocations.asDriver(onErrorJustReturn: []).flatMap {
return $0.last.map(Driver.just) ?? Driver.empty()
}
.map { $0.coordinate }
}
func requestLocationPermission() {
locationManager.requestAlwaysAuthorization()
}
}
My presenter then listens to changes on the repository properties. LocatorPresenter looks like this:
class LocatorPresenter: LocatorPresenterProtocol {
weak var view: LocatorViewProtocol?
var repository: LocationRepository?
let disposeBag = DisposeBag()
func handleLocationAccessPermission() {
guard repository != nil, view != nil else {
return
}
repository?.authorized.drive(onNext: {[weak self] (authorized) in
if !authorized {
print("not authorized")
if let sourceView = self?.view! as? UIViewController, let authorizationView = R.storyboard.locator.locationAccessRequestView() {
sourceView.navigationController?.present(authorizationView, animated: true)
}
} else {
print("authorized")
}
}).addDisposableTo(disposeBag)
}
}
It does work, but I'm getting the Driver calling twice for the first time I try to get the authorization status, so the access request view gets presented twice. What am I missing here?
Regards!
From startWith documentation:
StartWith
emit a specified sequence of items before beginning to emit the items from the source Observable
I have not tried it, but probably if you remove startWith(status) you won't receive the status twice.
It seems you are receiving the next sequence from the observable:
---------------------------------unauthorized----authorized----->
So with the line:
startWith(status) // status is unauthorized
you finally get this one:
-------unauthorized---------unauthorized----authorized----->

Swift Beacon doesn't work now using Swift 3.0

I have been following a guide to using Swift to create a Beacon proximity app, but since updating Xcode and updating the code to Swift 3.0 I am getting a fatal error.
Going through the functions I think there is an issue with the startScanning function, when it fires I get fatal error message.
Any hints at to what could help would be greatly appreciated:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var distanceLabel: UILabel!
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
view.backgroundColor = UIColor.gray
print("did load")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedAlways{
print("status authorized")
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self){
print("is monitoring")
if CLLocationManager.isRangingAvailable() {
print("scanning")
startScanning()
}
}
}
}
func startScanning() {
print("start Scanning")
let uuid = NSUUID(uuidString: "695e5f08824c785cadc72e1dde23be04")
let beaconRegion = CLBeaconRegion(proximityUUID: uuid as! UUID, identifier: "MyBeacon")
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
}
func updateDistance(distance: CLProximity){
UIView.animate(withDuration: 1) { [unowned self] in
switch distance {
case .unknown:
self.view.backgroundColor = UIColor.gray
self.distanceLabel.text = "UNKNOWN"
print("distance Unknown")
case .far:
self.view.backgroundColor = UIColor.blue
self.distanceLabel.text = "FAR"
print("distance Far")
case .near:
self.view.backgroundColor = UIColor.orange
self.distanceLabel.text = "NEAR"
print("distance Near")
case .immediate:
self.view.backgroundColor = UIColor.red
self.distanceLabel.text = "BOOM!"
print("distance Immediate")
}
}
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if beacons.count > 0 {
let beacon = beacons.first! as CLBeacon
updateDistance(distance: beacon.proximity)
print("found more than one beacon")
} else {
updateDistance(distance: .unknown)
print("found only one beacon")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The problem is that your UUID is in the wrong format, so this line fails to parse it, assigning nil to the variable uuid:
let uuid = NSUUID(uuidString: "695e5f08824c785cadc72e1dde23be04")
The program will then crash using the uuid as! UUID operation because ! will crash if there is a nil value.
To fix this, you need to add dashes at the appropriate places in the UUID string. You should also avoid using the ! operator to force unwrap optional variables in Swift, as it can cause crashes like this. Try this:
if let uuid = NSUUID(uuidString: "695e5f08-824c-785c-adc7-2e1dde23be04") {
let beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "MyBeacon")
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
}
else {
NSLog("Invalid UUID format")
}
When running check that the code does not go down the "Invalid UUID format" path.