I am trying to setup Reachability using the new 2.0 AFNetworking.
In my AppDelegate I initialise the sharedManager.
// Instantiate Shared Manager
[AFNetworkReachabilityManager sharedManager];
Then in the relevant VC method I check to see if isReachable:
// Double check with logging
if ([[AFNetworkReachabilityManager sharedManager] isReachable]) {
NSLog(#"IS REACHABILE");
} else {
NSLog(#"NOT REACHABLE");
}
At present this is not working as expected in the simulator, but I imagine this would need to be tested on device and not simulator.
Question
What I would like to do is monitor the connectivity within the VC. So I run the following in the viewDidLoad:
// Start monitoring the internet connection
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
How would I then register for the changes? What is/would be called once the network connection changes I cannot see this from the documentation.
As you can read in the AFNetworking read me page
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
}];
Here's also a link to the official documentation.
I have a singleton AFHTTPRequestOperationManager class. In the singleton has a method:
+(void)connectedCompletionBlock:(void(^)(BOOL connected))block {
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
BOOL con = NO;
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
con = YES;
}
if (block) {
[[AFNetworkReachabilityManager sharedManager] stopMonitoring];
block(con);
}
}];
}
Before make a request you call this method that return a block indicating if internet is reachable:
[TLPRequestManager connectedCompletionBlock:^(BOOL connected) {
if (connected) {
// Make a request
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Notice" message:#"Internet is not available." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
}];
I was just going through your question and all the answers. After that I decided to do all these things once. So, in my existing project I just included the AFNetworking through cocoa-pods and here is the solution which is woking for me completely.
Solution -- First of all AFNetworkReachabilityManager is a singleton class. You don't need to do AppDelegate initialisation for sharedManager.
//[AFNetworkReachabilityManager sharedManager];
#import <AFNetworkReachabilityManager.h>
- (void)viewDidLoad {
//Starting the network monitoring process
[[AFNetworkReachabilityManager sharedManager]startMonitoring];
//Checking the Internet connection...
[[AFNetworkReachabilityManager sharedManager]setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
UIAlertView *alertNetFound = [[UIAlertView alloc]initWithTitle:#"Network Found" message:#"Please Wait Until It is loading" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertNetFound show];
}else{
UIAlertView *alertNetNotFound = [[UIAlertView alloc]initWithTitle:#"No Internet" message:#"Please Check Your Internet Connection Honey" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertNetNotFound show];
}
}];
So, in this case every time the device connects to a network, it will do the startMonitoring process first and after that it will hit the status block every time and will display alert according to the status.
You can do anything according to your choice by replacing the alerts on the status block. I used this to load an webpage automatically from local storage but I removed that code for simplicity.
Its even working with my simulator and Mac mini..
Thanks
Hope this helped.
I use this in the app delegate ->
func reachablityCode() {
AFNetworkReachabilityManager.sharedManager()
AFNetworkReachabilityManager.sharedManager().startMonitoring()
AFNetworkReachabilityManager.sharedManager().setReachabilityStatusChangeBlock({(status) in
let defaults = NSUserDefaults.standardUserDefaults()
if status == .NotReachable {
defaults.setBool(false, forKey:REACHABLE_KEY)
}
else {
defaults.setBool(false, forKey: REACHABLE_KEY)
}
defaults.synchronize()
})
}
And then this in the base file ->
func isReachable() -> Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(REACHABLE_KEY)
}
Related
I have a weird issue with Zebra printer. On the big picture, I have codes to fetch items to print one by one from the queue. So if when the printing initiated, and there's 3 items on the queue, the code will loop and fetch the first data in the queue, send it to the printer, and delete the first data in the queue. Sort of like dequeueing.
The problem is, if it's the code that looping and sending the data directly to the printer, the printer will only print the first item. The next item is gone, even though NSLog shows that the printer connection opened, data sent, printing successful, and printer connection closed, for every single item.
But if each time the code print one label, and then the app shows message box like "press OK to print next label", and then the user tap the OK button, it can print the second and the rest of the label after every tap of the button.
I have then tried to emulate this. I've tried to use timer to "push the button programmatically" [btnPrint sendActionsForControlEvents:UIControlEventTouchUpInside], I also use timer to call the function directly, or giving delay to the thread, but none works. It has to be initiated from a button which tapped from human touch. I don't know why.
Here's the code:
// main function to print
-(void) printLabel {
if ([dataToPrint count] > 0) {
[self printWithData:[dataToPrint objectAtIndex:0]];
}
}
-(void)printWithData:(NSString*) data;
{
NSString *zplString = data;
// do something with zplString
NSLog(#"Sending data to printer");
printHandler = [[PrintingHandler alloc] init];
[printHandler setDelegate:self];
[printHandler initialize];
[printHandler printToSerial:bluetoothSerialNumber withData:zplString];
}
// delegate to call if the print is success
-(void) printIsSuccess
{
[dataToPrint removeObjectAtIndex:0];
// in here, I just use sleep code instead of button tap emulation to avoid unnecessarily too long code
[NSThread sleepForTimeInterval:2.0f];
[self printLabel];
}
// this is method inside PrintingHandler class that get called by PrintingHandler (printToSerial:)
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
[NSThread sleepForTimeInterval:1.0f];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}
Sorry, I've found the solution. The problem is it's too fast between opening the connection to the printer and sending data to the printer. I was putting some delay, but I put the delay at wrong position.
So the step to print currently is:
Open bluetooth printer connection
Send data
Delay
Close printer connection
When the correct one should be:
Open bluetooth printer connection
Delay
Send data
Close printer connection
Here I put the answer so this can help other people with same problem.
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
[NSThread sleepForTimeInterval:1.0f]; // this is the important one
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}
I'm using Cloud Kit in my app which makes use of the camera and only allows the user to submit photos if they're logged into iCloud. So when the user clicks the camera button, I call a CloudKit method to get the user's icloud status, which returns a CKAccountStatus value (0-3). I implemented this initially in the view controller and it worked perfectly. Then I did some refactoring and created a CKManager class to house all CK related methods. So now when the camera is clicked instead of calling the CK method off the container directly in the VC, I'm calling it via a method from my CKManager property (which is lazy instantiated). It should only return values 0-3, but it keeps returning 448 for some reason. However, in the CKManager logging, I can see it logging correctly that I'm logged into iCloud. So there's an issue of it translating from there back to the VC. I have feeling this is a threading/callback issue, which I'm not that well versed in.
Can someone take a look at the code and see if there's something obvious I'm doing wrong? Thanks in advance!
- (IBAction)cameraBarButtonPressed:(UIBarButtonItem *)sender {
NSLog(#"Entered cameraBarButtonPressed");
//CKContainer *container = [CKContainer defaultContainer];
dispatch_queue_t fetchQ = dispatch_queue_create("check user status", NULL);
__block CKAccountStatus userAccountStatus;
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
userAccountStatus = [self.ckManager getUsersCKStatus];
NSLog(#"cameraBarButtonPressed userAccountStatus: %ld", userAccountStatus);
if (userAccountStatus == CKAccountStatusAvailable) {
//NSLog(#"User is logged into CK - user can upload pics!");
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.delegate = self; // set the deleage for the ImagePickerController
// check to see if the camera is available as source type, else check for photo album
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
} else if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[cameraUI setAllowsEditing:YES]; // let the user edit the photo
// set the camera presentation style
//cameraUI.modalPresentationStyle = UIModalPresentationFullScreen;
cameraUI.modalPresentationStyle = UIModalPresentationCurrentContext;
dispatch_async(dispatch_get_main_queue(), ^{ // show the camera on main thread to avoid latency
[self presentViewController:cameraUI animated:YES completion:nil]; // show the camera with animation
});
} else if (userAccountStatus == CKAccountStatusNoAccount) {
//NSLog(#"User is not logged into CK - Camera not available!");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Not Available" message:#"You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else if (userAccountStatus == CKAccountStatusRestricted) {
NSLog(#"User CK account is RESTRICTED !");
} else if (userAccountStatus == CKAccountStatusCouldNotDetermine) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else { // did not get back one of the above values so show the Could Not Determine message
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
}
});
}
The above code is the code that does not work. Here is the code that does work. Just copying the beginning code as the rest is redundant from that point on...
CKContainer *container = [CKContainer defaultContainer];
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
[container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {...
Lastly, here is the code that gets called from CKManager for the code that does not work...
- (CKAccountStatus)getUsersCKStatus {
NSLog(#"Entered getUsersCKStatus...");
__block CKAccountStatus userAccountStatus;
[self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {
NSLog(#"Error: Error encountered while getting user CloudKit status: %#", error.localizedDescription);
} else {
if (accountStatus == CKAccountStatusAvailable) {
NSLog(#"Info: User is logged into CK - camera is available!");
userAccountStatus = CKAccountStatusAvailable;
} else if (accountStatus == CKAccountStatusNoAccount) {
NSLog(#"Info: User is not logged into CK - Camera not available!");
userAccountStatus = CKAccountStatusNoAccount;
} else if (accountStatus == CKAccountStatusRestricted) {
NSLog(#"Info: User CK account is RESTRICTED - what does that mean!?");
userAccountStatus = CKAccountStatusRestricted;
} else if (accountStatus == CKAccountStatusCouldNotDetermine) {
NSLog(#"Error: Could not determine user CK Account Status: %#", error.localizedDescription);
userAccountStatus = CKAccountStatusCouldNotDetermine;
}
}
}];
NSLog(#"CKAccountStatus: %ld", userAccountStatus);
return userAccountStatus;
}
In the getUsersCKStatus you are calling the accountStatusWithCompletionHandler. That is an asynchronous method. In your case it will return the userAccountStatus before it is set by its callback method.
You could solve this by making that method synchronous by implementing a semaphore. A better way would be passing on a callback block to that method and not returning a value.
I am using this method to ask a nearby device to join the session:
When I do it I also start spinning an indicator
[browser invitePeer:key
toSession:session
withContext:nil
timeout:30];
Is there a method called in the moment of timeout? what if the other device goes out of range?
EDIT:
I notice that this state is never called:
if (state == MCSessionStateConnecting) {
NSLog(#"CONNECTING %#", peerID);
}
in case of timeouts on the browser side, you need to watch for the MCSessionStateNotConnected state. i do something like this:
- (void)session:(MCSession *)session
peer:(MCPeerID *)peerID
didChangeState:(MCSessionState)state
{
if (state == MCSessionStateNotConnected)
{
if (self.isWaitingForInvitation)
{
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"ERROR_TITLE", nil)
message:NSLocalizedString(#"ERROR_TEXT", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"NO", #"Não")
otherButtonTitles:NSLocalizedString(#"YES", #"Sim"),
nil];
dispatch_sync(dispatch_get_main_queue(), ^{
[alertView show];
});
self.isWaitingForInvitation = NO;
}
}
use the dispatch_sync to make the alert popup right away.
Using a timer with a timer interval matching timeout parameter could be better idea.
Here is a simple scenario, I am using FBLoginView which show a login button and on click application go for fast switching but i want to check a bool value of terms and conditions before app go for fast switch. There is not way i am able to find to achieve this scenario.
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// Upon login, transition to the main UI by pushing it onto the navigation stack.
LGAppDelegate *appDelegate = (LGAppDelegate *)[UIApplication sharedApplication].delegate;
[self.navigationController pushViewController:((UIViewController *)appDelegate.mainViewController) animated:YES];
}
- (void)acceptTermsAndConditions {
if (!_checkboxButton.checked) {
NSString *alertMessage, *alertTitle;
alertTitle = #"Terms to Use";
alertMessage = #"Please accept the terms & condition to use this application.";
UIAlertView* av = [[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
[av release];
}
}
- (void)loginView:(FBLoginView *)loginView
handleError:(NSError *)error{
NSString *alertMessage, *alertTitle;
// Facebook SDK * error handling *
// Error handling is an important part of providing a good user experience.
// Since this sample uses the FBLoginView, this delegate will respond to
// login failures, or other failures that have closed the session (such
// as a token becoming invalid). Please see the [- postOpenGraphAction:]
// and [- requestPermissionAndPost] on `SCViewController` for further
// error handling on other operations.
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it. This conveniently
// handles cases like password change or iOS6 app slider state.
alertTitle = #"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
} else if (error.fberrorCategory == FBErrorCategoryAuthenticationReopenSession) {
// It is important to handle session closures as mentioned. You can inspect
// the error for more context but this sample generically notifies the user.
alertTitle = #"Session Error";
alertMessage = #"Your current session is no longer valid. Please log in again.";
} else if (error.fberrorCategory == FBErrorCategoryUserCancelled) {
// The user has cancelled a login. You can inspect the error
// for more context. For this sample, we will simply ignore it.
NSLog(#"user cancelled login");
} else {
// For simplicity, this sample treats other errors blindly, but you should
// refer to https://developers.facebook.com/docs/technical-guides/iossdk/errors/ for more information.
alertTitle = #"Unknown Error";
alertMessage = #"Error. Please try again later.";
NSLog(#"Unexpected error:%#", error);
}
if (alertMessage) {
[[[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
}
}
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
// Facebook SDK * login flow *
// It is important to always handle session closure because it can happen
// externally; for example, if the current session's access token becomes
// invalid. For this sample, we simply pop back to the landing page.
LGAppDelegate *appDelegate = (LGAppDelegate *)[UIApplication sharedApplication].delegate;
if (appDelegate.isNavigating) {
// The delay is for the edge case where a session is immediately closed after
// logging in and our navigation controller is still animating a push.
[self performSelector:#selector(logOut) withObject:nil afterDelay:.5];
} else {
[self logOut];
}
}
- (void)logOut {
[self.navigationController popToRootViewControllerAnimated:YES];
}
I want to execute some code only, and only if I am connected to the internet:
//Reachability
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
Reachability * reach = [Reachability reachabilityWithHostname:#"www.dropbox.com"];
reach.reachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Block Says Reachable");
connect = #"yes";
});
};
reach.unreachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
connect = #"no";
});
};
[reach startNotifier];
//Reachability
if (connect == #"no") {
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"" message:#"There is no internet connection. Please connect to the internet. If you are already connected, there might be a problem with our server. Try again in a moment." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles: nil];
[alert1 show];
} else if (titleSet == NULL){
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"" message:#"Please select a group or create a new one" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles: nil];
[alert1 show];
}else if (NavBar.topItem.title.length < 1){
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"" message:#"Please select a group or create a new one" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles: nil];
[alert1 show];
} else if (newmessagename.text.length < 4){
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"" message:#"Please give a name to your event that is at least 4 characters long" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles: nil];
[alert1 show];
}
It seems that the code is not executed in order. I think it is taking more time to check the Internet connection than it takes to execute the code. How can I fix this?
Please do not tell me to place the code directly in the parenthesis where connect = #"no"; is located.
The blocks aren't executed sequentially, they are executed asynchronously.
This means that you can't tell when the code inside the blocks is going to be called. The code using the block may finish and execute before the rest of your method (however this is unlikely, especially with Internet connections).
You should place your ifs in a method that is called at a valid time. This time is probably when you receive a response from your blocks, or, if my memory serves true, [reach startNotifier]; can notify you when there is a change in reachability status, this appears to be your reachabilityChanged: method:
-(void) reachabilityChanged:(id) parameter
{
//Query reachability and notify / cache as required.
}
Of course its not executed in order, the whole point of those methods is to stop the ui to freeze while you get your reachability response. Basically you set up the reachability responses and immediately ask for the result when nothing has been checked yet. What you have to really is to move it inside the brackets.
Something else that you can do is make a function with those results, and call this function in both of the blocks.
If you want to have this on the load of the viewcontroller or before you display anything else, then you either have to check for reachability before you show this controller, or add a "loading" screen.
EDIT: Something else that i dont understand is, those reachability methods seem to fire a block when they get the results, but you are registering for a notification as well. And i dont see you posting a notification for this. You are using 2 asynchronous approches here (blocks and notifications)