I saw this tweet by Marco Armant:
Subclassed UIActionSheet w/target:action:userInfo: on buttons to avoid delegates/buttonIndex. Didn't someone else do this? Can't find it.
I think that sounds like a great idea, but I wasn't able to find anyone's code that did this. Does anyone know of one, before I go do it myself?
Yes, see my github for OHActionSheet.
It is implemented using blocks, so that you can use it this way, even without deporting the target/action code elsewere in your source code, the great advantage being that everything is located in the same place in your source code, and that you can use as many OHActionSheets as you want in the same controller
NSURL* anURL = ... // some URL (this is only as an example on using out-of-scope variables in blocks)
[OHActionSheet showSheetInView:yourView
title:#"Open this URL?"
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:[NSArray arrayWithObjects:#"Open",#"Bookmark",nil]
completion:^(OHActionSheet* sheet,NSInteger buttonIndex) {
if (buttonIndex == sheet.cancelButtonIndex) {
NSLog(#"You cancelled");
} else {
NSLog(#"You choosed button %d",buttonIndex);
switch (buttonIndex-sheet.firstOtherButtonIndex) {
case 0: // Open
// here you can access the anURL variable even if this code is executed asynchrously, thanks to the magic of blocks!
[[UIApplication sharedApplication] openURL:anURL];
break;
case 1: // Bookmark
default:
// Here you can even embed another OHAlertView for example
[OHAlertView showAlertWithTitle:#"Wooops"
message:#"This feature is not available yet, sorry!"
cancelButton:#"Damn"
otherButtons:nil
onButtonTapped:nil]; // no need for a completion block here
break;
} // switch
}
}];
[EDIT] Edited sample code to add more details and usage examples
Related
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?
I was looking for a way to detect people using IAPCracker in my application.
Lately I found this useful post How to detect “LocallAPStore" - new iap cracker and used it to protect some of my in-app-purchases.
Now I found a new source of cracking in-app-...you know. So I installed this new tweak called IAPFree which was a new way of cracking IAPs. I tested it on some apps and my own app and it worked, which is not good!
I tried to detect it by the same way as the IAPCracker:
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/DynamicLibraries/iap.dylib"]){
NSLog(#"IAP Cracker detected");
}
But the name of the file was unfortunately changed to "iapfree.core.dylib"
(I opened IFile and found the file in the same directory).
Now I thought I could simply replace the directory. However, it doesn't worked!
I used this code to detect it somehow:
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/DynamicLibraries/iapfree.core.dylib"]){
NSLog(#"IAPfree detected");
}else{
NSLog(#"No IAPFree found");
}
I thought this would be an random error and I tried it with other files in the same directory. They did worked!
I can't figure out whats the problem with this file. I think it could be caused by the ".core.", but actually I don't know.
Do you know how to solve the problem or detect it in another way?
The best way (also the only way "Apple approved") to solve the issue is to check the in app purchase receipts with an external server, not the presence of a cracker! There're lots of 3rd party services doing that quite easily and some even for free.
As alternative you can just check the receipts locally as shown here and here (full disclosure, it's my blog ;) ). It have some advantage (simpler, works even if the validating server is offline or not reachable) but of course new cracking systems may fool it.
Here an bit of code: when you check the paymentQueue (callback of the inApp protocol), you can do something like this:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
case SKPaymentTransactionStateRestored:
{
[self checkReceipt:[_productIdentifierList objectAtIndex:0] transazione:transaction];
[self finishPaymentTransaction:transaction];
}
break;
case SKPaymentTransactionStateFailed:
{
[UIView msgBox:#"Transaction Error" title:#"Errore"];
[self finishPaymentTransaction:transaction];
}
break;
default:
break;
}
}
}
- (void) checkReceipt:(SKProduct *)prodotto transazione:(SKPaymentTransaction *)transaction
{
NSString*ricevuta = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSRange hackTest = [transaction.transactionIdentifier rangeOfString:#"com.urus.iap"]; // ok if this not found
NSRange hackTest2 = [transaction.transactionIdentifier rangeOfString:#"PUT HERE YOUR INAPP ID"]; // TODO: PUT HERE YOUR INAPP ID
if (hackTest.location == NSNotFound && hackTest2.location == NSNotFound)
{
// it pass the local test: receipt is probably good
}
else
{
// invalid receipt, fake for sure, cancel buying...
}
}
please note that you have to put your inApp code in the "hackTest2" check: so if you have more than one product you may made a loop...
Check also for "IAPFreeService.dylib"
Hope this helps.
I have a UIActivity subclass that creates its own activityViewController:
- (UIViewController *)activityViewController {
WSLInProgressViewController* progressView = [[[WSLInProgressViewController alloc] init] autorelease];
progressView.message = [NSString stringWithFormat:NSLocalizedString(#"Posting to %#...",#"Posting to..."),
self.activityType];
return progressView;
}
I've add a full repro on GitHub.
According to the documentation, you aren't supposed to dismiss this manually. Instead, the OS does that when you call activityDidFinish:. This works fine when ran on an iPhone.
When I say "works," this is the sequence of events that I'm expecting (and see on the iPhone):
Display the UIActivityViewController
User presses my custom activity
My view controller appears
I call activityDidFinish:
My custom view controller is dismissed
The UIActivityViewController is also dismissed
However, when I run this same code on the iPad Simulator -- the only difference being that I put the UIActivityViewController in a popup, as the documentation says you should -- the activityViewController never dismisses.
As I say, this is code wo/the popUP works on the iPhone and I have stepped through the code so I know that activityDidFinish: is getting called.
I found this Radar talking about the same problem in iOS6 beta 3, but it seems such fundamental functionality that I suspect a bug in my code rather than OS (also note that it works correctly with the Twitter and Facebook functionality!).
Am I missing something? Do I need to do something special in the activityViewController when it's run in a UIPopoverViewController? Is the "flow" supposed to be different on the iPad?
The automatic dismissal only appears to happen when your 'activity' controller is directly presented, not wrapped in anything. So just before showing the popup it's wrapped in, add a completion handler
activity.completionHandler = ^(NSString *activityType, BOOL completed){
[self.popup dismissPopoverAnimated:YES];
};
and you'll be good.
I see the question is quite old, but we've been debugging the same view-controller-not-dismissing issue here and I hope my answer will provide some additional details and a better solution than calling up -dismissPopoverAnimated: manually.
The documentation on the UIActivity is quite sparse and while it hints on the way an implementation should be structured, the question shows it's not so obvious as it could be.
The first thing you should notice is the documentation states you should not be dismissing the view controller manually in anyway. This actually holds true.
What the documentation doesn't say, and what comes as an observable thing when you come across debugging the non-dissmissing-view-controller issue, is iOS will call your -activityViewController method when it needs a reference to the subject view controller. As it turns out, probably only on iPad, iOS doesn't actually store the returned view controller instance anywhere in it's structures and then, when it wants to dismiss the view controller, it merely asks your -activityViewController for the object and then dismisses it. The view controller instantiated in the first call to the method (when it was shown) is thus never dismissed. Ouch. This is the cause of the issue.
How do we properly fix this?
Skimming the UIActivity docs further one may stumble accross the -prepareWithActivityItems: method. The particular hint lies along the following text:
If the implementation of your service requires displaying additional UI to the user, you can use this method to prepare your view controller object and make it available from the activityViewController method.
So, the idea is to instantiate your view controller in the -prepareWithActivityItems: method and tackle it into an instance variable. Then merely return the same instance from your -activityViewController method.
Given this, the view controller will be properly hidden after you call the -activityDidFinish: method w/o any further manual intervention.
Bingo.
NB! Digging this a bit further, the -prepareWithActivityItems: should not instantiate a new view controller each time it's called. If you have previously created one, you should merely re-use it. In our case it happily crashed if we didn't.
I hope this helps someone. :)
I had the same problem. It solved for me saving activityViewController as member and return stored controller. Activity return new object and dismiss invoked on new one.
- (UIViewController *)activityViewController {
if (!self.detaisController) {
// create detailsController
}
return self.detailsController;
}
I pass through the UIActivity to another view then call the following...
[myActivity activityDidFinish:YES];
This works on my device as well as in the simulator. Make sure you're not overriding the activityDidFinish method in your UIActivity .m file as I was doing previously. You can see the code i'm using here.
a workaround is to ask the calling ViewController to perform segue to your destination ViewController via - (void)performActivity although Apple does not recommend to do so.
For example:
- (void)performActivity
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
[self.delegate performSomething]; // (delegate is the calling VC)
[self activityDidFinish: YES];
}
}
- (UIViewController *)activityViewController
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIViewController* vc=XXX;
return vc;
}
else
{
return nil;
}
}
Do you use storyboards? Maybe in your iPad storyboard, the UIActivityIndicatorView doesn't have a check on "Hides When Stopped"?
Hope it helps!
So I had the same problem, I had a custom UIActivity with a custom activityViewController and when it was presented modally it would not dismiss not matter what I tried. The work around I choose to go with so that the experience remained the same to the user was to still use a custom UIActivity but give that activity a delegate. So in my UIActiviy subclass I have the following:
- (void)performActivity
{
if ([self.delegate respondsToSelector:#selector(showViewController)]) {
[self.delegate showViewController];
}
[self activityDidFinish:YES];
}
- (UIViewController *)activityViewController
{
return nil;
}
Then I make the view controller that shows the UIActivityViewController the delegate and it shows the view controller that you would otherwise show in activityViewController in the delegate method.
what about releasing at the end? Using non-arc project!
[progressView release];
Many Users have the same problem as u do! Another solution is:
UIActivityIndicatorView *progress= [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 50, 30, 30)];
progress.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
[alert addSubview:progress];
[progress startAnimating];
If you are using storyboard be sure that when u click on the activityind. "Hides When Stopped" is clicked!
Hope that helped...
At this point I'm pretty frustrated but I'm sure it is something I'm missing. In this code my segue to my new viewController is showing up after the rest of the function is executed. How do I get my viewController to be the code being executed? Basically stop the tweetText function from happening until that view is closed I'm trying to give the user an option to select a twitter account if there is more than one. I have tried many different ways. In Apples own example code they suggest to give the user an option but give nothing on how to do it without blowing through the rest of the code.
Here is the code:
[accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
dispatch_sync(dispatch_get_main_queue(), ^{
// Get the list of Twitter accounts.
self.accountsArray = [accountStore accountsWithAccountType:accountType];
if([self.accountsArray count] > 1 /* Check method to see if preference is still a valid account */) {
// Display user accounts if no preference has been set
[self performSegueWithIdentifier:#"TwitterAccounts" sender:self.accountsArray];
[tweet tweetText:tweetString account:self.twitterAccount type:AchievementTweet];
} else {
[tweet tweetText:tweetString account:[self.accountsArray lastObject] type:AchievementTweet];
}
});
} else {
[tweet performSelectorOnMainThread:#selector(displayText:) withObject:[NSNumber numberWithInt:403] waitUntilDone:NO];
}
}];
I am a little confused on what is your purpose but you got 2 options.
There is really no need for GCD here.
I would suggest to register for a notification which will perform your second action once your first action has been accomplished. (Similar to how apple handles the image picker, on their code they inform of when the picture has finally been saved to execute another function).
The other way is using delegates, on this approach you would put the second part of the code on a separate function, then declare that controller as the delegate of the viewcontroller you want executed first, then after whatever you want acomplished is done on the viewcontroller you would ask the delegate to perform the second code and if necessary ask the delegate to close the view as well.
When the user presses my 'import' button, they must be able to type in a URL, which they can then 'ok' or 'cancel'.
How do I do this?
Obviously I could create a new view containing a text field and 2 buttons. But this seems like over coding.
One solution I found involves ' hacking ' a UITextField into a UIAlertView: http://iphone-dev-tips.alterplay.com/2009/12/username-and-password-uitextfields-in.html
(EDIT: Better -- http://www.iphonedevsdk.com/forum/iphone-sdk-development/1704-uitextfield-inside-uialertview.html#post10643 )
This looks really ugly. It is clearly a hack.
Can anyone provide a better solution (or even a better implementation of the same solution path)?
OK After a ton of digging, here is the result.
Firstly, putting in 'UITextField UIAlertView' into SO's search returns dozens of hits.
It turns out there is a method for doing this, Need new way to add a UITextField to a UIAlertView but it is private API :|
Pretty much every other solution involves hacking a UIAlertView, which is ugly:
http://junecloud.com/journal/code/displaying-a-password-or-text-entry-prompt-on-the-iphone.html
http://iphone-dev-tips.alterplay.com/2009/12/username-and-password-uitextfields-in.html
https://github.com/josecastillo/EGOTextFieldAlertView/
How to move the buttons in a UIAlertView to make room for an inserted UITextField?
^ MrGando's answer is neat
http://iphonedevelopment.blogspot.com/2009/02/alert-view-with-prompt.html
Getting text from UIAlertView
The only proper solution I found (ie coding from scratch from a UIView) is here: https://github.com/TomSwift/TSAlertView
This is all it takes:
- (IBAction) importTap
{
TSAlertView* av = [[[TSAlertView alloc] init] autorelease];
av.title = #"Enter URL";
av.message = #"";
[av addButtonWithTitle: #"Ok"];
[av addButtonWithTitle: #"Cancel"];
av.style = TSAlertViewStyleInput;
av.buttonLayout = TSAlertViewButtonLayoutNormal;
av.delegate = self;
av.inputTextField.text = #"http://";
[av show];
}
// after animation
- (void) alertView: (TSAlertView *) alertView
didDismissWithButtonIndex: (NSInteger) buttonIndex
{
// cancel
if( buttonIndex == 1 )
return;
LOG( #"Got: %#", alertView.inputTextField.text );
}
and Presto!
Check out: https://github.com/enormego/EGOTextFieldAlertView
I have created a post in my blog on the topic "How to add UITextField to UIAlertView from XIB". Using XIB to add is easier than pure coding. You can add whatever in a customized small view, then add the small view into the AlertView using 1 line code. Please refer to the following link.
http://creiapp.blogspot.com/2011/08/how-to-add-uitextfield-to-uialertview.html