Covering window of UIAppDelegate with an overlay make user can't touch on extensions of UIActivityViewController when it's displayed - ios

I'm creating an overlay which will cover all displaying views on screen. This overlay always appears even in case rootViewController changes, pushing or presenting.
My idea is
Create CustomWindow which is a subclass of UIWindow. After that replacing default window of UIApplication with CustomWindow, create a new rootViewController for my new window.
In CustomWindow, I have an overlay (is an UIView). Overlay have light gray color with an alpha and every event on overlay will be pass through to below view.
Whenever CustomWindow add a new subview, i will bring overlay to front. It's make sure overlay will be on the top in every case.
CustomWindow
#implementation CustomWindow
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_overlay = [[UIView alloc] initWithFrame:self.bounds];
_overlay.userInteractionEnabled = NO;
_overlay.backgroundColor = [UIColor lightGrayColor];
_overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_overlay];
}
return self;
}
- (void)didAddSubview:(UIView *)subview {
[super didAddSubview:subview];
[self bringSubviewToFront:_overlay];
}
#end
Everything works fine in every case even when pushing, presenting or changing rootViewController.
Problem
But when i show an UIActivityViewController, I can't click on any extensions which are displayed on UIActivityViewController.
Magically
When i click outside of UIActivityViewController or click on Cancel Button, UIActivityViewController is dismissed normally.
If i change color of overlay to clearColor, it works fine too.
My question is
How can i touch on extensions when i have overlay on window and overlay have a color ?
If i can't, can anyone tell me why it happens ? It's perfect when you can quote the reason from a document.
I'm pretty sure this doesn't relate to how i initialize UIActivityViewController or the way i show UIActivityViewController.
MORE
I found a problem quite similar to this problem on Android. But i'm not sure because i haven't seen any official document about it from Apple. One more thing is when changing color to clearColor can affect touch. So actually, i don't think they are same.

