My app currently shows an alert if there isn't an internet connection. However, I would like it to reconnect automatically once internet connection is detected without user needing to restart the app.
the code for I used currently is
if Reachability.isConnectedToNetwork() == true {
print("Internet Connection Available!")
} else {
let alertController = UIAlertController(title: "Alert",
message: "Internet Connection not Available!",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
}
anyone can give some advice? if my question isn't clear, do let me know. Thx guys!
You should addObserver of Reachability in applicationDidFinishLaunching method, like below,
NotificationCenter.default.addObserver(self, selector: #selector(networkStatusChanged(_:)), name: NSNotification.Name(rawValue: ReachabilityStatusChangedNotification), object: nil)
Reach().monitorReachabilityChanges()
Implement this method as well,
#objc func networkStatusChanged(_ notification: Notification) {
let userInfo = (notification as NSNotification).userInfo
if let status = userInfo?["Status"] as? String, status == "Offline" {
//Display alert view
} else if let status = userInfo?["Status"] as? String, status != "Unknown" {
//Internet connection is active
}
}
Above function automatically triggers call when there is active internet connection.
It's Working well for me
func myconn(){
if Reachability.isConnectedToNetwork() == true
{
print("Internet Connection Available!")
}
else
{
let alertController = UIAlertController(title: "Alert", message:
"Internet Connection not Available!", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: {
self.myconn()
})
}
}
You need to add addObserver of ReachabilityChangedNotification.
NSNotificationCenter.defaultCenter().addObserver(self, selector:"checkForReachability:", name: ReachabilityChangedNotification, object: nil);
self.reachability = Reachability.reachabilityForInternetConnection();
self.reachability.startNotifier();
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as Reachability;
var remoteHostStatus = networkReachability.currentReachabilityStatus()
if (remoteHostStatus.value == NotReachable.value)
{
print("Not Reachable")
}
else if (remoteHostStatus.value == ReachableViaWiFi.value)
{
print("Reachable via Wifi")
}
else
{
print("Reachable")
}
}
Whenever change the network it will notify you.
You could also use RxSwift to observe you reachability notifications and deal with the changes whenever you get a new connection state.
like this:
var isOnline: Bool {
guard let reachability = reachability else { return false }
return reachability.currentReachabilityStatus.isOnline
}
func connectionStatus() -> Observable<ConnectionStatus> {
return notificationCenter
.rx
.notification(ReachabilityChangedNotification)
.observeOn(observeScheduler)
.flatMap { notification -> Observable<ConnectionStatus> in
guard let reachability = notification.object as? Reachability else {
return .empty()
}
return .just(ConnectionStatus(isOnline: reachability.isReachable))
}
.startWith(ConnectionStatus(isOnline: isOnline))
.distinctUntilChanged()
}
This way you'll be observing any changes in the connection and can react to it the you you want.
You just need to Subscribe to the Observable<ConnectionStatus> and then you can decide if you want the user to trigger a new reconnection flow or if you would retry a few times before displaying it.
Related
I have been trying to show an alert for internet connection is available or not. Implemented code for checking the internet and it works fine. But, when the internet is not readable, I am trying to show alert but alert show twice.
here is the code tried:
#objc func reachabilityChanged(_ note: NSNotification) {
let reachability = note.object as! Reachability
if reachability.connection != .unavailable {
if reachability.connection == .wifi {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
} else {
print("Not reachable")
let alert = UIAlertController(title: "No Internet Connection", message: "Make sure your device is connected to the internet.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
// self.window?.rootViewController?.present(alert, animated: true, completion: nil)
topMostViewController().present(alert, animated: true, completion: nil)
// let alertVC = UIAlertController(title: "No Internet Connection" , message: "Make sure your device is connected to the internet.", preferredStyle: UIAlertController.Style.alert)
// let okAction = UIAlertAction(title: "Okay", style: UIAlertAction.Style.cancel) { (alert) in
//// exit(0) // Your code here
// }
// alertVC.addAction(okAction)
// DispatchQueue.main.async {
// var presentVC = self.window?.rootViewController
// while let next = presentVC?.presentedViewController {
// presentVC = next
// }
// presentVC?.present(alertVC, animated: true, completion: nil)
// }
}
}
func topMostViewController() -> UIViewController {
var topViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
while ((topViewController?.presentedViewController) != nil) {
topViewController = topViewController?.presentedViewController
}
return topViewController!
}
In my scenario, User will get an alert for receiving Notification in application. If the user clicks on "Don't Allow" UILabel is updated with "Not enabled". If the user wants to change the notification,User will be navigated to application setting page to change the notification permission status.
func checkNotificationPermission(){
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]){
(granted, error) in
if granted == true {
DispatchQueue.main.async {
print("notificaation access true")
self.notificationAccessLbl?.text = "Enabled"
}
}
else {
DispatchQueue.main.async {
self.notificationAccessLbl?.text = "Not enabled"
}
}
}
UIApplication.shared.registerForRemoteNotifications() }
But when the user comes back to application, The UILabel is not getting updated when the user comes to application from Setting page.
for Updating the UILabel in application after the user comes from setting page to application. I Have Called
func checkNotificationPermission()
to update UILabel Value in ViewDidAppear() Method and I register the a function in applicationwillbecomeactive method() Kindly help me in this.
I have switch in setting page in application which allows user to enable disable push and that will be send on server but before that user must have allowed push from settings page of Device. here is my solution
I have created global object
var isPushEnabledFromSettings = false {
didSet {
// you can set label value here in main queue
}
}
and one method to check it
func isPushPermissionGiven (permission:#escaping (Bool) -> ()) {
if #available(iOS 10.0, *) {
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: {settings in
switch settings.authorizationStatus {
case .notDetermined:
permission(false)
case .denied:
permission(false)
case .authorized:
permission(true)
}
})
} else {
// Fallback on earlier versions
if UIApplication.shared.isRegisteredForRemoteNotifications {
permission(true)
} else {
permission(false)
}
}
}
and in view did load added these lines
self.isPushPermissionGiven { (permission) in
self.isPushEnabledFromSettings = permission
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationDidBecomeActive, object: nil, queue: .main) {[weak self] (notificaiont) in
guard let strongSelf = self else {return }
strongSelf.isPushPermissionGiven { (permission) in
DispatchQueue.main.async {
strongSelf.isPushEnabledFromSettings = permission
}
}
}
Now I have switch in setting page which allows user to enable disable push
#objc func switchChanged (sender:UISwitch) {
guard self.isPushEnabledFromSettings else {
AppDelegate.sharedDelegate.navigateUesrToSettings(withMessage: "Please grant push permission from settings")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
sender.setOn(false, animated: false)
})
return
}
}
func navigateUesrToSettings (withTitle title:String = "YourApp", withMessage message:String) {
let alertController = UIAlertController (title: title, message: message, preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let _ = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
self.navigate(To: UIApplicationOpenSettingsURLString)
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
AppDelegate.sharedDelegate.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
Hope it is helpful to you :)
I am currently having problems with delay of presenting a new alert controller after dismissing another alert controller.
My situation is as below:
There are two roles for peer to peer connection using MPC: Master and Slave.
Ideal case: only one master available
However, each device can set as Master when there is no connection between devices.
When both master devices are connected, there would be a conflict to the ideal case.
Therefore, I want to make an election of Master.
When two devices which are both Master individually
are now in connection, then alert controller of Master election will prompt in each device. When one of the device tabs "Stay As Master" button first, then another device will dismiss election Alert Controller and prompt "xxx remains as master" alert.
However, I discover that there is delay of dismissing the previous Alert Controller.
Codes in MainVC:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(testing), name: NSNotification.Name(rawValue: "hasMaster"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(stayAsMaster), name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil)
}
func testing(notification: Notification){
if(appDelegate.mpcHandler.checkNoOfPeersConnected()>0){
if self.appDelegate.masterSlaveDataController.checkHasMaster() && self.appDelegate.masterSlaveDataController.checkMaster(){
self.promptHasMaster()
}
else{
DispatchQueue.main.asyncAfter(deadline: .now()){
if self.appDelegate.masterSlaveDataController.checkHasMaster() && self.appDelegate.masterSlaveDataController.checkMaster(){
self.promptHasMaster()
}
}
}
}
}
func promptHasMaster(){//prompt Elect Master Alert Controller
let alertController = UIAlertController(title: "Elect Master", message: "There are more than one masters in the connection. One of you has to give up the master role and switch to slave!", preferredStyle: .alert)
let peerID = self.appDelegate.mpcHandler.session.myPeerID
let m = UIAlertAction(title: "Stay as Master", style: .default, handler: { (alert) in
self.appDelegate.mpcHandler.send(d: self.mToDictionary(peerID: peerID, action: "stayAsMaster")!)
})
let s = UIAlertAction(title: "Switch to Slave", style: .default, handler: { (alert) in
self.appDelegate.mpcHandler.send(d: self.mToDictionary(peerID: peerID, action: "switchToSlave")!)
})
alertController.addAction(m)
alertController.addAction(s)
self.present(alertController, animated: true, completion: nil)
}
//Create NSDictionary for sending peerID
func mToDictionary(peerID: MCPeerID, action: String) -> [NSDictionary]?{
var dict: [NSDictionary] = [["action" : action]]
let d = ["peerID" : peerID] as NSDictionary
dict.append(d)
return dict
}
func stayAsMaster(notification: Notification){
let peerId = NSDictionary(dictionary: notification.userInfo!)
print("stay as master", peerId.allValues)
if presentedViewController == nil {
let alertController = UIAlertController(title: String(describing: peerId.allValues) + " remains as Master", message: "", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alertController.addAction(dismiss)
self.present(alertController, animated: true, completion: nil)
} else{
let alertController = UIAlertController(title: String(describing: peerId.allValues) + " remains as Master", message: "", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alertController.addAction(dismiss)
self.dismiss(animated: false) { () -> Void in
self.present(alertController, animated: true, completion: nil)
}
}
}
Codes in MPCHandler:
func send(d: [NSDictionary]){
NSLog("%#", "Send data: \(d) to \(session.connectedPeers.count) peers")
if session.connectedPeers.count > 0{
do {
let data = NSKeyedArchiver.archivedData(withRootObject: d)
try self.session.send(data, toPeers: session.connectedPeers, with: .reliable)
} catch {
NSLog("%#", "Error for sending data: \(error)")
}
}
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
NSLog("%#", "didReceive: \(data)")
if var dict: [NSDictionary] = NSKeyedUnarchiver.unarchiveObject(with: data) as? [NSDictionary]{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let d = dict.removeFirst()
switch d.value(forKey: "action") as! String {
case "stayAsMaster":
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil, userInfo: [peerID:dict.first!])
default: break
}
}
}
When I tab "Stay as Master" in the Alert Controller of one device, the console of Xcode of another device prints the print("stay as master", peerId.allValues) immediately, but it dismisses the current alert controller after several seconds. Does anyone have any idea, please? Thanks for any help in advance.
I see a couple of potential issues in your code, try to change these:
whenever you post a notification wrap such code inside a DispatchQueue.main.async
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil, userInfo: [peerID:dict.first!])
}
please check here:
Posting NSNotification on the main thread
please add removeObserver (or you might crash after deinit):
deinit {
NotificationCenter.default.removeObserver(self)
}
I am using local notifications in my app, before presenting user with new notification screen I want to check the authorisation status first. I am using shouldPerformSegue(identifier:, sender:) -> Bool method, so that if the notifications are not authorised by user, the scene where the user configures and saves a new notification is not presented:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "AddReminder" {
// Check to see if notifications are authorised for this app by the user
let isAuthorised = NotificationsManager.checkAuthorization()
if isAuthorised {
print(isAuthorised)
return true
}
else {
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {
(action: UIAlertAction) in
}
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsURL = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsURL) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (success) in
print("Settings opened: \(success)")
})
}
}
alert.addAction(cancelAction)
alert.addAction(settingsAction)
present(alert, animated: true, completion: nil)
print(isAuthorised)
return false
}
}
// By default, transition
return true
}
Here is the method I use for authorisation check:
static func checkAuthorization() -> Bool {
// var isAuthorised: Bool
var isAuthorised = true
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
})
print("Reached .notDetermined stage")
case .authorized:
isAuthorised = true
case .denied:
isAuthorised = false
}
}
//print("Last statement reached before the check itself")
return isAuthorised
}
I figured that the last statement in the above function (return isAuthorized) returned before the body of UNUserNotificationCenter.current().getNotificationSettings{} is executed, therefore it always returns whatever isAuthorized is configured to, at the very beginning of the method.
Question:
Could you please suggest how I could check for authorisation using I better way, since my way does not even work.
This is only my first IOS app, so I am relatively new to IOS development; any help would be greatly appreciated.
If anyone having similar problem, then instead of using getNotificationsSettings(){} method, which will be computed after the enclosing method is returned; we could use different approach, i.e. getting currentUserNotificationSettings, which is notification settings of our app. Then check if current settings contain .aler, .sound and etc. If the answer is YES, then we could be sure that the applications has its notifications enabled.
Writing an app that requires the network. Run a method to check the WiFi is on and working, initially in the ViewController. So I got ...
wifiWorks = Reachability.isConnectedToNetwork()
This is defined like this ...
var wifiWorks: Bool = false {
didSet {
if wifiWorks {
print("Wifi On")
NotificationCenter.default.post(name: Notification.Name("WifiBon"), object: nil, userInfo: nil)
} else {
print("WiFi Off")
NotificationCenter.default.post(name: Notification.Name("noWiFi"), object: nil, userInfo: nil)
}
}
}
Now if noWifi gets called it shows an alert that basically goes to the settings like this...
func noWifi(notification:NSNotification) {
DispatchQueue.main.async {
//self.navigation.isHidden = true
let alert = UIAlertController(title: "Stop", message: "Your iPad isn't connected to the WiFi ...", preferredStyle: UIAlertControllerStyle.alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("Settings opened: \(success)") // Prints true
})
}
}
alert.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}
}
At which point, my app isn't running any longer, the user is sitting in settings, and has to switch back to my app having (hopefully) turned the WiFi on. Back in my app I got this in the app delegate.
func applicationWillEnterForeground(_ application: UIApplication) {
print("applicationWillEnterForeground")
wifiWorks = Reachability.isConnectedToNetwork()
}
Which brings me back full circle, so you cannot continue with the app, cause it needs the WiFi.
My question, does this dance make sense; or was/is there a cleaner way to do this?
You can use Reachability's own Notifications, as it's GitHub page suggests:
NotificationCenter.default.addObserver(self, selector: #selector(self.reachabilityChanged),name: ReachabilityChangedNotification,object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
Callback:
func reachabilityChanged(note: NSNotification) {
let reachability = note.object as! Reachability
if !reachability.isReachable {
//you can show alert here
}
}
Also be aware that user can turn on/off wifi without leaving your app, just by switching wifi toggle from Control Center. That's why you should not rely only on applicationWillEnterForeground method.