My situation is a little more complicated than the others listed.
I have a UITableView that takes up most of the screen.
Each row pops up a subview that contains more profile information. When the screen is clicked again this subview disappears. This works perfectly.
In the Navigation Bar I have a button that will display a small menu.
- (IBAction)menuButtonClicked:(UIBarButtonItem *)sender {
//If menuView exists and Menu button is clicked, remove it from view
if (self.menuView) {
self.tableView.userInteractionEnabled = true;
[self.menuView removeFromSuperview];
self.menuView = Nil;
}
//Menu View doesn't exist so create it
else {
// Create the Menu View and add it to the parent view
self.menuView = [[[NSBundle mainBundle] loadNibNamed:#"MenuView" owner:self
options:nil] objectAtIndex:0];
self.menuView.layer.cornerRadius = 20.0f;
self.menuView.layer.borderWidth = 3.0f;
self.menuView.layer.borderColor = [UIColor whiteColor].CGColor;
self.menuView.frame = CGRectMake(0, 64, self.menuView.frame.size.width,
self.menuView.frame.size.height);
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(singleTapGestureCaptured:)];
[self.menuView addGestureRecognizer:singleTap];
//Disable Selection of Profiles while Menu is showing
self.tableView.userInteractionEnabled = false;
//Add MenuView to View
[self.view addSubview: self.menuView];
}
}
//Removed Sub Views from View when tapped
-(void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture{
if(self.profileView){
[self.profileView removeFromSuperview];
self.profileView = Nil;
}
if(self.menuView) {
self.tableView.userInteractionEnabled = true;
[self.menuView removeFromSuperview];
self.menuView = Nil;
}
}
Now I want to dismiss this menus if the menu button is clicked again (working in above code) but also when the user touches out of the menu and on the tableView or navbar. If the menu is displayed, I don't want the tableView to display it's profile subview (working in above code) but just remove the menuView. I can't get the menuView to go away if I touch the tableView.
Can anyone point me in the right direction?
Make a new transparent overlay view sized to cover the entire screen. Add your menuView as a subview of the overlay, then add the overlay as a subview of your main window. Put a tap gesture recognizer on the overlay that will dismiss it when tapped.
You may need to set cancelsTouchesInView to NO on your gesture recognizer if buttons on your menu view are not working.
Roughly this (please excuse typos, I haven't compiled this):
- (void)showMenu
{
self.overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
overlay.backgroundColor = [UIColor clearColor];
self.menuView = /* code to load menuView */;
[overlay addSubview:self.menuView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(onSingleTap:)];
tap.cancelsTouchesInView = NO;
[overlay addGestureRecognizer:tap];
[self.tableView.window addSubview:overlay];
}
- (void)handleSingleTap:(UITapGestureRecognizer *)sender
{
[self.overlay removeFromSuperview];
}
You might also want to add a swipe gesture recognizer to also dismiss the overlay, as someone may attempt to scroll the table expecting the menu to be dismissed.
While trying to make my own custom topdown-slide menu using my own custom NIB file, I found that this can be achieved by many techniques. I would like to suggest and share a different solution which is very similar but is created with a custom button on the background.
I've been looking around but could not find answers mentioning this.
This is very similar to the tap recogniser except for one thing - tap recogniser spreads all over the layout (including subviews), while using a layer of custom button allows you to interact with the top view and dismiss/ remove it from superview when clicking on lower layer (when lower layer is the background button). This is how I did it:
You create the layout
You add a UIButton with type UIButtonTypeCustom to the layout
You frame this layout over the view you wish to be responsive to that tap/click
You add your menu view on top of that layout and animate your menu to appear
- (void)showMenuViewWithBackgroundButtonOverlay
{
self.backgroundButton = ({
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.frame;
[button addTarget:self action:#selector(toggleAppMenu) forControlEvents:UIControlEventTouchUpInside];
button;
});
if (!self.menu) {
self.menu = [self createMenu]; // <-- get your own custom menu UIView
}
if (!self.overlay) {
self.overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.overlay.backgroundColor = [UIColor clearColor];
[self.overlay addSubview:self.backgroundButton];
[self.overlay addSubview:self.menu];
[self.view addSubview:self.overlay];
}
[self toggleAppMenu];
}
And the toggleAppMenu:
- (void)toggleAppMenu
{
CGRect nowFrame = [self.menu frame];
CGRect toBeFrame = nowFrame;
CGFloat navHeight = self.navigationController.navigationBar.frame.size.height;
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
if (self.showingMenu) {
toBeFrame.origin.y = toBeFrame.origin.y-nowFrame.size.height-navHeight-statusBarHeight;
[UIView animateWithDuration:0.5 animations:^{
[self.menu setFrame: toBeFrame];
}completion:^(BOOL finished) {
self.showingMenu = !self.showingMenu;
[self.view endEditing:YES];
[self.overlay removeFromSuperview];
self.overlay = nil;
NSLog(#"menu is NOT showing");
}];
}
else{
toBeFrame.origin.y = navHeight+statusBarHeight;
[UIView animateWithDuration:0.5 animations:^{
[self.menu setFrame: toBeFrame];
}completion:^(BOOL finished) {
self.showingMenu = !self.showingMenu;
NSLog(#"menu is showing");
}];
}
}
I hope this will be helpful for someone.
Works on Swift 5
I create custom view and I want it hide by tap outside subview. Maybe it can help for someone or anybody can suggest a better way :)
// create tap for view
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(animateOut))
self.addGestureRecognizer(tapGesture)
// create tap for subview
let tapGesture2 = UITapGestureRecognizer(target: self, action: nil)
container.addGestureRecognizer(tapGesture2)
Related
I've got a very strange problem with a UIWebView:
I have a function in my appdelegate which opens a UIWebView to show my privacy policy. The function gets called twice from two different views. Once if the user clicks a button on the login screen and once if the user clicks a button on a view, which lies on top a google map view.
When the function is called from the login view everything works fine and I can , but when the function is called from the other view, the UIWebView appears but is not complete operable (for testing I load google.com):
I cannot scroll and I cannot open most of the links, the only elements, which respond to touch events are marked in this picture:
And here comes my code:
In my appdelegate I have:
- (void)showPrivacy:(UIView *)view
{
overlay = [[UIView alloc] initWithFrame:view.frame];
overlay.backgroundColor = [UIColor blackColor];
overlay.alpha = 0;
[view addSubview:overlay];
privacyView = [[WPPrivacyView alloc] initWithFrame:CGRectMake(15, 40, view.frame.size.width-30, view.frame.size.height-75)];
privacyView.layer.shadowColor = [UIColor blackColor].CGColor;
privacyView.layer.shadowOffset = CGSizeMake(0, 1);
privacyView.layer.shadowOpacity = 0.75;
privacyView.layer.shadowRadius = 20.0f;
privacyView.userInteractionEnabled = YES;
privacyView.scrollView.scrollEnabled = YES;
privacyView.scrollView.bounces = NO;
privacyView.scalesPageToFit = YES;
[view addSubview:privacyView];
[UIView animateWithDuration:0.1 animations:^{
privacyView.alpha = 1;
}];
privacyView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1);
CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale"];
bounceAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.1],
[NSNumber numberWithFloat:1.1],
[NSNumber numberWithFloat:0.95],
[NSNumber numberWithFloat:1.0], nil
];
bounceAnimation.duration = 0.3;
bounceAnimation.removedOnCompletion = NO;
[privacyView.layer addAnimation:bounceAnimation forKey:#"bounce"];
privacyView.layer.transform = CATransform3DIdentity;
}
To hide the View I call:
- (void)hidePrivacy
{
[UIView animateWithDuration:0.3 animations:^{
privacyView.alpha = 0;
privacyView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1);
} completion:^(BOOL finished) {
[privacyView removeFromSuperview];
}];
}
The showPrivacy function gets called from the login view and from the map view as follows:
WPAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
[appDelegate showPrivacy:self.view];
So why is everything working fine, when the webview appears at the login view and why can I operate SOME elements but not all, when it appears at the map view?
Because you're adding your overlay view as a subview. This means the superview is going to get involved with user interaction and some events will get 'stolen' or blocked. In particular, scrolling, which uses gesture recognizers.
You may want to change your design so that you present a modal view controller with your overlay. If you want the background to be the original view then you could grab an image of it before you present the modal.
In my project i am having scroll at bottom and table at top.
Inside scrollview i am adding 10 buttons.for each button i had pan gesture to move around the screen.when the button intersect with table is will add a picture in tableview.
But the pan is working within the scroll.how to make it move over view
MY coding is:
downscroll=[[UIScrollView alloc]initWithFrame:CGRectMake(24, 635, 980, 100)];
downscroll.backgroundColor=[UIColor redColor];
downscroll.contentSize=CGSizeMake(990, 100);
[self.view addSubview:downscroll];
for(int i=1;i<=8;i++)
{
b1=[UIButton buttonWithType:UIButtonTypeCustom];
b1.frame=CGRectMake(30+px, 0, 80, 80);
[b1 setImage:[UIImage imageNamed: [NSString stringWithFormat:#"Icon%i.png",i]] forState:UIControlStateNormal];
[downscroll addSubview:b1];
// [self.view sendSubviewToBack:b1];
// [self.view bringSubviewToFront:b1];
[groupbutton addObject:b1];
panRecognizer3= [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(move:)];
[b1 setUserInteractionEnabled:YES];
b1.exclusiveTouch=YES;
img.tag=i;
[b1 addGestureRecognizer:panRecognizer3];
px=px+120;
}
When i add my buttons directly without scroll scrollview it works fine.But i need to add button scrollview
please help with proper code. Thanks in advance.
Here is the way I used in my application
In my code I follow the below steps
In UIPanGestureRecognizer starting point I add the button to the self.view from scrollView
Drag button by using the translationInView method
3.In UIPanGestureRecognizer end state I'll check if the button dropped in the destination View.If so do your required task else add the button at the same position in the scrollView
`MoveControl` is a UIPanGestureRecognizer method
- (void)MoveControl:(UIPanGestureRecognizer *)recognizer
{
UIButton *vew=(UIButton *)[recognizer view];
CGPoint newCenter = [recognizer translationInView:self.view];
if (recognizer.state==UIGestureRecognizerStateBegan) {
CGPoint point=vew.frame.origin;
[vew setTitle:NSStringFromCGPoint(point) forState:UIControlStateSelected];
CGRect rect=[self.view convertRect:[vew frame] fromView:[vew superview]];
[vew setFrame:rect];
[self.view addSubview:vew];
CGPoint point1=vew.frame.origin;
[vew setTitle:NSStringFromCGPoint(point1) forState:UIControlStateDisabled];
}
else
{
CGPoint oldcentre= CGPointFromString([vew titleForState:UIControlStateDisabled]);
CGRect rect=vew.frame;
CGPoint origin=rect.origin;
origin.x=(newCenter.x+oldcentre.x);
origin.y=(newCenter.y+oldcentre.y);
rect.origin=origin;
vew.frame=rect;
if (CGRectIntersectsRect(rect, [pageOriginalContainer frame])) {
[YourTable setBackgroundColor:[UIColor lightGrayColor]];//Notifying that the tableView will accept the icon
}
else
{
[YourTable setBackgroundColor:[UIColor clearColor]];
}
}
if (recognizer.state==UIGestureRecognizerStateEnded)
{
CGRect rect=[vew frame];
if (CGRectIntersectsRect(rect, [pageOriginalContainer frame])) {
//your method of adding the Image to table
}
else//else part is means for if user dropped dragging somewhere else other than Table
{
CGPoint point=CGPointFromString([vew titleForState:UIControlStateSelected]);
CGRect frame=vew.frame;
frame.origin=point;
vew.frame=frame;
[pageCopyContainer addSubview:vew];
// [NSFileManager defaultManager]
}
}
}
I have create a UIViewController with multiple subviews.. To switch to all subviews i have added a segmented control.. screen looks like this..
On the second view i have added a UIToolbar, using this line of code..
toolbar = [UIToolbar new];
toolbar.barStyle = UIBarStyleDefault;
[toolbar sizeToFit];
toolbar.frame = CGRectMake(0, 0, 800, 40);
UIBarButtonItem *filterByClass = [[UIBarButtonItem alloc] initWithTitle:#"A" style:UIBarButtonItemStyleBordered target:self action:#selector(goToFilteredByClass:)];
UIBarButtonItem *spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
NSArray *buttonItems = [NSArray arrayWithObjects:filterByClass, spacer, nil];
[toolbar setItems:buttonItems animated:NO];
So the screen will look like this..
when i go back to segment A, here's my screen..
then data is covered by the toolbar.. I want to remove it because segment A doesn't have a toolbar.. Is there a way to fix this issue..?
Thanks,
Link
Set up an action in your view controller, and have your segmented control call that action when its "value changed" event fires.
The segments of the control are numbered like an array, from 0 onwards. In your action method, you test for the segment you're interested in (in this case, segment 0) and show or hide the toolbar. You could animate it offscreen too, if you prefer a sliding animation.
If you aren't worried about leaving the toolbar for reuse later, you could use removeFromSuperview in your action method; but you won't get the animation if you use this method.
Quick example for hiding it using Core Animation:
-(IBAction)segmentedControlValueChanged:(UISegmentedControl *)sender
{
switch (sender.selectedSegmentIndex) {
case 0:
// A was pressed, so hide the toolbar
[UIView animateWithDuration:0.2
animations: ^(void) { toolbar.alpha = 0.0; }];
break;
case 1:
// B was pressed so show the toolbar
[UIView animateWithDuration 0.2
animations: ^(void) { toolbar.alpha = 1.0; }];
break;
}
}
I've got the View-Controller with a button. I want the new view to be loaded over my View-Controller when the button is pressed. It'd not replace the existing view, I want it to be smaller than the screen and hide when I tap out of the small view.
How should it be implemented in code?
- (IBAction)button:(id)sender
{
UIView *view2=[[UIView alloc]initWithFrame:CGRectMake(0,0,200,200)];
[self.view addSubview:view2];
UITapGestureRecognizer *Tap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(Tapview)] autorelease];
[view2 addGestureRecognizer:Tap];
}
-(void)Tapview
{
[view2 removeFromSuperview];
}
Add Tap Gesture to self.view like this:
UITapGestureRecognizer *oneFinger =
[[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerAction:)] autorelease];
// Set required taps and number of touches
[oneFinger setNumberOfTapsRequired:1];
[oneFinger setNumberOfTouchesRequired:1];
// Add the gesture to the view
[[self view] addGestureRecognizer:oneFinger];
Add one BOOL flag in .h file; in ViewDidLoad method add this:
flag = FALSE;
Now I assume u have UIView *smallView which be added on screen like this:
[self.view addSubView:smallView];
flag = TRUE;
smallView.center = self.view.centre;
Now when tapped on self.view tap gesture action called
- (void)oneFingerAction:(UITapGestureRecognizer*)sender
{
if(sender.view == self.view)
{
if(flag){
if(smallView)
{
[smallView removeFromSuperView];
}
}
}
}
First I would like to say that I'm really new to ipad/ipod/iphone development, and to objective-c too.
With that being said, I'm trying to develop a small application targeting the iPad, using Xcode and IB, basically, I have a table, for each UITableViewCell in the table, I added to the accessoryView a button that contains an image.
Here is the code:
UIImage *img = [UIImage imageNamed:#"myimage.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, img.size.width, img.size.height);
button.frame = frame; // match the button's size with the image size
[button setBackgroundImage:img forState:UIControlStateNormal];
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;
So far, so good, now the problem is that I want a PopOver control to appear when a user taps the button on the accessoryView of a cell.
I tried this on the "accessoryButtonTappedForRowWithIndexPath" of the tableView:
UITableViewCell *cell = [myTable cellForRowAtIndexPath:indexPath];
UIButton *button = (UIButton *)cell.accessoryView;
//customViewController is the controller of the view that I want to be displayed by the PopOver controller
customViewController = [[CustomViewController alloc]init];
popOverController = [[UIPopoverController alloc]
initWithContentViewController: customViewController];
popOverController.popoverContentSize = CGSizeMake(147, 122);
CGRect rect = button.frame;
[popOverController presentPopoverFromRect:rect inView:cell.accessoryView
permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
The problem with this code is that it shows the Popover at the top of the application View, while debugging I saw the values of "rect" and they are:
x = 267
y = 13
so I think it is pretty obvious why the PopOver is being displayed so up on the view, so my question is, how can I get the correct values for the PopOver to appear just below the button on the accessoryView of the cell?
Also, as you can see, I'm telling it to use the "cell.accessoryView" for the "inView:" attribute, is that okay?
Try using button.bounds instead of button.frame since the rect is relative to the inView.
Also note that if the cell is near the bottom of the screen, the popover may not appear at the right size because you are forcing the arrow in the Up direction. You should either handle this manually or just use Any.
[popOverController presentPopoverFromRect:rect inView:cell.accessoryView
permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
Changing inView:cell.accessoryView to inView:cell
here's for swift:
let cell = tableView.cellForRowAtIndexPath(indexPath)
let rect = tableView.convertRect(tableView.rectForRowAtIndexPath(indexPath), toView: tableView)
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("loadZone") as! UIViewController
var nav = UINavigationController(rootViewController: popoverContent)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = nav.popoverPresentationController
popoverContent.preferredContentSize = CGSizeMake(100,200)
popover!.sourceView = tableView
popover!.sourceRect = rect
self.presentViewController(nav, animated: true, completion: nil)