This is due to a UIRemoveView (private) in the hierarchy. As best I can determine, your app cannot forward events directly to remote views. I suspect this is a security measure to prevent you from presenting the share dialog and automatically sending a touch event to it to do an external action the user didn't request. Remote views don't run in your application's process. The "Copy" button is interacted with across an XPC link.
This all means that if the remote view is covered by one of your views, there's no way (at least that I've found) to interact with it. You have to ensure that you're not covering it.
Actually doing that is simple. The thing that holds the remote view is called a UITransitionView and is used for other OS-level things that you probably shouldn't be covering either. So don't:
- (void)didAddSubview:(UIView *)subview {
[super didAddSubview:subview];
if ([subview isKindOfClass:NSClassFromString(#"UITransitionView")]) {
// To raise it above your overlay;
// otherwise it's immediately above the view controller (below the overlay)
[self bringSubviewToFront:subview];
} else {
[self bringSubviewToFront:self.overlay];
}
}
But.... This requires you to talk about UITransitionView in your code. This is both fragile, and possibly a forbidden use of private APIs.
Otherwise you'll have to wrap your UIActivityViewController requests with some call/notification that tells the window not to cover views until we're done (which you'll have to clear in the completion handler).

Related

How can I assign a pointer to the keyboard before I have assigned first responder

I am trying to create a user interface enabling users to switch between the keyboard and other menus when using a chat application.
On a click of the textField bar I want to raise either the keyboard or a collection view.
The problem occurs when I click the 'menu' button. I want the textField bar to raise revealing my menu view. Then, on a click on the keyboard button, instantly switch to the keyboard, rather than having it slide up from the bottom. This means I need to have the keyboard already loaded and hidden but in the background of the app.
Currently though the earliest I am managing to assign a variable to the keyboard is in the keyboardDidShow function.
-(void) keyboardDidShow: (NSNotification *) notification {
// Get the window the keyboard is a subview of
_window = [UIApplication sharedApplication].windows.lastObject;
_keyboard = _window.subviews[0];
}
This means that after it has been loaded once I can hide and reveal it, but I don't want it visible when it is loading this first time.
To achieve this using alternate means I have tried adding my extra views as subviews of the UIWindow the keyboard is created in:
[_window addSubview:_menuView];
[_window addSubview:_gamesView];
[_window addSubview:_stickerView];
[self hideSpecificView];
Unfortunately I keep coming across the same problem, until I have loaded the keyboard once it needs to fully load before I can get a pointer to it to hide it.
Here is a picture of my toolBar incase I am not being clear:
On clicking the menu icon or the stickers icon I want the bar to raise with a collection view. If I then click the textfield, with these views visible, I want to hide the visible view to immediately show the keyboard behind.
I have also tried experimenting with keyboardWillShow but as the window hasn't been loaded in front our screen I can't get a pointer to the keyboard to hide it before it loads.
An example of what I am after can be found many chat apps (facebook messenger, LINE, Kakao Talk)
Any help would be greatly appreciated
Although the way I came up with isn't perfect it works almost perfectly so hopefully this might help people in the future. If anyone else has solved it differently please post as it would be interesting to know how you did it.
I started by adding a class variable to a UIWindow in my header file and then setting off a timer to ping just after the keyboard will show method finishes. After this method has finished the keyboard has been created, just, and so I allocate it and hide it.
-(void) keyboardWillShow: (NSNotification *) notification {
// More keyboard code
_window = [UIApplication sharedApplication].windows.lastObject;
[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(allocateKeyboard)
userInfo:nil
repeats:NO];
}
- (void)allocateKeyboard {
if (!_keyboard) {
_keyboard = _window.subviews[0];
}
_keyboard.hidden = YES;
[self setViewForButtonType];
}
I have already previously added my other views, hidden them and constrained them to the bottom of the main view, this means that when the keyboard rises they do too.
- (void)viewDidLoad {
[self.view addSubview:_menuView];
[self.view addSubview:_gamesView];
[self.view addSubview:_stickerView];
}
...
- (void)hideViews {
_keyboard.hidden = YES;
_menuView.hidden = YES;
_gamesView.hidden = YES;
_stickerView.hidden = YES;
}
When buttons get pressed I simple then unhide the view that I want to see and hide the rest of the views.
When I say that this method doesn't work perfectly it is because if you load view main view and then click a button before the keyboard has loaded for the first time then you get a quick glimpse of the keyboard before the view appears over the top. This though only happens the first time and only if they don't click in the text field first.
Anyway, I found this was the best way of making views look like they are in front of the keyboard. Obviously my code was a lot longer and more complex (too long for here) but this is the gist of the method I used to solve it. Comment if you have any queries and I hope this helps.

How to reset or restart root view of UIViewController to its original state?

I am new to iOS Development. I have a very simple UIViewController, with two button in the view. I am allowing users to edit UI components or drag and drop from one place to another. Now I want to implement reset functionality allowing users to reset UI to its original stage discarding all the changes.
As mentioned in several post tried setNeedsDisplay but looks like its not right way to achieve this. What are the my options ?
e.g. In Android world, you can finish current Activity (Equivalent of UIViewController) and relaunch self to achieve original state. Is it possible to do something like it in iOS.
- (IBAction)resetButtonClicked:(id)sender {
// Now I want discard all the changes on the screen
// On some button click or specific action from user.
// Tried calling setNeedsDisplay as pointed in few others answers but somehow
// setNeedsDisplay does not reload my ViewController.
// [self.view setNeedsDisplay];
}
I have done something similar. What I did was:
1) create local CGRect variables for each frame ui control i.e. CGRect originalFrame1;
2) get the original frame position for each ui control and store them on viewDidLoad i.e. originalFrame1 = _frame1.frame;
3) on the resetButtonClicked set it back to is original position _frame1.frame = originalFrame1;
I can think of two options:
1- Save the positions of your initial state, and when the user clicks the reset button, just put those buttons on their initial states.
2- OR (strange way of doing it), you can initialise the same controller you are in and just present it from itself, that should be somehow close to your android approach. You'll get the same controller on its initial state (If you didn't didn't dismiss the first one, you should)
Hope that helps :)
u can save the positions for all subviews(buttons,label,...) in the main view.
self.view.subview it will return array contain all elements u have it :)
and when u wanna rest your view just get the old position for each subview from the array.
NSArray *arrayOfView = [view subviews];
//also:
NSMutableArray *mutableArrayOfView = [[view subviews] mutableCopy];
for (UIImageView *imageView in imageViewArray) //uiimageview or whatever your subview //is you can check by using isKindofClass. `if ([view isKindOfClass:[UIImageView class]])`
{
//let's set frame for image view before adding to
[myimageView setFrame:imageview.frame];//or something like that
}
EVery app will have a keyWindow object for which you should assign some viewController to display.
window.rootViewController = [[UIViewController alloc] init];
Lets say your controller's class is CustomViewController
So, in your app delegate,didFinishLaunchingWithOptions, you will write
self.window.rootViewController = [[CustomViewController alloc] init];
And the function invoked when you click reset button be as below,
-(void)reset{
// reset the view with default
self.view.window.rootViewController = [[CustomViewController alloc] init];
}
If you follow above code, it won't have any memory leaks because ,when you set rootViewController again ,the old one will be lost and removed from memory.

