Getting top UIWIndow/UIView on device using public/non-public/private API - ios

I'm trying to find out how to find the top most view using non-public/private api, is there any way doing it (iOS 7.1.2)?
Edit:
Maybe I wasn't clear,
I want to get the top most view no matter what app is active now (facebook, whatsapp, games, lockscreen, calendar, etc.).

Options
If I understand correctly, you're wanting to show a UIView on top of another application's window. In the app sandbox, you are only allowed access to your own application's window. Jailbreaking, however, will allow you to do this, but it's not exactly simple.
In order to show a view on top of applications, you have three choices. Which option you choose depends on what you're trying to do.
Inject your code into third-party applications
Inject your code into SpringBoard
Use a combination of methods 1 and 2.
Option 1
If you choose to go with this route, your code will be running inside each third-party application. This allows you to modify the view hierarchy of the application to show your own window or even modify existing ones. Since you're code is running in the application, you can obtain the key window by simply calling:
[[UIApplication sharedApplication] keyWindow]
Option 2
If you choose this option, your code will be running regardless of what application is open. By obtaining the main window, you can show UI over anything, including system alerts. I've posted an example of this to GitHub at rpendleton/tutorials/so-26032688. The example shows an alert anytime the user takes their phone out of silent. The code to retrieve SpringBoard's window is:
[[NSClassFromString(#"SBUIController") sharedInstance] window]
Option 3
The third option, and the one you'll likely end up using, is a combination of both options 1 and 2. In this scenario, you'll inject your code into third-party applications, and then communicate with SpringBoard to decide what needs to be done. Typically, your application would communicate with SpringBoard, then SpringBoard can relay that information to the desired application. You may be able to communicate directly between your application and a third party application, but I haven't tried that.
Communication
In order to communicate between your application and SpringBoard / other third-party applications, you'll need to use a messaging system. An easy way to do this communication is via a open source library called RocketBootstrap.
Screenshots
The screenshot on the left is from the example I posted on GitHub, and the screenshot on the right is from one of my tweaks AppScan. Although I'm presenting an alert in both, you can show whatever view you desire.

try getting window of AppDelegate like this
UIWindow *window = ((AppDelegate*)[UIApplication sharedApplication].delegate).window;
And then access window's last object like this
[[window subviews] objectAtIndex:0];
or like this
[[window subviews]lastObject];
If you want to addsubview to it
[[[window subviews] objectAtIndex:0] addSubview:view];

Reading the question and the comments this is what you're looking for...I think, not the most detailed question I saw:
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = self;
window.windowLevel = [[UIApplication sharedApplication] keyWindow].windowLevel + 1;
[window makeKeyAndVisible];
You want to use this as part of the view controller that you want on top.

You question is not totally clear, can you explain why you need to do that?
By the way most probably what you need is the key window first, as stated in the doc:
The key window is the one that is designated to receive keyboard and
other non-touch related events. Only one window at a time may be the
key window.
What you can do is ask for the application current key window:
NSArray * array = [[UIApplication sharedApplication] windows];
Iterate over that array to find the key window and then check for its subviews:
UIView * topView = [[window subviews]lastObject]
If you are looking to a way for emulate an alert view, You should create your own UIWindow and later ad your view controller that handles the "alert" as a rootViewController and make it" key and visible".
Something like that :
UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
alertWindow.rootViewController = [[AlertViewController alloc] initWithXibNamed:#"AlertXib"];
alertWindow.windowLevel = UIWindowLevelAlert;
[alertWindow makeKeyAndVisible];
And when you want to dismiss just set the rootViewController to nil and make the window hidden.
Using the root view controller approach helps into avoid messing with rotation corrections. Even if in iOS8 the rotates.
I've found a good reference here.

Related

How can I choose which scene gets restored on iPadOS when all have been dismissed?

I have an iPad app in which I'm starting to support multiple windows / scenes. I have one main window type, let's say MainScene, and at least one secondary window type for opening specific types of content, say DetailScene.
I have not declared my scene types in Info.plist. I have implemented application:configurationForConnectingSceneSession:options: like this:
-(UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options
{
NSUserActivity *activity = options.userActivities.anyObject;
NSString *activityType = activity.activityType;
if ([activityType isEqualToString:#"detailType"])
return [DetailSceneDelegate makeSceneConfiguration];
return [MainSceneDelegate makeSceneConfiguration];
}
Say I perform these steps:
Launch app for the first time. I get a call to configurationForConnectingSceneSession, and the activity type is nil so it returns a MainScene.
Open a new window for some piece of content. That uses the detail scene activity type, so configurationForConnectingSceneSession returns a DetailScene. Creating the new scene looks like this:
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"detailType"];
activity.userInfo = #{#"content_id": #(contentRowId)};
[[UIApplication sharedApplication] requestSceneSessionActivation:nil userActivity:activity options:nil errorHandler:nil];
Suspend the app and open the app switcher. Discard (by flicking up) first the main window and then the detail window. The app is now killed.
Relaunch the app.
At this point I do not get a call to configurationForConnectingSceneSession. I get the detail scene back, restored from its user activity, with calls straight to DetailSceneDelegate.
My question: how do I control what scene gets restored in this situation? I want my main scene to come back.
Messages and Mail and Notes all do this. If you open Messages and drag a conversation out into a new window, you get a window for that conversation with a Done button in the corner that will dismiss the window. If you perform my steps above with Messages, you will relaunch to the full Messages view. Are they converting the detail view to a main view on the fly? Or is there a way to tell the system that the detail scene is secondary and should not be restored first, or that I should get asked what I want to restore via configurationForConnectingSceneSession? Or something else?
I cross-posted this to the Apple Developer forums and got an answer from a Framework Engineer. The answer is UISceneActivationConditions, set as a property on UIScene. Set the canActivateForTargetContentIdentifierPredicate to always return NO:
scene.activationConditions.canActivateForTargetContentIdentifierPredicate = [NSPredicate predicateWithValue:NO];
I do this in my implementation of scene:willConnectToSession:options: in my UIWindowSceneDelegate implementation.
As of current writing, on Xcode 13.2 and iOS 15.2 simulator, it works if the second launch (step 4 above) is via tapping the app icon, but not when it's via building and running in Xcode. I may file a feedback on this.

Finding TopMostWindow/FrontMostWindow in iOS 8

In iOS 7 and earlier, if we want to find topMostWindow in application, we were commonly using following line of code
[[[UIApplication sharedApplication] windows] lastObject]
This will commonly return UITextEffectsWindow if there is keyboard available on screen else it returns UIWindow.(This is common scenario, not always true)
But, In iOS 8, when i use above command it always gives me UITextEffectsWindow. Also, [[UIApplication sharedApplication] windows] always contains 2 objects(UIWindow and UITextEffectWindow). What should i do to get FrontMostWindow in case of iOS 8.
I think the (internal) UITextEffectsWindow is in fact the frontmost window. So I guess the question is not how to get the frontmost window, but what window you want to get.
Maybe you are looking for the key window? (From the docs: "The key window is the one that is designated to receive keyboard and other non-touch related events.")
So this would be
[[UIApplication sharedApplication] keyWindow]
I think this is the front most window from an end user's perspective (in all cases I have seen so far).

How to call a view controller from AppDelegate in iOS

I am creating an iOS app in which I have the following requeriment: the app should show the login screen when it starts the first time and also that screen must also be shown when the app comes from the background to the foreground, in case it has been sent to background during run time.
I have handled to show the screen in both cases. However, when I do it and the app comes from the background, and I click the texfield to type my password, the app gets frozen, and it fails in a thread that I don't know what it means.
I call the screen to be shown when the app comes from background like this, in the applicationWillEnterForeground, in AppDelegate:
self.window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RoomRootViewController* room = [[RoomRootViewController alloc] init];
[[self window] setRootViewController:room];
[self.window makeKeyAndVisible];
Is this the correct way to do so?
Thanks a lot in advance ! I am completely lost with this as I am very new in iOS, so any help will be very appreciated.
Attached in an image you can see where the app fails.
The code you are currently using is completely deleting the root view controller of your app window, in other words, you are deleting all the views and view controllers in your app. If you are not using ARC, this is just one huge memory leak. If you are, it's still not a very good idea.
In your applicationWillEnterForeground: method, try using this code instead:
RoomRootViewController* room = [[RoomRootViewController alloc] init];
[self.window.rootViewController presentViewController:room
animated:NO
completion:nil];
This will display the RoomRootViewController over the top of all your app's current views, instead of deleting them. You can then dismiss it like this:
[self.window.rootViewController dismissViewControllerAnimated:YES
completion:nil];
and easily return to the rest of your app.
It will be very messy if you are using this line of code
self.window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
It means every time you are creating new instance of UIWindow which will contain UIViewController and all other component each time. Which means when you go to background and comes to foreground the old window instance has been flushed off and it will contain new components that's why when you click on the UITextField it has been deallocated, The reason you are getting error. Don't create new instance of window and use the code as #PartiallyFinite does.
Hope this helps.

Display one thing on iPad and another on Apple Tv?

I have an app idea, but I'm not sure if it's possible.
I was wondering if I'm able to display one thing on the iPad ( or iPhone )
screen, and something totally different on the Apple Tv at the same time.
For example, a quiz app, where the question is displayed on the Apple Tv, and the multiple choices are listed on the iPad for the user to pick.
I'm not sure if this is possible or if you can only Mirror the iPad screen onto the Apple Tv.
If there is some "Proof of Concept" example code, I'd love to take a look.
Thank you so much.
Chris
Turns out that is is pretty simple to support two screens: the primary screen of the iOS device and a secondary screen (either an external display or mirroring on an Apple TV).
Based on information from the blog post Creating a Dual-Screen AirPlay Experience for iOS and Apple TV, you don't need to do much.
Basically you need to check the screens property from UIScreen. There are also notifications you should listen for (UIScreenDidConnectNotification and UIScreenDidDisconnectNotification) so you know if the number of screens changes while your app is running.
Once you have a second screen, you need to create a new window for it. Code like the following can be used:
if ([UIScreen screens].count > 1) {
if (!_secondWin) {
UIScreen *screen = [UIScreen screens][1];
_secondWin = [[UIWindow alloc] initWithFrame:screen.bounds];
_secondWin.screen = screen;
}
}
where _secondWin is a UIWindow ivar.
Once the window is setup, create a view controller, make it the window's root view controller, and show the window:
SomeViewController *vc = [[SomeViewController alloc] init...];
_secondWin.rootViewController = vc;
_secondWin.hidden = NO;
This is pretty much it other than proper handling of the notifications. Keep in mind that you can't get any touch events on the 2nd display so make sure whatever you show is basically display-only.
Depending on your app, you might have the 2nd screen/window being used throughout the lifetime of the app (as long as the 2nd screen is available any way). Or you might only create and use the 2nd window/screen under certain circumstances. When you don't setup the 2nd window/screen, your app will simply be mirrored to the 2nd display or Apple TV.
The last piece is to turn on mirroring to the Apple TV. This is done on the iOS device, not in the app.
The blog post I linked has a few more details worth reviewing.

For iOS when would an App use more than one Window?

In the view programming guide for iOS, it states "Every iOS application needs at least one window—an instance of the UIWindow class—and some may include more than one window."
What are some examples Apps that would need more than one window?
Thanks
Apps that require to output video to a second screen might use more than one window.
Here you have a question about that particular topic.
You could also use more than one window to achieve other objectives, but that is not recommended by Apple. In general if you see that you need 2 windows or more, I'd suggest that something is wrong with your approach.
I've played around with 2 windows to integrate cocos2d and uikit in a test project, the code was pretty clean and the idea was to switch between windows, using the visibility and the key window, as necessary. It worked, but sometimes when sending the app to background, for some magical reason the active, key window would be made invisible.
Afaik the only case you'd need more than one window is if you connect another screen like a TV to your device. In that case you could provide a totally independent UI for the second screen. F.e. the Keynotes app on iPad does that when you connect another screen to the device.
You could register for the UIScreenDidConnectNotification and handle it like this:
- (void)screenDidChange:(NSNotification *)notification
{
if ([UIScreen screens] count] > 1)
{
UIScreen *extScreen = [[UIScreen screens] objectAtIndex:1];
UIWindow *extWindow = [[UIWindow alloc] initWithFrame:[extScreen bounds]];
//add some subviews to the window
extWindow.screen = extScreen;
[extWindow makeKeyAndVisible];
}
}

Resources