I was using a code snippet in my project answered here: UIAlertView without having reference to it
Here's the code:
+ (UIAlertView *) getUIAlertViewIfShown {
if ([[[UIApplication sharedApplication] windows] count] == 1) {
return nil;
}
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
if ([window.subviews count] > 0) {
UIView *view = [window.subviews objectAtIndex:0];
if ([view isKindOfClass:[UIAlertView class]]) {
return (UIAlertView *) view;
}
}
return nil;
}
Unfortunately its not working in iOS 7 and I'm unable to dismiss an alert view. While debugging I found that in the loop its showing that view is of class UITransitionView. Pretty confusing because I couldn't find any quick documentation for this view class.
Any ideas how can I fix this problem?
In iOS7, the UIAlertView window does not appear in -[UIApplication windows]. In fact, the UIAlertView itself is never added to any window, -[UIAlertView window] is always nil. Instead, the alert view manages a variety of undocumented views placed in -[UIApplication keyWindow] with no reference back to the alert view.
Your only real option in iOS7 is to actually keep track of your alert views.
iOS 7 solution
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
I am not sure if it is approvable by AppStore, but works
UPD single line code:
UIAlertView *topMostAlert = [NSClassFromString(#"_UIAlertManager") performSelector:#selector(topMostAlert)];
I have faced similar issue and in my case alerts are displayed from different instance of view controller, As Brian has already mentioned that UIAlertView window does not appear in -[UIApplication windows] in iOS7.
So to keep track of this following approach can be followed -
Define a BOOL constant in App Delegate -
#property (nonatomic, assign) BOOL isAlertVisibleOnAppWindow;
Where 'UIAlerView` is present, check earlier instance existence -
AppDelegate *delegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
if (!delegate.isAlertVisibleOnAppWindow) {
delegate.isAlertVisibleOnAppWindow = YES;
UIAlertView *alertView = [[UIAlertView alloc] init…//alert init code
// Either handle alert cancel/completeion click here via blocks, or use alert delegates to reset the isAlertVisibleOnAppWindow BOOL variable to NO.
}
This might be helpful to some other people, thought of sharing this.
UIAlertView *topMostAlert = [NSClassFromString(#"_UIAlertManager") performSelector:#selector(topMostAlert)];
This will NOT be allowed to publish into Apple Store. During build validation Xcode will throw an error, something like: "access to undocumented method.."
So you can't use it, however this code works well.
You can register to UIWindowDidBecomeVisibleNotification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(aWindowBecameVisible:)
name:UIWindowDidBecomeVisibleNotification
object:nil];
and in aWindowBecameVisible check the window description for _UIModalItemHostingWin:
if ([[theWindow description] hasPrefix:#"<_UIModalItemHostingWin"])
{
// This is the alert window
}
Related
I want to add 3d touch home screen shortcuts to my objective c app. The main part of my app functions when a UIButton is pressed. This calls the method in the ViewController.h and ViewController.m
- (IBAction)StartScanning:(id)sender;
This allows the camera to start functioning and pushes the correct view controller and desired methods.
I have read many walkthroughs but still cannot understand how to start this IBAction when a 3d touch shortcut is pressed.
Sorry if this is a repeat question or I am just being stupid. I'm a bit new to all of this.
It's not that hard to do and as you mentioned, there's a lot of tutorials out there to help. In summary, first you need to add the 3d touch delegate to your ViewController.h or .m. <UIViewControllerPreviewingDelegate>
That will give you access to the delegate methods you need to show the home screen shortcuts. Here's an example of one of my apps (name removed in this example).
In my AppDelegate, performActionForShortcutItem:completionHandler: is called first and sent the shortcut the user selected. Use it to determine how to respond to the shortcut. I passed the shortcut to a method, handleShortcutItem:shortcutItem" that would determine which storyboard I would use (I know there's no 3D Touch in iPads right now but I wanted to build in the code for when Apple comes out with one).
Based on the shortcut, I create my ViewController and pass the shortcut to the method logShortcutUsed, passing in the shortcut title.
#pragma mark - Shortcut Items
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
completionHandler([self handleShortcutItem:shortcutItem]);
}
- (BOOL)handleShortcutItem:(UIApplicationShortcutItem *)shortcutItem {
UIStoryboard *storyboard;
UINavigationController *navController = (UINavigationController *) self.window.rootViewController;
if (IS_IPAD()) {
storyboard = [UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil];
} else {
storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
}
xxxViewController *vb = (xxxViewController *)navController.topViewController;
if ([shortcutItem.localizedTitle isEqualToString:#"New Match"]) {
[vb logShortcutUsed:shortcutItem.localizedTitle];
[vb startNewMatch];
return TRUE;
} else if ([shortcutItem.localizedTitle isEqualToString:#"New Game"]) {
[vb logShortcutUsed:shortcutItem.localizedTitle];
[vb gamePressedFromShortcut];
return TRUE;
}
return FALSE;
}
In my main ViewController, I create the dynamic shortcuts (you can have static or dynamic shortcuts). This is what will be seen by the user when they 3D Touch the icon. I include an icon as well, that's optional. The shortcutItems is just an array of UIApplicationShortcutItems.
- (void)setupDynamicShortcuts {
UIApplicationShortcutItem *newMatch = [[UIApplicationShortcutItem alloc] initWithType:#"$(PRODUCT_BUNDLE_IDENTIFIER).NewMatch"
localizedTitle:NSLocalizedString(#"New Match", #"Start a new match")
localizedSubtitle:NSLocalizedString(#"Start a new match", #"Start a new match button.")
icon:[UIApplicationShortcutIcon iconWithTemplateImageName:#"Sport Net-50"]
userInfo:nil];
UIApplicationShortcutItem *newGame = [[UIApplicationShortcutItem alloc] initWithType:#"$(PRODUCT_BUNDLE_IDENTIFIER).NewGame"
localizedTitle:NSLocalizedString(#"New Game", #"Start a new game")
localizedSubtitle:NSLocalizedString(#"Start a new game", #"Start a new game button.")
icon:[UIApplicationShortcutIcon iconWithTemplateImageName:#"volleyball-50"]
userInfo:nil];
[UIApplication sharedApplication].shortcutItems = #[newMatch, newGame];
}
In the same ViewController are the methods that will be called from the AppDelegate, startNewMatch and gamePressedFromShortcut. I also log these calls to my analytics so I can track how many times people use this feature, which is something I strongly suggest.
It's not as difficult as it initially seems.
Okay I am kind of new to IOS development, but I am writing an application where I am using a timer class to time out the user if they idle too long on any particular scene in my storyboard and it bumps the user back to the original scene/view. I have a single story board that is made up of several scenes/views(not sure what the correct word here is), and each scene has its own view controller.
I accomplish the timeout via the appdelegate class. See code below.
So I have the code working and it works great, but I am trying to make it so that it will ignore the timer if we are on the main scene.
I have googled this, read copious amounts of documentation, and have tried many things but so far I haven't been able to figure out how to get the currently viewed scene in the applicationDidTimeout method.
If I can get the name of the currently viewed scene/view, then I can choose to ignore the timer or not.
Does anyone know how to do this?
Thank you for your time.
#import "StoryboardAppDelegate.h"
#import "TIMERUIApplication.h"
#implementation StoryboardAppDelegate
#synthesize window = _window;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// applicaiton has timed out
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
-(void)applicationDidTimeout:(NSNotification *) notif
{
NSLog (#"time exceeded!!");
UIViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:NULL] instantiateViewControllerWithIdentifier:#"StoryboardViewController"];
UINavigationController * navigation = [[UINavigationController alloc]initWithRootViewController:controller];
[self.window setRootViewController:navigation];
navigation.delegate = self;
navigation.navigationBarHidden = YES;
if (controller) {
#try {
[navigation pushViewController:controller animated:NO];
} #catch (NSException * ex) {
//“Pushing the same view controller instance more than once is not supported”
//NSInvalidArgumentException
NSLog(#"Exception: [%#]:%#",[ex class], ex );
NSLog(#"ex.name:'%#'", ex.name);
NSLog(#"ex.reason:'%#'", ex.reason);
//Full error includes class pointer address so only care if it starts with this error
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if ([ex.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
//view controller already exists in the stack - just pop back to it
[navigation popToViewController:controller animated:NO];
} else {
NSLog(#"ERROR:UNHANDLED EXCEPTION TYPE:%#", ex);
}
} #finally {
//NSLog(#"finally");
}
} else {
NSLog(#"ERROR:pushViewController: viewController is nil");
}
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
}
#end
I'm assuming you've written the logic for the timer somewhere. Can you just invalidate the timer when you've popped back to the rootViewController?
Also instead of pushing a viewController onto the navigationViewController and handling the errors, you should check to see if the controller you're pushing is already in the stack like so:
if (![navigation.viewControllers containsObject:viewController] {
// push onto the stack
}
You could also check to see how many levels are currently in the navigationController by checking the count of the viewControllers array like so:
if ([navigation.viewControllers count] == 0) {
// I know we're on the main screen because no additional viewControllers have been added to the stack.
}
If you are not using modal controllers anywhere then the simplest solution would be
UINavigationController* nav = (UINavigationController*)self.window.rootViewController; // You could just save the nav as part of your app delegate
if (nav.viewControllers.count > 1){
[nav popToRootViewControllerAnimated:YES];
}
This is different then your current code because your main page will not be deleted and recreated every time the timer goes off
Okay I figured out how to do this. I was making this way too complicated.
To solve this I simply made a property and method in the app delegate class where I could set a scene name.
Then in each view controller header file I import the header file for the app delegate class and define a reference to it. Then in the load event for each view I simply set the scene name in the app delegate class using this line of code.
[myAppDelegate setSceneName:self.title];
Easy peasy!
I have a framework that creates some views, the app that uses the framework calls a method from it and pass in the current view controller, the framework then calls presentModalViewController to display a view.
It was working just fine with iOS 6.1 SDK but when I updated to Xcode 5 and iOS 7 SDK I don't see the modal view anymore, instead all I get is a blank screen.
EDIT
Heres some code:
The Framework is called "testityi"
testityi.m
#import "TestViewController.h"
#implementation testitiy
- (NSString*) sayHi : (NSString*) name {
return [NSString stringWithFormat:#"Hello %#", name];
}
- (void) displayView:(UIViewController *)parentController {
TestViewController* controller = [[TestViewController alloc] init];
[parentController presentViewController:controller animated:YES completion:nil];
}
TestViewController is simply a view with a label that says "View from framework"
The framework itself works fine, calling sayHi method works just fine.
The third party app has a view with a label and a button which calls sayHi method and then displayView method, heres the view controller code:
MainViewController.m
- (IBAction)buttonPressed:(id)sender {
testitiy* framework = [[testitiy alloc] init];
NSString* msg = [NSString stringWithFormat:#"Calling sayHi method on framework...\n result: %#", [framework sayHi:#"John"]];
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"sayHi method call" message:msg delegate:self cancelButtonTitle:#"Ok, show me the view" otherButtonTitles:nil, nil];
[alert show];
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if(buttonIndex == [alertView cancelButtonIndex]) {
testitiy* framework = [[testitiy alloc] init];
[framework displayView:self];
}
}
The alert button action is also working correctly, I added a NSLog before and its working.
After clicking the alert button a view is presented but instead of containing the label "View from framework" I get a blank screen.
You can see the code on Github
EDIT 2
I got it... I wasn't calling initWithBundle on the ViewController from the framework, I added the a custom init method that calls:
framework: TestViewController.m
+ (NSBundle *)frameworkBundle {
static NSBundle* frameworkBundle = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:#"testity.bundle"];
frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath];
});
return frameworkBundle;
}
- (id) initWithFramework {
NSBundle* bundle = [[self class] frameworkBundle];
self = [super initWithNibName:#"TestViewController" bundle: bundle];
return self;
}
And changed testitiy.m
- (void) displayView:(UIViewController *)parentController {
TestViewController* controller = [[TestViewController alloc] initWithFramework];
[parentController presentViewController:controller animated:YES completion:nil];
//[parentController.navigationController pushViewController:controller animated:YES];
}
And now its working...
I hope this helps someone else but I'm guessing it was a stupid mistake of mine.
Sorry for all the trouble and thanks for your time!
So after a while I finally understand the issue:
When using a custom framework, all resources like images and NIB files have to be manually included in the third-party app so that it has access to those files.
My problem was that I was including the resources (stored in a bundle) into the third-party app but the framework was trying to display the View based on its own resources, which the app couldn't access, for that reason I was getting a blank screen.
I just needed to tell the framework to use the included bundle into the third-party app to display that View (using the initWithNibName: Bundle method).
See EDIT 2 in the question to see the code that solved my problem.
Hope this helps someone. :-)
I'm using a UIWebView, and it pops an alert when a button is clicked(from javascript). There is another button (in native side), which closes controller, so deallocs also UIWebView.
The problem is, if I touch the button in UIWebView, and touch to close button before alert is populated, my controller and UIWebView are deallocated, but alert remains on screen. Then if I click any button on alert, application crashes and gives following error:
[UIWebView modalView:didDismissWithButtonIndex:]: message sent to deallocated instance
And this method is called from private method
[UIModalView(Private) _popoutAnimationDidStop:finished:]
I'm using ARC, and my dealloc is like this:
- (void)dealloc {
[_myWebView stopLoading];
_myWebView.delegate = nil;
_myWebView = nil;
}
But this does not solve my problem because I think UIModalView has a reference of my webview as a delegate, and I could not set it to nil because its private.
How can I solve it?
Regards
Find a way to set the delegate on UIAlertView to nil before deallocating UIWebView.
This is an Apple bug in their handling of the alert view. Open a bug report.
In the meantime, here are some workarounds:
Create a category on UIAlertView:
#interface UIAlertView (QuickDismiss) #end
#implementation UIAlertView (QuickDismiss)
- (void)__quickDismiss
{
[self dismissWithClickedButtonIndex:self.cancelButtonIndex animated:NO];
}
#end
Now, in your view controller's dealloc method, call this:
[[UIApplication sharedApplication] sendAction:#selector(__quickDismiss) to:nil from:nil forEvent:nil];
This will dismiss all alert views that are currently open, including the ones displayed by your web view.
If that does not work, you can always iterate all subviews of all UIApplication.sharedApplication.windows objects, checking whether [view.class.description hasPrefix:#"UIAlertView"] is true, and dismissing that. This is a less elegant method than the previous one, and should be last resort.
Good luck.
Finally, I find a great solution which actully works. I use method swizzle to hook UIAlertView Delegate function - (void)didPresentAlertView:(UIAlertView *)alertView
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ([self.delegate respondsToSelector:#selector(didPresentAlertView:)]) {
[self.delegate didPresentAlertView:alertView];
}
if ([self.delegate isKindOfClass: [UIWebView class]]) {
uiWebView = self.delegate;
}
}
I just retain UIWebView instance in this function so that the UIWebView instance as UIAlertView's delegate will not be released before UIAlertView instance being released.
I´m trying to integrate ShareKit in my ios game.
Everything is working fine and the actionsheet is shown and I can interact with it but I´m not able to return the focus to my app when the sharekit action has finished (by closing the actionsheet or finishing any action).
I have tried in several ways but any has worked for me. What´s happening?
I´m not an expert programmer so I expect I´m missing something.
I´m
This is my .h
#import <UIKit/UIKit.h>
#import "SHK.h"
#import "SHKConfiguration.h"
#interface SocialWrapper: UIViewController{
}
- (id) init;
- (void) open;
- (void) dealloc;
#end
and .m
#import "SocializeWrapper.h"
#implementation SocialWrapper
- (id) init {
self=[super init];
DefaultSHKConfigurator *configurator = [[DefaultSHKConfigurator alloc] init];
[SHKConfiguration sharedInstanceWithConfigurator:configurator];
[SHK flushOfflineQueue];
return self;
}
- (void) open
{
NSString *someText = #"Hello Earth!";
SHKItem *item = [SHKItem text:someText];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[window addSubview:self.view];
[SHK setRootViewController:self];
[actionSheet showInView:self.view];
}
- (void) dealloc {
NSLog(#"SHK dealloc");
[self release];
[super dealloc];
}
#end
I´m calling it by using this wrapper
#import "SocializeWrapper.h"
SocialWrapper *socialize;
void SHKinit(void) {
NSLog(#"SHK Init");
socialize = [[SocialWrapper alloc] init];
}
void SHKopenWeb(void){
NSLog(#"SHK Open actionsheet");
[socialize open];
}
I´m working with ios 5, xcode 4.3.2 and the last sharekit version from the git.
I think I have to dissmiss my SocialWrapper once the actionsheet is closed but I don´t know how to capture that event, or even if this is correct. I´m stucked.
any help will be greatly appreciated.
UPDATE
As comment adviced, now the controller is on a category, using the actionsheet delegate, the focus can be regained when clicking the cancel´s actionsheet button. The problem still persists when an action is finished or cancelled. Don´t know how to capture that event.
This is my category code:
#import "SocialWrapper.h"
#implementation UIViewController (SocialController)
-(void) loadconfig
{
DefaultSHKConfigurator *configurator = [[DefaultSHKConfigurator alloc] init];
[SHKConfiguration sharedInstanceWithConfigurator:configurator];
[SHK flushOfflineQueue];
}
- (void) open
{
NSLog(#"Opening social button");
NSString *someText = #"Monkey Armada rules!";
SHKItem *item = [SHKItem text:someText];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[window addSubview:self.view];
[actionSheet setDelegate:self];
[SHK setRootViewController:self];
[actionSheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSLog(#"SHK actionsheet dissmiss with button %d", buttonIndex);
if(buttonIndex == 4)
{
NSLog(#"SHK close actionsheet");
[self dismissViewControllerAnimated:YES completion:nil];
[self.view removeFromSuperview];
}
}
#end
Well since SHKActionSheet is a subclass of UIActionSheet you can set the delegate of that class to self to know when the dismissal happens.
Also, [self release]; in dealloc is complete misunderstanding of what release does, if you're in dealloc then releasing self won't do anything !
Learn the memory management rules.
I should also warn you that [window addSubview:self.view] is deprecated, you should not do that at all. In fact, I don't see a reason to wrap share kit stuff each view controller should be able to write that code easily. At worse you could put that code in a category on UIViewController if you don't want to rewrite the code every time.