iOS waiting screen - ios

When my app is launched I'd like to display a kind of "waiting view" so I can make the first http calls before accessing to the app.
I saw a nice one, all grey and a little transparent with only an activity indicator inside.
I do not really know how to build this kind of view in a good manner, is it a simple UIView ?

Fist short answer is: yes, the simplest way is to use a UIActivityIndicatorView.
If you don't like all this "big" static libraries just use this:
#import <QuartzCore/QuartzCore.h>
.....
- (UIActivityIndicatorView *)showActivityIndicatorOnView:(UIView*)aView
{
CGSize viewSize = aView.bounds.size;
// create new dialog box view and components
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// other size? change it
activityIndicatorView.bounds = CGRectMake(0, 0, 65, 65);
activityIndicatorView.hidesWhenStopped = YES;
activityIndicatorView.alpha = 0.7f;
activityIndicatorView.backgroundColor = [UIColor blackColor];
activityIndicatorView.layer.cornerRadius = 10.0f;
// display it in the center of your view
activityIndicatorView.center = CGPointMake(viewSize.width / 2.0, viewSize.height / 2.0);
[aView addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
return activityIndicatorView;
}
This needs access to a QuartzCore class so add it in to you frameworks. After you task is done send it a stopAnimating and it will disappear. If you don't need it anymore remove it from your view to save memory (btw. this is ARC code).
I call it like this
self.activityIndicatorView = [self showActivityIndicatorOnView:self.parentViewController.view];
In this case I have UINavigationViewController as a main view controller in my app. This is importent because the activity indicator should show up in the middle of the screen and not e.g. in the middle of a table view.
The simplest implementation is this:
- (UIActivityIndicatorView *)showSimpleActivityIndicatorOnView:(UIView*)aView
{
CGSize viewSize = aView.bounds.size;
// create new dialog box view and components
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicatorView.center = CGPointMake(viewSize.width / 2.0, viewSize.height / 2.0);
[aView addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
return activityIndicatorView;
}

MBProgressHUD might be what you're looking for:
https://github.com/matej/MBProgressHUD

There are many many way to do this, but yes, it can be a simple UIView.
Maybe you should give a look at three20 library which offers built-in loaders TTActivityLabel like these:

Related

Objective C: Asynchronous loading of some data -> I want to update afterwards labels

I am a bit confused. I have written a class, that calculates some stuff and makes an internet query. Afer this query some NSString properties of this class are updated with the resulting values.
In my view controller, I create an instance of this class. I want to show in the labels waiting for text "Loading..." until the data has arrived. As soon as the data is ready, I want to replace the text. But how do I do that? And depending on if one property, I also want to redraw one view of this view controller. Furthermore I don't want to block my UI.
This so far hasn't worked...
self.firstLabel.text = #"Loading...";
self.secondLabel.text = #"Loading...";
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator setColor:[UIColor colorWithHexString:#"3375cb"]];
indicator.center = self.view.center;
[self.view addSubview:indicator];
[indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.myInstance fillLabelsWithLiveData];
dispatch_async(dispatch_get_main_queue(), ^{
[indicator stopAnimating];
self.firstLabel.text = myInstance.someText;
self.secondLabel.text = myInstance.someMoreText;
if(myInstance.myBool){
//redraw
}
});
});
You say that your code isn't working, but you don't say what the actual behaviour is, so I would like to write about how I would debug the code in order to see where the problem is.
First, I would put a few log statements into the code, in order to see what it actually does.
NSLog(#"MyViewController: Starting to load...");
self.firstLabel.text = #"Loading...";
self.secondLabel.text = #"Loading...";
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator setColor:[UIColor colorWithHexString:#"3375cb"]];
indicator.center = self.view.center;
[self.view addSubview:indicator];
[indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"MyViewController: Starting to load on background queue...");
[self.myInstance fillLabelsWithLiveData];
NSLog(#"MyViewController: Done loading on background queue...");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"MyViewController: Updating main queue UI");
[indicator stopAnimating];
self.firstLabel.text = myInstance.someText;
self.secondLabel.text = myInstance.someMoreText;
if(myInstance.myBool){
NSLog(#"MyViewController: Will redraw...");
//redraw
} else {
NSLog(#"MyViewController: Will not redraw (myInstance.myBool is NO)");
}
});
});
When you do this you will see where the problem lies exactly. (Note that in tutorials all over the internet, the log messages are often ultra short. Like NSLog(#"loading...");. The problem is that if you have many logs in your app and every class logs like this, the log messages become useless. What's worse, you may see that "loading..." is printed on the console and assume that your code is called, when in fact some other place in the program prints "loading...". So I always add some context to NSLog calls. (In fact, I never use NSLog, I use custom logging libraries that also print file names and line numbers.))
Just a few comments on your code.
centering the indicator
indicator.center = self.view.center; // (1a)
This does not center the indicator in self.view
indicator.center = centerOf(self.view.bounds); // (1b)
does. You have to define centerOf:
CGPoint centerOf(CGRect rect) {
CGFloat x = CGRectGetMidX(rect);
CGFloat y = CGRectGetMidY(rect);
return CGPointMake(x, y);
}
(1a) works sufficiently well though if self.view.frame.origin is sufficiently near to (0, 0).
check if your code hangs
If you add the logs and look into the console, there are a few things that can go wrong:
The new log lines do not show up at all => in this case, you should check why your code isn't called at all
The new log lines are printed up to some point, e.g. "Done loading on background queue" is never printed to the console. => In this case, the code that is in-between hangs, obviously.
The logs look good, but the indicator doesn't show up. 2 options:
everything is so fast that you just don't see the indicator.
the loading on the background queue takes some time, but the indicator is not visible => is the superview of the indicator visible? Use the "inspect view hierarchy" feature of Xcode to check what is going on. Where is your view? Add some code like self.view.backgroundColor = [UIColor orangeColor]; to see if it makes a difference.
The logs look good, but the labels did not update.
if this is the case, I cannot help you anymore. You didn't post the real code. You posted a simplified version of it, and the bug hides behind that simplification.

Why would an activity indicator show up properly on iPhone but not on iPad?

I have my app setup to show this view when it is loading data:
self.loadingView = [UIView new];
self.loadingView.frame = CGRectMake(0, 0, self.tableView.frame.size.width, self.view.frame.size.height);
self.loadingView.backgroundColor = [UIColor groupTableViewBackgroundColor];
[self.view addSubview:self.loadingView];
[self.view bringSubviewToFront:self.loadingView];
self.activityIndicator = [UIActivityIndicatorView new];
self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview:self.activityIndicator];
[self.view bringSubviewToFront:self.activityIndicator];
[self.activityIndicator startAnimating];
Then, I remove it from its superview. It works on iPhone. It works on iPad sometimes too, except for when I'm using the same code in a UISplitViewController. I've tried various adjustments to centering the views, etc., but can't figure it out. What's going wrong?
I've add trouble with activity indicators in the past as well. Make sure you are not calling the startAnimating or stopAnimating while any animations are taking place. I recommend calling the startAnimating selector in viewDidLayoutSubviews.
The line where you set the center of the indicator looks like the source of the problem. self.view.frame.size is probably equal to screen size at this point and so when you show that view controller over the whole screen it's ok, but inside a split view controller it's not because indicator is off-bounds. You can check that from Xcode's Debug -> View Debugging -> Capture View Hierarchy (while the app is running).
Try setting activity indicator's center using autolayout and it should work.

Is UICollectionView.backgroundView broken

I'm writing something relatively simple, or so I thought.
Firstly, the code, for which I'm trying to place an image on the background of the UICollectionView if there are no results returned from my server. The image is 200 by 200:
UIView *myView = [[UIView alloc] initWithFrame:self.view.bounds];
CGRect myViewSpace = self.view.bounds;
CGFloat myX = (myViewSpace.size.width /2.0) - 100;
CGFloat myY = (myViewSpace.size.height /2.0) - 100;
UIImageView *imView = [[UIImageView alloc] initWithFrame:CGRectMake(myX, myY, 200, 200)];
imView.image = [UIImage imageNamed:#"imNotHome"];
[myView addSubview:imView];
myCollectionView.backgroundView = myView;
Once there are results, I want to be able to remove it.
I thought it'd be as simple as placing the following, before I reloaded the UICollectionView:
[myCollectionView.backgroundView removeFromSuperview];
However, it appears to be doing nothing.
Am I missing something?
Thanks in advance!
It should be done this way instead:
myCollectionView.backgroundView = nil;
Explanation: You should unset the UICollectionView's background in the same way as you set it. You didn't set it by manipulating the view hierarchy, but by setting the background property. You did call addSubview in the previous line, but that was to add a subview to your background view, not to add the background view itself.
Edit:
There is a very good article about this UICollectionView bug here:
http://blog.spacemanlabs.com/2013/11/uicollectionviews-backgroundview-property-is-horribly-broken/
The solution the author gives is to reset the background to an empty opaque view:
UIView *blankView = [UIView new];
blankView.backgroundColor = [UIColor whiteColor];
[myCollectionView.backgroundView removeFromSuperview];
myCollectionView.backgroundView = blankView;
Also, the author recommends not using the backgroundView property at all but doing it yourself:
Frankly, I think the best solution is to just ignore the backgroundView property all together. Instead, make the collection view’s background clear, and implement your own backgroundView; just throw a view behind the collection view.

iOS 7 Translucent Modal View Controller

The App Store app on iOS 7 uses a frosted glass-type effect where it is possible to see the view behind. Is this using an API built into iOS 7 or is it custom code. I was hoping it would be the former but I can't see any obvious references in the documentation. Obvious things like (like setting the alpha property on the modal view) don't seem to have any effect.
To see an example, open the App Store app and press the button at the top-right.
With the release of iOS 8.0, there is no need for getting an image and blurring it anymore. As Andrew Plummer pointed out, you can use UIVisualEffectView with UIBlurEffect.
UIViewController * contributeViewController = [[UIViewController alloc] init];
UIBlurEffect * blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *beView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
beView.frame = self.view.bounds;
contributeViewController.view.frame = self.view.bounds;
contributeViewController.view.backgroundColor = [UIColor clearColor];
[contributeViewController.view insertSubview:beView atIndex:0];
contributeViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:contributeViewController animated:YES completion:nil];
Solution that works before iOS 8
I would like to extend on rckoenes' answer:
As emphasised, you can create this effect by:
Convert the underlying UIView to an UIImage
Blur the UIImage
Set the UIImage as background of your view.
Sounds like a lot of work, but is actually done pretty straight-forward:
1. Create a category of UIView and add the following method:
-(UIImage *)convertViewToImage
{
UIGraphicsBeginImageContext(self.bounds.size);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
2. Make an image of the current view and blur it by using Apple's Image Effect category (download)
UIImage* imageOfUnderlyingView = [self.view convertViewToImage];
imageOfUnderlyingView = [imageOfUnderlyingView applyBlurWithRadius:20
tintColor:[UIColor colorWithWhite:1.0 alpha:0.2]
saturationDeltaFactor:1.3
maskImage:nil];
3. Set it as background of your overlay.
-(void)viewDidLoad
{
self.view.backgroundColor = [UIColor clearColor];
UIImageView* backView = [[UIImageView alloc] initWithFrame:self.view.frame];
backView.image = imageOfUnderlyingView;
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Just reimplemented Sebastian Hojas' solution in Swift:
1. Create a UIView extension and add the following method:
extension UIView {
func convertViewToImage() -> UIImage{
UIGraphicsBeginImageContext(self.bounds.size);
self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return image;
}
}
2. Make an image of the current view and blur it by using Apple's Image Effect (I found a reimplementation of this in Swift here: SwiftUIImageEffects
var imageOfUnderlyingView = self.view.convertViewToImage()
imageOfUnderlyingView = imageOfUnderlyingView.applyBlurWithRadius(2, tintColor: UIColor(white: 0.0, alpha: 0.5), saturationDeltaFactor: 1.0, maskImage: nil)!
3. Set it as background of your overlay.
let backView = UIImageView(frame: self.view.frame)
backView.image = imageOfUnderlyingView
backView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
view.addSubview(backView)
I think this is the easiest solution for a modal view controller that overlays everything with a nice blur (iOS8)
UIViewController * contributeViewController = [[UIViewController alloc] init];
UIBlurEffect * blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *beView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
beView.frame = self.view.bounds;
contributeViewController.view.frame = self.view.bounds;
contributeViewController.view.backgroundColor = [UIColor clearColor];
[contributeViewController.view insertSubview:beView atIndex:0];
contributeViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:contributeViewController animated:YES completion:nil];
There is no API available in the iOS 7 SDK which will allow you to "frost" the underlaying view controller.
What I have done is render the underlaying view to an image, which I then frosted and set that as background the the view that is being presented.
Apple provides a good example for this: https://developer.apple.com/downloads/index.action?name=WWDC%202013
The project you want is called, iOS_RunningWithASnap
A little simplier way to achieve this (based on Andrew Plummer's answer) with Interface Builder (also it removes side effect that appears in Andrews answer):
In IB add Visual Effect View to your View Controller under your other views;
Make top, bottom, left, right constraints from Visual Effect View to top (parent) View, set all of them to 0;
Set Blur Style;
Add the code where you present your new fancy View Controller:
UIViewController *fancyViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"yourStoryboardIDFOrViewController"];
fancyViewController.view.backgroundColor = [UIColor clearColor];
fancyViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:fancyViewController
animated:YES
completion:nil];
Actually, the second and third lines are VERY important - otherwise controller will blink and then turn black.
Since iOS 8, this works:
let vc = UIViewController()
vc.view = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
vc.modalPresentationStyle = .OverFullScreen
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .OverFullScreen
presentViewController(nc, animated: true, completion: nil)
The key is the .OverFullScreen flag and ensuring the viewControllers have a blur UIVisualEffectView that is the first visible view.
As #rckoenes said, there is no Apple provided framework to get that effect. But some people out there already built good alternatives, like this one for example:
https://github.com/JagCesar/iOS-blur/
A couple of alternative approaches that also work on iOS 5 and 6:
FXBlurView: https://github.com/nicklockwood/FXBlurView
iOS RealtimeBlur: https://github.com/alexdrone/ios-realtimeblur
Fast & easy solution
with XIB support you can use for the old school boys
https://github.com/cezarywojcik/CWPopup
Instead of presenting the viewController as a modalView, you could add it as a child viewController and create a custom animation. You would then only need to change the default view of the viewController to a UIToolBar in viewDidLoad.
This will allow you to mimic the appstore's blurred modal view as closely as possible.
I have uploaded my take of the blurred view controller to [GitHub][1]. It also comes with a segue subclass so you can use it in your storyboards.
Repository: https://github.com/datinc/DATBlurSegue
Apple released the UIImageEffect category for those effects. Those category should be added manually to the project, and it support iOS7.
You can use UIToolbar as background.
By default UIToolbar have 50px height.
Add auto layout constraints on UIToolbar.
Then select height constraint and modify it.
Hierarchy will look like this:
UIView -> clear colour for background.
- UIToolbar
- Other contents.

How to update UILabel programmatically in iOS

I am facing a problem with updating my labels.. it doesn't remove the old values so new values go on top of old ones.. any help with this will be appreciated..
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateLabels)
userInfo:nil
repeats:YES];
-(void) updateLabels{
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
If you set the text of your label you do not need to call setNeedsDisplay or clearContext etc.
In your code, I do not know what are your variables i and x?
The main problem is that you are creating and adding new labels on your view. When you call updateLabels method, may cause a Memory leak. Simply you have n times labels overlapped.
You need to remove the labels before you create and add new labels or you can reuse which you already have. To reuse your labels you need to save them to an array and update texts.
If you want to create new labels then you can do like this unless you have other labels in your view
-(void) updateLabels{
// remove all labels in your view
for (UIView *labelView in self.view.subviews) {
if ([labelView isKindOfClass:[UILabel class]]) {
[labelView removeFromSuperview];
}
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
When you create new labels like this you need to add them to your view as subview
[self.view addSubview: valLabel];
if you have other labels in your view then you can save them in an array and remove just them
Your updateLabels method is actually creating new UILabel controls each time so they will simply appear "on top of" older ones. I'm guessing this is not what you want, although it's not perfectly clear so apologies if I've misunderstood what you're trying to do.
If I'm correct about that, create your UILabel controls just once maybe in your viewDidLoad or similar. Then just set their .text properties when your timer fires.
You need to call setNeedsDisplay so that the app knows it has changed and redraw it.
- (void)setNeedsDisplay
Set clearsContextBeforeDrawing property of your label to YES
you can set this from nib as well as code.
label.clearsContextBeforeDrawing = YES;

Resources