I'm using the following code to detect when a user has connectivity to the internet. If there's no connectivity, add a subview to show that. If the user regains connectivity, remove the view.
Adding the view works just fine, however removing it isn't working. Anybody know why? (Yes, I have double checked that removeNetworkIndicator is getting called)
- (void)testInternetConnection
{
__weak typeof(self) weakSelf = self;
internetReachable = [Reachability reachabilityWithHostname:#"google.com"];
// Internet is reachable
internetReachable.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf removeNetworkIndicator];
});
};
// Internet is not reachable
internetReachable.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf addNetworkIndicator];
});
};
[internetReachable startNotifier];
}
- (void)addNetworkIndicator {
NetworkIndicatorViewController *networkIndicatorView = [[NetworkIndicatorViewController alloc] initWithNibName:#"NetworkIndicatorViewController" bundle:nil]; //creat an instance of your custom view
networkIndicatorView.activityIndicator.hidden = YES;
networkIndicatorView.view.tag = 400;
[self.view addSubview:networkIndicatorView.view];
}
- (void)removeNetworkIndicator {
UIView *networkIndicator = (UIView *)[self.view viewWithTag:400];
NSLog(#"networkindicator: %#",networkIndicator);
networkIndicator.hidden = YES;
[networkIndicator removeFromSuperview];
}
Sidenote: the NSLog networkindicator is logging (null) but I don't understand why...
It's not immediately clear to me why your current code isn't working, but in the meantime, you can do this:
- (void)removeNetworkIndicator {
for(UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[NetworkIndicatorViewController class]]) {
[subview removeFromSuperview];
}
}
}
Also... make sure you're using a UIView subclass and not a UIViewController subclass.
create a property for the view in your view controller:
#property (nonatomic, strong) NetworkIndicatorViewController *networkIndicatorView
and then simply use this to show:
[self.view addSubview:self.networkIndicatorView.view];
and this to hide:
[self.networkIndicatorView.view removeFromSuperview];
Related
I am adding activity indicator on top of the view and wish to disable the selections in the background when the activity indicator is on. Also for some reason, my activity indicator is still spins for about 30-45 seconds(depending on the network speed) after the data is displayed on the table view. I have created a category for activity indicator.
Activity Indicator category code:
- (UIView *)overlayView {
return objc_getAssociatedObject(self, OverlayViewKey);
}
- (void)setOverlayView:(UIView *)overlayView {
objc_setAssociatedObject(self, OverlayViewKey, overlayView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)showActivityIndicatorForView:(UIView *)view {
self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
self.center = self.overlayView.center;
[view setUserInteractionEnabled:NO];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self.overlayView setUserInteractionEnabled:NO];
[self startAnimating];
[self.overlayView addSubview:self];
[view addSubview:self.overlayView];
[view bringSubviewToFront:self.overlayView];
self.hidesWhenStopped = YES;
self.hidden = NO;
}
- (void)hideActivityIndicatorForView:(UIView *)view {
[self stopAnimating];
[self.overlayView setUserInteractionEnabled:YES];
[self.overlayView removeFromSuperview];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
[view setUserInteractionEnabled:YES];
}
Usages in table view controller:
#interface MyTableViewController()
#property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
#end
#implementation MyTableViewController
- (id) initWithSomething:(NSString *)something {
self = [super init];
if (self) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.overlayView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self getDataServiceRequest];
[self.activityIndicator showActivityIndicatorForView:self.navigationController.view];
}
- (void)requestCompletionCallBack sender:(ServiceAPI *)sender {
// Do something here with the data
[self.activityIndicator hideActivityIndicatorForView:self.navigationController.view];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
#end
What am I doing wrong here? Why am I still able to select the data in the background when the activity indicator is on and even after disabling the user interaction.
Move your call to hideActivityIndicatorForView to inside the call to dispatch_async(dispatch_get_main_queue(). It's a UI call, and needs to be done on the main thread.
As for how to disable other actions on your view controller, you have a few options. One simple thing I've done is the put the activity indicator inside a view that's pinned to the whole screen, set to opaque=false, and with a color that's black with an alpha setting of 0.5. That way the content underneath is visible but the user can't click on it. You need to add an outlet to your "coveringView" and show-hide it instead of showing/hiding the activity indicator view.
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
fix it
[self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
for my iOS app I created a custom UIView that has the function of "Custom Notifications".
Everything works fine, but I have a problem, I wish the notification to appear only once. In a nutshell my Case Study is that if I click several times the button that brings up the notification latter overlaps indefinitely, instead I would (perhaps through the use of a Boolean) be able to bring up a one-time notification also if the button is pressed several times ...
Could you advise the best way to achieve this?
The custom view is presented in the external view controller in this way
-(IBAction)loginUser:(id)sender {
UTAlertView *alert = [[UTAlertView alloc] initWithTitle:#"Attenzione" message:#"Tutti i campi sono obbligatori" ];
alert.alertViewType = UTAlertViewTypeWarning;
[alert presentAlert];
[self.view addSubview:alert];
}
Custom UIView Class for Notification - Implementation file
#interface UTAlertView ()
#property (nonatomic, assign) BOOL alertActive;
#end
#implementation UTAlertView
#synthesize alertIcon;
#synthesize titleLabel, messageLabel;
#synthesize alertView;
#synthesize alertActive;
-(id)initWithTitle:(NSString*)title message:(NSString *)message {
[self initializeStringElementAlertView:title message:message];
return self;
}
-(void)initializeStringElementAlertView:(NSString *)title message:(NSString *)message {
alertView = [self initWithFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, kViewSize_W, kViewSize_H)];
[UTAlertElement alertTitle:titleLabel withString:title andAddSubview:self];
[UTAlertElement alertMessage:messageLabel withString:message andAddSubview:self];
alertIcon = [UTAlertElement iconAlertViewInView:self];
}
-(void)presentAlert {
if (!alertActive) {
alertActive =YES;
[UIView animateWithDuration:.3
animations:^{
[self bounce:1] ;
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height -60, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
} completion:^(BOOL finished) {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(setHideAnimation)];
tapGesture.cancelsTouchesInView = NO;
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGesture];
[self performSelector:#selector(setHideAnimation) withObject:self afterDelay:3];
}];
}
}
-(void)setHideAnimation {
[UIView animateWithDuration:.3
animations:^{
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
} completion:^(BOOL finished) {
alertActive =NO;
}];
}
If you simply just want your code to execute only, then have a look at dispatch_once here.
Basically you can just do:
static dispatch_once_t once;
dispatch_once(&once, ^ {
// Your code to be executed only once.
});
If you want to be able to open the notification multiple times, but not at the same time, you have multiple options, including:
The singleton pattern: I would not use this in this case as this would mean that you won't be able to have multiple instances of your notification type.
Blocks or delegate: Having a completion block or using the delegate pattern could also be an option. However, I think that this approach would leave it up to the consumer of the notification class to handle the logic of when it should be possible to show the notification. This might be what you want (if you have different logic on different views), but I don't think so.
These could be good approaches, but in your case I would simply go with some basic state handling, as you suggest yourself. So, I would probably do something like this:
Step 1: Add the BOOL property to your notification class
#property (nonatomic, assign) BOOL isVisible
Step 2: Add state handling to your show and hide methods
-(void)presentAlert {
//PRESENT THE ALERT
if (!self.isVisible) {
self.isVisible = YES;
[UIView animateWithDuration:.3
animations:^{
[self bounce:1];
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - 60, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
}
completion:^(BOOL finished) {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(setHideAnimation)];
tapGesture.cancelsTouchesInView = NO;
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGesture];
[self performSelector:#selector(setHideAnimation) withObject:self afterDelay:3];
}];
}
}
-(void)setHideAnimation {
//REMOVE THE ALERT
[UIView animateWithDuration:.3
animations:^{
self.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height +100, [UIScreen mainScreen].bounds.size.width, kViewSize_H);
}
completion:^(BOOL finished) {
self.isVisible = NO;
}];
}
I haven't really tested it, but it should get you started. Also, you might want to look into avoiding retain cycles.
Remember that when using this approach you should not create the notification every time the user triggers the notification. You probably want to do something like:
#property (nonatomic, strong) UTAlertView *loginAlert;
And then when you setup the view (e.g. in viewDidLoad or loadView) you can create the notification:
- (void)viewDidLoad {
[super viewDidLoad];
self.loginAlert = [[UTAlertView alloc] initWithTitle:#"Attenzione" message:#"Tutti i campi sono obbligatori" ];
self.loginAlert.alertViewType = UTAlertViewTypeWarning;
}
And of course, then in your method that is responsible of showing the alert would do something like:
-(IBAction)loginUser:(id)sender {
[self.loginAlert presentAlert];
[self.view addSubview:self.loginAlert];
}
I think a single instance (Singleton) alert will solve your problem. Use the singleton pattern to avoid multiple instance of alert view appearing.
You can use GCD dispatch_once.
For example in init method of your view create dispatch_once_t dispatch_flag and then:
dispatch_once( & dispatch_flag, ^{
// your presentation code goes here
} );
I'm trying to add a subview to view that displays an activity indicator and blocks touches while I'm fetching data from the server.
Here is the code that adds the loading view to the main view controller's screen:
+ (void)showLoadingView {
ViewController *vc = [AppDelegate mainViewController];
if (!vc._activityViewContainer) {
LoadingView *loadingView = [[LoadingView alloc] initWithFrame:vc.view.bounds];
[vc.view addSubview:loadingView];
vc._activityViewContainer = loadingView;
}
}
+ (void)stopLoadingView {
ViewController *vc = [AppDelegate mainViewController];
if (vc._activityViewContainer) {
[vc._activityViewContainer removeFromSuperview];
vc._activityViewContainer = nil;
}
}
Here is my loading view:
#implementation LoadingView
- (id)initWithFrame:(CGRect)frame {
if (self == [super initWithFrame:frame]) {
self.userInteractionEnabled = YES;
self.backgroundColor = [UIColor lightGrayColor];
self.alpha = 0.6;
self.layer.zPosition = 10000;
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
NSUInteger avSize = 100;
CGRect activityViewFrame = CGRectMake((frame.size.width - avSize) / 2, (frame.size.height - avSize) / 2, avSize, avSize);
activityView.frame = activityViewFrame;
[self addSubview:activityView];
[activityView startAnimating];
}
return self;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self pointInside:point withEvent:event] ? self : nil; }
#end
I'm trying to make it so that the loading view absorbs/blocks all touches to other sibling views. It's working fine when I'm testing it by presenting it without doing any fetching, it enters the loading view's hitTest method and blocks the touch. However, when I'm actually doing any kind of fetch from the server, it doesn't seem to work. The touch is delayed a few seconds and does not get intercepted by the LoadingView.
I'm using GTMHTTPFetcher's
- (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler
I'm not that sure what's going on and I tried looking for similar examples on SO, but couldn't find what I was looking for. Any help would be great, thanks!
I was doing my fetches on the main thread which was blocking the UI; I thought GTM did everything on a different thread, but it doesn't. Thanks to those who posted things which helped me in the right direction
I am having some trouble getting my UIActivityIndicatorView to start animating. Here is my setup:
In my viewDidLoad in my view controller I have:
- (void)viewDidLoad{
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
The selector getSchoolList communicates with a server to retrieve a list of schools in a given state. Then, the selector updateUI is called to populate my UIPickerView with the list. In my updateUI selector I have:
-(void)updateUI {
_schools = [_server returnData];
if(!(_schools == nil)) {
NSLog(#"update the UI");
}
else
NSLog(#"Error:Show re-load button");
[_activityIndicator stopAnimating];
}
When I run this code, my UIActivityIndicatorView shows up, but does not animate. Can someone explain the proper way to animate my UIActivityIndicatorView? Any help is much appreciated.
You need to add the UIActivityIndicatorView to your view in viewDidLoad like this:
- (void)viewDidLoad {
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self addSubview:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
EDIT
If _activityIndicator is a properly connected IBOutlet to a UIActivityIndicatorView, you should only need to check the 'animating' box. There would be no need to alloc/init another UIActivityIndicatorView.
Breakpoint the update function, but I don't see where you add that as a view to the hierarchy. I think you're looking at a different indicator view in the program.
I have a subview with live view from the back camera. I am animating it when toggling the camera to front. Because I could't sync the animation and the toggle, I covered it with another view with plain black background. Because toggle is slower, I want to remove the black subview after toggle was performed. But when I do it, it won't disappear. Debugger shows all NSLogs I put in, and the debugger even stops when I put breakpoints anywhere, so the code is reachable. But the UIView is simply not modified and still holds the black subview.
Interesting fact: when I turn display off with the top button and then back on again, the black view is gone!
CODE:
- (IBAction)cameraChanged:(id)sender {
NSLog(#"Camera changed");
UIView *animView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.videoPreviewView.frame.size.width, self.videoPreviewView.frame.size.height)];
animView.tag = 42;
[animView setBackgroundColor:[UIColor blackColor]];
[UIView transitionWithView:self.videoPreviewView duration:1.0
options:self.animate ? UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[self.videoPreviewView addSubview:animView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
[self.captureManager toggleCamera];
NSLog(#"Done toggling");
[self done];
});
}
completion:^(BOOL finished){
if (finished) {
//
}
}];
self.animate = !self.animate;
}
- (void)done {
for (UIView *subview in [self.videoPreviewView subviews]) {
NSLog(#"In for");
if (subview.tag == 42) {
NSLog(#"removing");
[subview removeFromSuperview];
}
}
}
Anybody knows why is this happening and how to solve it?
UIView Update should be run on Main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
//BlahBlah
dispatch_sync(dispatch_get_main_queue(), ^{
//update your UIView
});
});