Set background image for entire iOS app

I'm trying to set a background image for the entire app following this suggestions: set background image for entire iPhone / iPad app
But in iOS 7 (don't know about other versions) it doesn't work well at all.
I've created a simple repository so you can understand better what's is going on.
There are some glitches in the transitions.
When you tap on a row in the first view, the second view is pushed into the navigation controller but there's a weird effect. It seems that the rows transparency played into this.
Also the other problem is when you turn back to the previous view controller there's a subtle shadow of the view controller that is popped from the navigation stack. As I stated before you can get what I mean by running the simple Xcode project.
Repo: https://github.com/socksz/FixedBackgroundImage
Any ideas? I've already tried to set the background image for each controller but it isn't what I want because in that way the image "overlaps" the previous background image and it's not the desired effect.
Hope I explained well.
EDIT 1
It seems that the problem occurs because of the way iOS 7 manages the transitions between two view controllers. In you are in the second view controller and try to turn to the previous controller with the swipe gesture you can see that as you begin the gesture the first controller appears below the second controller (the controller you're seeing) and, since the UITableViewCells have transparent backgrounds, you already see the first controller. Actually I'm afraid that there's not a solution. What a pity that I cannot have a fixed background image without setting the background image on each controller.
I had a requirement in an iPhone app to set the background image of a page based on a user's preferences. The way I dealt with it was to add a UIImageView with the background image as a sub-view of the view, like so -
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"background-image"]];
bgImageView.frame = self.view.bounds;
[self.view addSubview:bgImageView];
[self.view sendSubviewToBack:bgImageView];
I cloned your Github repository and added the above piece of code in viewDidLoad of both the view controllers. I also added the following line of code in the same method -
self.tableView.opaque = NO;
I commented out the code in didFinishLaunchingWithOptions where you set the background color. With these changes, the artifacts while navigating between view controllers are gone. I tested with iPhone Retina (3.5-inch) as well as iPhone Retina (4-inch) simulators.
The reason why the artifacts are seen while navigating to and from the ViewController in the storyboard require some investigations. My suggestion may or may not work for your requirements, but, you can try this as a solution.
P.S. The method requires some tweaks to autolayout constraints.
You Just have to write only one line in
appdelegate.m file's applicationdidFinishLaunchingWithOptions: method
[self.window setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"MainBackground.png"]]];
and put below line in every screen's viewDidLoad method
[self.view setBackgroundColor:[UIColor clearColor]];
I did not find a way to put this globally. However, and you will probably find this useful for static/fixed images (instead of the moving images you get when you set the backgroundColor property), Use the backgroundView property for every screen.
self.tableView.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"background.jpg"]];
I did it myself by creating a UtilTableViewController which does all theme and custom things I need it to do, putting this code there, then subclassing all my views. It's not a globally set image, but I only have to set it once and all of my TableViews will use it.
Put this code in your appdelegate.m file applicationDidFinishLaunching method.
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:rootViewController];
windowBackground=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"background_window.png"]];
windowBackground.frame=CGRectMake(0, 0, 320, 568);
[window addSubview:windowBackground];
[windowBackground release];
window.frame = CGRectMake(0, 0, window.frame.size.width,568);
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
Add this code in every viewController class viewDidLoad method.
self.view.backgroundColor = [UIColor clearColor];
Late post...
If you are using a NavigationController you might try overriding the TopViewController "get" portion to automatically set the BackGroundColor to your image. Appologies, we use Xamarin which converts from C# to objective C for us (not sure of the specific syntax). In C# it will look something like this within your NavigationController class.
public override UIViewController TopViewController
{
get
{
if (base.TopViewController.View.BackgroundColor != "Your Image")
{
base.TopViewController.View.BackgroundColor = "Your Image";
}
return base.TopViewController;
}
}
Write this code in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"YourImageName.png"]];
self.window.backgroundColor = background;
you can set the background image through below code ... wew can put this code in viewdidload method in viewcontroller.m file
[self.window setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"bg.png"]]];

How to add a title over MPMovieplayerViewController and hide it on tap

