Reasons for Rejection: The activity indicator spins indefinetely and the user can't access the content
The same situation,Second time be rejected because of used MBProgressHUD.
Who can tell me Uploaded to appstore app would be any different? I done various tests, such a problem did not appear in the local.
-----------------------------in my controller-----------------------------------
- (void)downloadList
{
if (isLoading) {
return;
}
isLoading = YES;
//do something......
//show the progressbar based on MBProgressHUD
[[MyDelegate getAppDelegate] showProgressBarForTarget:self whileExecuting:#selector(showProgressBarForLoading)];
}
}
- (void)showProgressBarForLoading
{
while (isLoading) {
//i++;
continue;
}
}
- (void)downloadListDidReceive:(XGooConnection*)sender obj:(NSObject*)obj
{
//do something......
isLoading = NO;
}
-----------------------------in my AppDelegate-------------------------------
- (void)showProgressBarForTarget:(id)target whileExecuting:(SEL)theSelector
{
UIViewController *controller = [splitViewController.viewControllers objectAtIndex:0];
HUD = [[MBProgressHUD alloc] initWithView:controller.view];
[controller.view addSubview:HUD];
HUD.delegate = self;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:theSelector onTarget:target withObject:nil animated:YES];
}
-----------------------------Reasons for Rejection detail-------------------------------------
The most recent version of your app has been rejected........
Reasons for Rejection:
The steps to reproduce are:
Launch the app
Select the Menu button at the top left corner
Select a menu item
The activity indicator spins indefinetely and the user can't access the content
First off, the reason for this rejection is likely improper usage of MBProgressHUD, not MBprogressHUD itself.
If this only occurs during app store testing, try running the app in Release configuration. There also might be networking conditions there, that you haven't anticipated. Perhaps this only occurs when there is a network error (airplane mode?). Are you setting isLoading = NO when a network error occurs?
FWIW, there is a much better way to show / hide the HUD for asynchronous requests. Pooling a flag in a while loop like this is extremely inefficient. Look at the NSURLConnection example in the MBProgressHUD demo app.
Related
I am writing an ios app, that has multiple UIViewcontrollers. They all have UITableViews that are filled with data, that is acquired from different API's. But the problem that I am facing is that, when I tap on a cell, the the app won't navigate to the next page, until the data for that page is acquired. This make the app look, mighty slow. I need some way to navigate to next page, where I can put some spinner animation to let the user know that it is acquiring data(atleast). I don't want the user to think that the app has crashed, or something(it stays in the same page for solid 7-10 seconds)
Thanks in advance
you should call all the api in the background so it wont effect the main thread, use the queue for call api in background queue like below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
//call apis here
});
My solution to this problem is,You can call the API using dispatch queue,So that it will not affect other functionalities.
Please follow these simple steps
ViewController1 = VC1
ViewController1 = VC2
in VC1
[self.navigationController pushViewController:VC2 animated:YES];
in VC2
#implementation {
BOOL hasData;
}
-(void)viewDidLoad {
hasData = NO;
[self getAndShowData];
}
-(void)getAndShowData {
// Start Showing Spinner
// load your data from server and
// on success
1.) hasData = YES;
2.) Call reload tableview
// Remove spinner
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
if(hasData == NO) return 0;
return actual number of rows.
}
}
I'm examining some code written by someone else, as part of a new job I've been hired for. One of the bugs confronting me is that, on certain pages, when the MBProgressHUD is displayed for "Logging In" or "Connecting," it gets a chunk taken out of the middle. For example:
Obviously what I'm looking for is something a little more like this:
It only seems to happen when the app returns from the background (i.e. not when the app is first booting up, when we also use MBProgressHUD but it works perfectly), and only on certain pages. The box loads correctly, and then about half a turn of the Activity Indicator later, that hole appears. It then stays like that until the box disappears.
I'd add some code to look at, but to be perfectly honest, I don't know where to start. I can't think of anything that could take a chunk out of the middle like that, and as you can see from the transparency of the second picture, there doesn't seem to be a box of that shape/size behind the Activity Indicator that could be accidentally turning green.
I've never used MBProgressHUD myself before, and I've never encountered a graphical bug of this nature. Does anyone know what is going on, or failing that, can anyone give me some leads to investigate regarding what could be causing this behavior?
EDIT:
Below is the code used to add the Activity Indicator to the HUD (from within the MBProgressHUD object):
// Update to indeterminate indicator
[self.indicator removeFromSuperview];
self.indicator = nil;
if (IOSVersion >= 8.0 && (DeviceScreenSize().height >= 1136.0 || DeviceScreenSize().width >= 1136.0)) {
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
} else {
self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
}
[(UIActivityIndicatorView *)indicator startAnimating];
[self addSubview:indicator];
This is part of a larger updateIndicators method, but the salient points are there: the indicator is removed (in case there was another there before), then it is re-added and animated. Comment out either of the startAnimating or addSubview lines, and the HUD appears without the Activity Indicator, but the problem never occurs.
That sounds to me like the animation of the Activity Indicator is somehow causing the missing piece of the underlying view. But why would that be? Has anyone heard of that kind of thing before?
EDIT 2:
As far as I can tell, the problem only happens on one ViewController in the whole app, and yet that ViewController never references MBProgressHUD or Activity Indicators of any kind. And all of that functionality is in the AppDelegate method applicationDidBecomeActive:, as below:
MBProgressHUD* hud = [[MBProgressHUD alloc] initWithWindow:self.window];
[self.window addSubview:hud];
hud.labelText = LocalizedString(#"Logging in...");
[hud showAnimated:YES whileExecutingBlock:^{
User* U = self.SelectedUser;
if (!isEmpty(U)) {
if ([U networkLogin]) {
[self setSelectedUser:U];
if ([U Disabled] != disabledTypesNone) {
[Flurry logEvent:#"Login Failed" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue],
#"Disabled": [#([U Disabled]) stringValue]}];
ret = NO;
} else {
[Flurry logEvent:#"Login" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue]}];
[Flurry setUserID:[NSString stringWithFormat:#"%# - %#", U.EmpID, U.DisplayName]];
}
} else {
ret = NO;
}
}
} completionBlock:^{
[hud removeFromSuperview];
if (!ret) {
[[NSNotificationCenter defaultCenter] postNotificationName:kDisplayLogin object:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:kLoggedIn object:nil];
}
}];
Since it's happening in AppDelegate, it ought to be the same throughout the app, correct? And yet, when that same code is called (from AppDelegate, as before) while the app is on a different ViewController, it works with no problems.
What might make the behavior of that method different between different ViewControllers?
In our iPgone & iPad app we use push segue transitions between different ui contollers, most of them extend UICollectionViewController. In each controller we load data from our internal API. Loading is done viewWillAppear or viewDidLoad.
Now, the thing is, that this API call sometime can take a second or two, or even three... well, lot's of stuff there, let's assume we can't change it. But, we can change the user experience and at least add the "loading" circle indicator. The thing is, what I can't understand by means of correct concept, while transition from A to B, the "load" is done at B, while page A still presented.
So, question is "how do I show indicator on page A, while loading controller for page B?"
Thanks all,
Uri.
Common approach in this case is to load data in destination view controller NOT in main thread. You can show indicator while loading data in background thread and then remove it.
Here is sample of code from my project solving the same problem:
- (void) viewDidLoad {
...
// add indicator
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.view.center;
[self.view addSubview:self.spinner];
...
// fetch news
[self.spinner startAnimating];
__weak typeof(self) weakSelf = self
[[BitrixApiClient sharedInstance] getLatestNewsWithCompletionBlock:^(NSArray *newsArray, NSUInteger maxPageCount, NSUInteger currentPageNumber, NSError *error) {
if (!error) {
weakSelf.newsArray = newsArray;
weakSelf.currentPageNumber = currentPageNumber;
[weakSelf.newsTableView reloadData];
}
// stop spinning
[weakSelf.spinner stopAnimating];
}];
}
I've been trying to find some information regarding the new multitasking switcher in iOS 7 and especially the screenshot that the OS takes when the app is going into hibernation.
Is there any way to completely turn off this feature or screenshot? Or can I hide the app altogether from the switcher? The app needs to run in the background, but we do not want to show any screenshot from the app.
The screenshot is potentially a security-risk, think along the lines for banking-apps where your card number or account summary will be available to anyone that double-click on the home button on the device.
Anyone with any insight into this? Thanks.
In Preparing Your UI to Run in the Background, Apple says:
Prepare Your UI for the App Snapshot
At some point after your app enters the background and your delegate method returns, UIKit takes a snapshot of your app’s current user interface. The system displays the resulting image in the app switcher. It also displays the image temporarily when bringing your app back to the foreground.
Your app’s UI must not contain any sensitive user information, such as passwords or credit card numbers. If your interface contains such information, remove it from your views when entering the background. Also, dismiss alerts, temporary interfaces, and system view controllers that obscure your app’s content. The snapshot represents your app’s interface and should be recognizable to users. When your app returns to the foreground, you can restore data and views as appropriate.
See Technical Q&A QA1838: Preventing Sensitive Information From Appearing In The Task Switcher
In addition to obscuring/replacing sensitive information, you might also want to tell iOS 7 to not take the screen snapshot via ignoreSnapshotOnNextApplicationLaunch, whose documentation says:
If you feel that the snapshot cannot correctly reflect your app’s user interface when your app is relaunched, you can call ignoreSnapshotOnNextApplicationLaunch to prevent that snapshot image from being taken.
Having said that, it appears that the screen snapshot is still taken and I have therefore filed a bug report. But you should test further and see if using this setting helps.
If this was an enterprise app, you might also want to look into the appropriate setting of allowScreenShot outlined in the Restrictions Payload section of the Configuration Profile Reference.
Here is an implementation that achieves what I needed. You can present your own UIImageView, or your can use a delegate-protocol pattern to obscure the confidential information:
// SecureDelegate.h
#import <Foundation/Foundation.h>
#protocol SecureDelegate <NSObject>
- (void)hide:(id)object;
- (void)show:(id)object;
#end
I then gave my app delegate a property for that:
#property (weak, nonatomic) id<SecureDelegate> secureDelegate;
My view controller sets it:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.secureDelegate = self;
}
The view controller obviously implements that protocol:
- (void)hide:(id)object
{
self.passwordLabel.alpha = 0.0;
}
- (void)show:(id)object
{
self.passwordLabel.alpha = 1.0;
}
And, finally, my app delegate avails itself of this protocol and property:
- (void)applicationWillResignActive:(UIApplication *)application
{
[application ignoreSnapshotOnNextApplicationLaunch]; // this doesn't appear to work, whether called here or `didFinishLaunchingWithOptions`, but seems prudent to include it
[self.secureDelegate hide:#"applicationWillResignActive:"]; // you don't need to pass the "object", but it was useful during my testing...
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.secureDelegate show:#"applicationDidBecomeActive:"];
}
Note, I'm using applicationWillResignActive rather than the advised applicationDidEnterBackground, because, as others have pointed out, the latter is not called when double tapping on the home button while the app is running.
I wish I could use notifications to handle all of this, rather than the delegate-protocol pattern, but in my limited testing, the notifications aren't handled in a timely-enough manner, but the above pattern works fine.
This is the solution I worked with for my application:
As Tommy said: You can use the applicationWillResignActive. What I did was making a UIImageView with my SplashImage and add it as subview to my main window-
(void)applicationWillResignActive:(UIApplication *)application
{
imageView = [[UIImageView alloc]initWithFrame:[self.window frame]];
[imageView setImage:[UIImage imageNamed:#"Portrait(768x1024).png"]];
[self.window addSubview:imageView];
}
I used this method instead of applicationDidEnterBackground because applicationDidEnterBackground won't be triggered if you doubletap the home button, and applicationWillResignActive will be. I heard people say though it can be triggered in other cases aswell, so I'm still testing around to see if it gives problem, but none so far! ;)
Here to remove the imageview:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(imageView != nil) {
[imageView removeFromSuperview];
imageView = nil;
}
}
Hope this helps!
Sidenote: I tested this on both the simulator and a real device: It Won't Show on the simulator, but it does on a real device!
This quick and easy method will yield a black snapshot above your app's icon in the iOS7 or later app switcher.
First, take your app's key window (typically setup in AppDelegate.m in application:didFinishLaunchingWithOptions), and hide it when your app is about to move into the background:
- (void)applicationWillResignActive:(UIApplication *)application
{
if(isIOS7Or8)
{
self.window.hidden = YES;
}
}
Then, un-hide your app's key window when your app becomes active again:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(isIOS7Or8)
{
self.window.hidden = NO;
}
}
At this point, check out the app switcher and verify that you see a black snapshot above your app's icon. I've noticed that if you launch the app switcher immediately after moving your app into the background, there can be a delay of ~5 seconds where you'll see a snapshot of your app (the one you want to hide!), after which it transitions to an all-black snapshot. I'm not sure what's up with the delay; if anyone has any suggestions, please chime in.
If you want a color other than black in the switcher, you could do something like this by adding a subview with any background color you'd like:
- (void)applicationWillResignActive:(UIApplication *)application
{
if(isIOS7Or8)
{
UIView *colorView = [[[UIView alloc] initWithFrame:self.window.frame] autorelease];
colorView.tag = 9999;
colorView.backgroundColor = [UIColor purpleColor];
[self.window addSubview:colorView];
[self.window bringSubviewToFront:colorView];
}
}
Then, remove this color subview when your app becomes active again:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(isIOS7Or8)
{
UIView *colorView = [self.window viewWithTag:9999];
[colorView removeFromSuperview];
}
}
I used the following solution:
when application is going to resign I get appWindow snapshot as a View and add blur to it. Then I add this view to app window
how to do this:
in appDelegate just before implementation add line:
static const int kNVSBlurViewTag = 198490;//or wherever number you like
add this methods:
- (void)nvs_blurPresentedView
{
if ([self.window viewWithTag:kNVSBlurViewTag]){
return;
}
[self.window addSubview:[self p_blurView]];
}
- (void)nvs_unblurPresentedView
{
[[self.window viewWithTag:kNVSBlurViewTag] removeFromSuperview];
}
#pragma mark - Private
- (UIView *)p_blurView
{
UIView *snapshot = [self.window snapshotViewAfterScreenUpdates:NO];
UIView *blurView = nil;
if ([UIVisualEffectView class]){
UIVisualEffectView *aView = [[UIVisualEffectView alloc]initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
blurView = aView;
blurView.frame = snapshot.bounds;
[snapshot addSubview:aView];
}
else {
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:snapshot.bounds];
toolBar.barStyle = UIBarStyleBlackTranslucent;
[snapshot addSubview:toolBar];
}
snapshot.tag = kNVSBlurViewTag;
return snapshot;
}
make your appDelegate implementation be the as follows:
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
//...
//your code
//...
[self nvs_blurPresentedView];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
//...
//your code
//...
[self nvs_unblurPresentedView];
}
I created Example projects in Swift and Objective C.
Both projects makes the following actions in:
-application:didResignActive - snapshot is created, blurred and added to app window
-application:willBecomeActive blur view is being removed from window.
How to use:
Objecitve C
Add AppDelegate+NVSBlurAppScreen .h and .m files to your project
in your -applicationWillResignActive: method add the following line:
[self nvs_blurPresentedView];
in your -applicationDidEnterBackground: method add the following line:
[self nvs_unblurPresentedView];
Swift
add AppDelegateExtention.swift file to your project
in your applicationWillResignActive function add the following line:
blurPresentedView()
in your applicationDidBecomeActive function add the following line:
unblurPresentedView()
if only use [self.window addSubview:imageView]; in applicationWillResignActive function, This imageView won't cover UIAlertView, UIActionSheet or MFMailComposeViewController...
Best solution is
- (void)applicationWillResignActive:(UIApplication *)application
{
UIWindow *mainWindow = [[[UIApplication sharedApplication] windows] lastObject];
[mainWindow addSubview:imageView];
}
Providing my own solution as an "answers", though this solution is very unreliable. Sometimes i get a black screen as the screenshot, sometimes the XIB and sometimes a screenshot from the app itself. Depending on device and/or if i run this in the simulator.
Please note i cannot provide any code for this solution since it's a lot of app-specific details in there. But this should explain the basic gist of my solution.
In AppDelegate.m under applicationWillResignActive i check if we're
running iOS7, if we do i load a new view which is empty with the
app-logo in the middle. Once applicationDidBecomeActive is called i
re-launch my old views, which will be reset - but that works for the
type of application i'm developing.
You can use activator to configure double clicking of home button to launch multitasking and disable default double clicking of home button and launching of multitasking window. This method can be used to change the screenshots to the application's default image. This is applicable to apps with default passcode protection feature.
Xamarin.iOS
Adapted from https://stackoverflow.com/a/20040270/7561
Instead of just showing a color I wanted to show my launch screen.
public override void DidEnterBackground(UIApplication application)
{
//to add the background image in place of 'active' image
var backgroundImage = new UIImageView();
backgroundImage.Tag = 1234;
backgroundImage.Image = UIImage.FromBundle("Background");
backgroundImage.Frame = this.window.Frame;
this.window.AddSubview(backgroundImage);
this.window.BringSubviewToFront(backgroundImage);
}
public override void WillEnterForeground(UIApplication application)
{
//remove 'background' image
var backgroundView = this.window.ViewWithTag(1234);
if(null != backgroundView)
backgroundView.RemoveFromSuperview();
}
I made an app for iPhone. Now, I'm recreating it for iPad.
When the user selects the action button in the toolbar, a popover should show with a UIActivityViewController, but for some reason, it's taking about 10 seconds for it to show the first time. On iPhone, it takes about a second. It's the same code except for the popover.
I tried disabling the popover, but it still takes around 10 seconds to show.
Here is the code:
-(IBAction)Actions:(UIBarButtonItem*)sender
{
if ([activityPopover isPopoverVisible] == YES)
{
[activityPopover dismissPopoverAnimated:YES];
return;
}
UIWebView *currentWebView = ((TabView *)self.tabs[self.currentTabIndex]).webViewObject;
NSString *currentURL = (NSString*)[currentWebView request].mainDocumentURL;
if (currentURL == NULL) return;
BookmarkActivity *bookmarkActivity = [[BookmarkActivity alloc] init];
UIActivityViewController *sharing = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObject:currentURL] applicationActivities:#[bookmarkActivity]];
activityPopover = [[UIPopoverController alloc] initWithContentViewController:sharing];
[activityPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
I have tested on my iPad 3 and my iPad mini, both take awhile to present this.
How can I solve the problem?
Good question, I just had the same problem. It is not really solvable. However, you may improve the user experience by creating an activity indicator and then sending the initialization of the UIActivityViewController to the background:
-(void)openIn:(id)sender
{
// start activity indicator
[self.activityIndicator startAnimating];
// create new dispatch queue in background
dispatch_queue_t queue = dispatch_queue_create("openActivityIndicatorQueue", NULL);
// send initialization of UIActivityViewController in background
dispatch_async(queue, ^{
NSArray *dataToShare = #[#"MyData"];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:dataToShare applicationActivities:nil];
// when UIActivityViewController is finally initialized,
// hide indicator and present it on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
[self presentViewController:activityViewController animated:YES completion:nil];
});
});
}
It works like a charm. When the user touches the button, the activity indicator starts animating, thus indicating that the process will take a while.
I was having the same issue on iOS 7. When I removed UIActivityTypeAirDrop from the allowed activity types, however, the controller appears almost instantly.
Although these calls are already from the main thread, since iOS 7, wrapping some of those presentation calls in a dispatch block seems to greatly reduce the delay
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:activityViewController animated:YES completion:nil];
});
Had this issue myself recently. Would sometimes take nearly 4 or 5 seconds to pop up, which is a lifetime! Only the first time though. Subsequent calls were quick.
Also had a similar issue a couple of years back with the keyboard appearing slowly and someone produced a few lines of code added to the appdelegate that preloads the keyboard to get around that.
I used a similar approach here to preload the UIActivityViewController by placing this in the AppDelegate on startup. It's absolutely a hack which shouldn't be necessary but I couldn't find any other options.
let lagfreeAVC:UIActivityViewController = UIActivityViewController(activityItems: ["start"], applicationActivities: nil)
lagfreeAVC.becomeFirstResponder()
lagfreeAVC.resignFirstResponder()