I am utilizing a split view controller when the user can select an item out of the master view and drag it over to the detail view and place it. While the user is dragging, a UIView appears at the bottom of the detail view where they can drag the item if they change their mind mid-drag and want to remove it. I am having troubles detecting when the touch is in the UIView to remove. Here is what I have so far:
Code that is executed when the user selects the item in the master controller.
MasterViewController:
// Create pointer to window
UIWindow *window = [UIApplication sharedApplication].delegate.window;
// Create draggable view
self.draggableImageView = [[UIImageView alloc] initWithImage:imageView.image];
// Adapt to orientation
[self.draggableImageView rotateToOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
// Hide detail calloutview if there is one
[self.detailViewController hideCalloutView:NO];
// Add drag-view to window
CGPoint location = [gesture locationInView:gesture.view.window];
[self.draggableImageView setCenter:location];
[window addSubview:[self draggableImageView]];
// Animate the marker to jump up
[UIView animateWithDuration:0.10
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
// Move the marker up
[self.draggableImageView setCenter:CGPointMake(location.x + 75.0f, location.y)];
} completion:^(BOOL finished) {
}];
// Show our remove marker view
[self.detailViewController showRemoveMarkerView];
While the user is dragging, a method in the detail view controller is being called with the updated point and this is the code within that method:
- (void)pointForMarkerDidChange:(UIImageView *)imageView atPoint:(CGPoint)point
{
NSLog(#"Point for Marker Did Change, point = %#", NSStringFromCGPoint(CGPointMake(point.x - 75.0f, point.y)));
UIWindow *window = [UIApplication sharedApplication].delegate.window;
CGRect removeMarkerFrame = [window convertRect:self.removeMarkerView.frame fromView:self.view];
if (CGRectContainsPoint(removeMarkerFrame, point)) {
NSLog(#"YES");
}
}
EDIT: I changed up the following code and it works now.
- (void)pointForMarkerDidChange:(UIImageView *)imageView atPoint:(CGPoint)point
{
UIWindow *window = [UIApplication sharedApplication].delegate.window;
CGRect removeMarkerFrame = [self.removeMarkerView convertRect:self.removeMarkerView.round.frame toView:self.view];
CGPoint point2 = [window convertPoint:point toView:self.view];
if (CGRectContainsPoint(removeMarkerFrame, point2)) {
NSLog(#"YES");
}
}
Related
I've got a custom UIStoryboardSegue subclass which just replaces the root view controller with the destination VC. Works exactly as I want it to... however, I'd like to be able to add a transition animation, and I can't find any good examples of how to do that in the context of replacing the root VC.
The -perform selector of my class is this:
-(void)perform {
UIViewController *source = (UIViewController *)self.sourceViewController;
source.view.window.rootViewController = self.destinationViewController;
}
...How do I add a nice animated transition?
There are numerous ways you could add a "nice animation". Here is an example of a sort of card shuffle animation where one view moves up and left, while the other moves down and right, then reverses after changing the z-order of the two views. This implementation inserts the destination controller's view into the window's view hierarchy under the source controller's view.
-(void)perform {
CGFloat dur = 1.0;
UIView *destView = [self.destinationViewController view];
UIView *srcView = [self.sourceViewController view];
CGFloat viewWidth = srcView.frame.size.width;
CGPoint center = srcView.center;
AppDelegate *appDel = [[UIApplication sharedApplication] delegate];
destView.frame = srcView.bounds;
[appDel.window insertSubview:destView belowSubview:srcView];
[UIView animateWithDuration:dur animations:^{
srcView.frame = CGRectOffset(srcView.frame, -viewWidth/1.9, -20);
destView.frame = CGRectOffset(destView.frame, viewWidth/1.9, 20);
} completion:^(BOOL finished) {
[appDel.window bringSubviewToFront:destView];
[UIView animateWithDuration:dur animations:^{
destView.center = center;
srcView.center = center;
} completion:^(BOOL finished) {
[srcView removeFromSuperview];
appDel.window.rootViewController = self.destinationViewController;
}];
}];
}
I am doing app related to custom alert. Written code for displaying alert view with transparent background when i click ok button on alert-view an alert disappears.
I need help for same thing happen while i touch the transparent view also my code is a below
:
- (void)didCustomPopUpAlertLoad:(UIView *)parentView andtitle:(NSString *)strTitle {
[self setRootView:parentView];
self.lblAlertMessage.text = strTitle;
//Add alertview into transparent view to hide parent view interaction
UIView *transparentView = [[UIView alloc] initWithFrame:parentView.bounds];
[transparentView setBackgroundColor:[UIColor clearColor]];
[transparentView addSubview:self];
float x = (int)(transparentView.bounds.size.width - self.bounds.size.width)>>1;
float y = (int)(transparentView.bounds.size.height - self.bounds.size.height)>>2;
[self setFrame:CGRectMake(x, y+62, self.bounds.size.width, self.bounds.size.height)];
// [self setFrame:CGRectMake(x+10, y+62, self.bounds.size.width, self.bounds.size.height)];
[self.window addSubview:transparentView];
[self.window makeKeyAndVisible];
[[transparentView subviews]
makeObjectsPerformSelector:#selector(setUserInteractionEnabled:)
withObject:[NSNumber numberWithBool:FALSE]];
}
-(void)didCustomPopUpUnload{
[self.superview removeFromSuperview];
// Set up the fade-in animation
self.window = nil;
}
-(IBAction)didActionOkAlertPopUp:(id)sender{
[self didCustomPopUpUnload];
}
Create a custom transparent view then override this method
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Add your alert view into this view and use this view as full screen view.
You can use event param to calculate to know user touch inside or outside of alertview.
OK, I get the concept of adding a UIImageView as soon as the app launches, and animating it out to fake the splash animation. However, the status bar interferes.
I need a UIImageView on top of everything, including the status bar, and while it fades away, the app is shown with the status bar. Hence, settings the Status bar initially hidden, then animating it in is not a viable option.
What you require is a second UIWindow with a windowLevel of UIWindowLevelStatusBar or higher. You would create two UIWindow objects in your application delegate, one with your regular view hierarchy, the other with the image, and animate the second to fade out (or however else you need to animate). Both windows should be visible, with the splash window being on top.
This approach is complicated, as you might have problems with rotation, depending on your regular view hierarchy. We've done this in our software, and it works well.
EDIT:
Adapted Solution (window approach, very simple):
UIImageView* splashView = [[UIImageView alloc] initWithImage:[UIImage imageWithBaseName:#"Default"]];
[splashView sizeToFit];
UIViewController* tmpVC = [UIViewController new];
[tmpVC.view setFrame:splashView.bounds];
[tmpVC.view addSubview:splashView];
// just by instantiating a UIWindow, it is automatically added to the app.
UIWindow *keyWin = [UIApplication sharedApplication].keyWindow;
UIWindow *hudWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0.0f, -20.0f, keyWin.frame.size.width, keyWin.frame.size.height)];
[hudWindow setBackgroundColor:[UIColor clearColor]];
[hudWindow setRootViewController:tmpVC];
[hudWindow setAlpha: 1.0];
[hudWindow setWindowLevel:UIWindowLevelStatusBar+1];
[hudWindow setHidden:NO];
_hudWin = hudWindow;
[UIView animateWithDuration:2.3f animations:^{
[_hudWin setAlpha:0.f];
} completion:^(BOOL finished) {
[_hudWin removeFromSuperview];
_hudWin = nil;
}];
Finally, credit goes to this guy.
A more simple approach would be to launch your application with the status bar hidden, have the view you would like to animate on the top of the view hierarchy, and after the animation is finished, display the status bar using [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]
You can add a subview to the App's UIWindow. Set the Y coordinate of the UIImageView's frame to -20 pixels in order to handle the status bar.
Add the Default PNG image to your window and tag it:
static const NSInteger kCSSplashScreenTag = 420; // pick any number!
UIImageView *splashImageView;
// Careful, this wont work for iPad!
if ( [[UIScreen mainScreen] bounds].size.height > 480.0f ) // not best practice, but works for detecting iPhone5.
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default-568h"]];
}
else
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
}
splashImageView.frame = CGRectMake(0.0f, -20.0f, splashImageView.image.size.width, splashImageView.image.size.height);
splashImageView.tag = kCSSplashScreenTag;
[self.window addSubview:splashImageView];
[splashImageView release];
[self _fadeOutSplaceImageView];
Then fade it out
- (void)_fadeOutSplashImageView
{
UIView *splashview = [self.window viewWithTag:kCSSplashScreenTag];
if ( splashview != nil )
{
[UIView animateWithDuration:0.5
delay:0.0
options:0
animations:^{
splashview.alpha = 0.0f;
}
completion:^(BOOL finished) {
[splashview removeFromSuperview];
}];
}
}
So I am usually follow this way to dismiss ModalViewController when tap outside its view
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if(UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM())
{
if(![self.view.window.gestureRecognizers containsObject:recognizer])
{
recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];
}
}
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
[self dismissModalViewControllerAnimated:YES];
[self.view.window removeGestureRecognizer:recognizer];
}
}
}
its working good with regular ModalViewController .. but now I have a modelViewController which is a NavigationViewController with "x" viewController as the root one .. when I use this way and tap on the navigationBar the controller is dismissed , or when I navigate to "y" controller from x controller and want to get back , also when I click the back button the ModalView is dismissed ! which is wrong behavior to me .. I just want the controller to be dismissed in case the tap was completely outside the controller (view area + navigationBar area) .. any one can provide a help please ?
Update your code following:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint flocation = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
CGPoint tap = [self.view convertPoint:flocation fromView:self.view.window];
CGPoint tapBar = [self.navigationController.navigationBar convertPoint:flocation fromView:self.view.window];
if (![self.view pointInside:tap withEvent:nil] && ![self.navigationController.navigationBar pointInside:tapBar withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Remove UITapGestureRecognizer from window when tap on UIBarButtonItem
-(void)viewWillDisappear:(BOOL)animated {
[self.view.window removeGestureRecognizer:self.tapOutsideGestureRecognizer];
}
In order to use a UISplitViewController, I'm replacing my window root controller when navigating from one view controller to the other.
In order to have some nice transition while doing so, I'm using a zooming effect like this:
MyOtherViewController *controller = [[MyOtherViewController alloc] initWithNibName:#"MyOtherView" bundle:nil];
UIWindow *window = ((MyAppDelegate *)[[UIApplication sharedApplication] delegate]).window;
controller.view.frame = [window frame];
controller.view.transform = CGAffineTransformMakeScale(0.01,0.01);
controller.view.alpha = 0;
[window addSubview:controller.view];
[UIView animateWithDuration:0.2 animations:^{
controller.view.transform = CGAffineTransformMakeScale(1,1);
controller.view.alpha = 1.0;
} completion:^(BOOL finished) {
if (finished) {
[self.view removeFromSuperview];
window.rootViewController = controller;
}
}];
and this works pretty well, except that while doing the animation, the new view is always oriented as if in portrait mode, regardless of the current device orientation. When the animation is finished, the view orients itself correctly.
What am I missing?
Things I've tried:
putting my new controller view as the sole subview of the UIWindow
making my new controller the root view controller before the animation begins
A curious thing is that, if I do a recursiveDescription on the window at the beginning of my method, the window frame is defined as having a size of 768x1024 (i.e., portrait), and the view inside it of 748x1024 but with a transform of [0, -1, 1, 0, 0, 0] (does this mean a rotation or what? Shouldn't it be the identity transform?)
UIWindow doesn't rotate. It has a rotated view inside of it (as you've seen). In this case, though, I think the problem is likely that your view has a transform on it already at this point, and you need to concatenate with it rather than replace it as you're doing in your setTransform: calls.
You shouldn't be asking the app delegate for the window, you should be getting the window from the view (self.view.window).
If at any point you're attaching your view to the window itself, rather than putting it inside the rotation view, then you'll need to know the effective transform of the view you want to match by walking the hierarchy:
- (CGAffineTransform)effectiveTransform {
CGAffineTransform transform = [self transform];
UIView *view = [self superview];
while (view) {
transform = CGAffineTransformConcat(transform, [view transform]);
view = [view superview];
}
return transform;
}
I finally figured out what was wrong. Since the frame is not a real property but a sort of calculated one, based on the values of the view bounds and the view transform, I needed to set the frame after setting the same transform as the current view, and before setting the transform again to set up the initial state of the animation. Also, the frame I need to set is the same one as the current view is currently using, as it is taking into account the window orientation (or lack thereof, as Rob Napier pointed)
So, without further ado, here's the working code:
MyOtherViewController *controller = [[MyOtherViewController alloc] initWithNibName:#"MyOtherView" bundle:nil];
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGAffineTransform t = self.view.transform;
controller.view.transform = t;
controller.view.frame = self.view.frame;
controller.view.transform = CGAffineTransformScale(t,.01,.01);;
[window addSubview:controller.view];
controller.view.alpha = 0;
[UIView animateWithDuration:0.2 animations:^{
controller.view.transform = t;
controller.view.alpha = 1.0;
} completion:^(BOOL finished) {
if (finished) {
[self.view removeFromSuperview];
window.rootViewController = controller;
[controller release];
}
}];