I need to add a title over the top toolbar in MPMoviePlayerViewController, and if am playing a video, a user tap should hide the title just like it hide any other controls.
Currently I am adding a UILabel as a subview of moviePlayer view. Though this approach adds a title over the topbar (I am setting the frame accordingly), it does not hide the title when user taps over the screen.
Is there any direct api/hack through which I can get access to the top toolbar of MPMoviePlayerViewController? I am thinking like, if I can add the title as a subView of top toolbar, hiding process will be handled by MPMoviePlayerViewController. Any thoughts?
Thanks in advance!
Note that my answer is referring to MPMoviePlayerController, not MPMoviePlayerViewController. I would advise you to use it either directly or via its reference on MPMoviePlayerViewController.
First of all, never ever add anything directly onto MPMoviePlayerController's view. That is prone to have all kinds of weird side-effects and is clearly advised against in the documentation. Instead use its backgroundView for hosting your custom stuff or the parent of MPMoviePlayerController's view (making it a sibling).
But then again, that won't solve the issue you describe with having that view/label/whatever dis/reappear together with the controls if the user taps or simply waits.
There are ways to do that, but I am afraid those are in the grey-zone - or in other words, have a chance of getting rejected by Apple. In fact its not just a grey-zone but a clear violation of Apple's development guidelines.
Just, I have used this trick in the past on major apps and never got detected/rejected.
First, you need to locate the interface-view of your MPMoviePlayerController instance.
/**
* This quirky hack tries to locate the interface view within the supposingly opaque MPMoviePlayerController
* view hierachy.
* #note This has a fat chance of breaking and/or getting rejected by Apple
*
* #return interface view reference or nil if none was found
*/
- (UIView *)interfaceView
{
for (UIView *views in [self.player.view subviews])
{
for (UIView *subViews in [views subviews])
{
for (UIView *controlView in [subViews subviews])
{
if ([controlView isKindOfClass:NSClassFromString(#"MPInlineVideoOverlay")])
{
return controlView;
}
}
}
}
return nil;
}
UIView *interfaceView = [self interfaceView];
Now that you got the instance, simply add your view/label/whatever onto that view.
[interfaceView addSubview:myAwesomeCustomView];

Add clickable and fixed subview to UITableViewController?

I'd like to place an ADBannerView object onto my UITableView screen statically, what means that I want it to always stay above my toolbar (self.navigationController.toolbar), even when the user is scrolling the tableview. I've solved this by adding by ADBannerView as a subview to my toolbar and given it negative values for the frames origin:
[self setBannerViewSize];
[self.navigationController.toolbar addSubview:bannerView];
The only problem is: I can't click and open the iAd this way - I can see the banner but nothing happens when I tap on it.
Since I'm also using a refreshControl, the option to use a UIViewController instead of UITableViewController and add a tableView manually wouldn't work for me. Is there any other way I can get my ADBannerView statically showing in my table view controller AND still being tappable?
Thank you in advice!
Yay!! After all I succeeded in solving this (really annoying) problem by myself (and a lot of reading around)!
First, I found this really world-changing post. Basically this post handles with the topic that a UITableViewController uses self.view for its tableView property, so overriding the tableView property (or synthesizing it manually) plus giving self.view a new view (from application) and adding tableView as its subview would make it possible to reach the real superview of tableView.
But this still didn't solve my problem, although I was sure it would, because it all made sense. My bannerView appeared in the right place (and was fixed) but it still didn't do anything when clicked. But there was a second minor thing I didn't know about:
As I read in this post the superview of a subview doesn't only have to be userInteractionEnabled but also have a non-transparent backgroundColor. Because my superviews background color was set to [UIColor clearColor] it all didn't work - but setting its backGroundColor to e.g. blackColor solved the whole problem: the bannerView got finally tappable! :)
So, my code is now looking like this:
#synthesize tableView;
- (void)viewDidLoad
{
[super viewDidLoad];
if (!tableView && [self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
[self resizeTableToFitBanner];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:bannerView];
// some other code
}
BannerViewController in Apple's iAdSuite sample code solves this problem very elegantly:
https://developer.apple.com/library/ios/samplecode/iAdSuite/Introduction/Intro.html
I think you should use a container view, and set things up in IB. You can add a tool bar and ADBannerView to the bottom of the view of your navigation controller's root view controller. Fill the rest of the space with a container view - this will give you an embedded view controller automatically. You should delete this one and then drag in a tableViewController and control drag from the container view to the tableViewController to hook up the embed segue.

Resources