I'm trying to add a gesture recognizer to specific tabBar item's subview. I can successfully add one to the tabBar itself, but not a specific index.
I was able to trick it into reacting based on a selectedIndex by implementing this in my AppDelegate:
[self.tabBar.view addGestureRecognizer:longPressNotificationsGR];
-(void)showFilterForNotifications:(UILongPressGestureRecognizer *)gesture {
if (self.tabBar.selectedIndex == 4) {
if (gesture.state == UIGestureRecognizerStateBegan) {
NSLog(#"Began");
} else if (gesture.state == UIGestureRecognizerStateEnded) {
NSLog(#"Ended");
}
}
}
Is there a better way? And yes I know this isn't what Apple had in mind but this is what I need for my particular project. I just feel like this isn't the best workaround, if there even is one, and i'm not sure how this will behave performance wide since its in the AppDelegate. But ultimately, I don't want to do it this way, I want to add it to the objectAtIndex:n so users can't just press and hold anywhere on the tabBar, even if 4 is the currently selected index. Right now users can tap & hold on index 1 icon, if 4 is selected, and the gesture methods get called. I want it to happen only if user is tapping & holding on objectAtIndex:n
You can get the UIView of a tab at a specific index with the following category method on UITabBar:
- (UIView*)tabAtIndex:(NSUInteger)index
{
BOOL validIndex = index < self.items.count;
NSAssert(validIndex, #"Tab index out of range");
if (!validIndex) {
return nil;
}
NSMutableArray* tabBarItems = [NSMutableArray arrayWithCapacity:[self.items count]];
for (UIView* view in self.subviews) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")] && [view respondsToSelector:#selector(frame)]) {
// check for the selector -frame to prevent crashes in the very unlikely case that in the future
// objects that don't implement -frame can be subViews of an UIView
[tabBarItems addObject:view];
}
}
if ([tabBarItems count] == 0) {
// no tabBarItems means either no UITabBarButtons were in the subView, or none responded to -frame
// return CGRectZero to indicate that we couldn't figure out the frame
return nil;
}
// sort by origin.x of the frame because the items are not necessarily in the correct order
[tabBarItems sortUsingComparator:^NSComparisonResult(UIView* view1, UIView* view2) {
if (view1.frame.origin.x < view2.frame.origin.x) {
return NSOrderedAscending;
}
if (view1.frame.origin.x > view2.frame.origin.x) {
return NSOrderedDescending;
}
NSLog(#"%# and %# share the same origin.x. This should never happen and indicates a substantial change in the framework that renders this method useless.", view1, view2);
return NSOrderedSame;
}];
if (index < [tabBarItems count]) {
// viewController is in a regular tab
return tabBarItems[index];
}
else {
// our target viewController is inside the "more" tab
return [tabBarItems lastObject];
}
return nil;
}
Then, you just need to add your gesture recognizer:
[[self.tabBarController.tabBar tabAtIndex:4] addGestureRecognizer:myGestureRecognizer];
Related
I created the side menu structure using the SWReveal view controller.What I want to do is to cancel the opening of the right-side view controller on some pages.I have researched and found something like this:
- (BOOL)revealControllerPanGestureShouldBegin:(SWRevealViewController *)revealController
if([revealController.frontViewController isKindOfClass:[UINavigationController class]]){
UINavigationController *navController = (UINavigationController *)revealController.frontViewController;
UIViewController *lastViewController = navController.viewControllers.lastObject;
if([lastViewController isKindOfClass:[DetailViewController class]] ||
[lastViewController isKindOfClass:[TableDateViewController class]] ||
[lastViewController isKindOfClass:[MapViewController class]])
{
return NO; // I do not want to open it for the view controllers I want
}
}
return YES;
}
This worked for me,but it also affected the opening of the left page.There is no problem with the touch action(tap gesture),but this applies to the pan gesture.I mean the pan gesture does not work for the view controller I want to run.I want to work correctly for some view controller,but I do not want to affect the left side.
I added the right toggle like this:
-(void)sideRightMenuLoad{
[((PersonelViewController *)[self.navigationController.viewControllers objectAtIndex:0]).view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
SWRevealViewController *revealViewController = self.revealViewController;
if(revealViewController){
[self.sideRightBarButton setTarget:self.revealViewController];
[self.sideRightBarButton setAction:#selector(rightRevealToggle:)];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
This code needs to work to open the right-side page:
[self performSegueWithIdentifier:SWSegueRightIdentifier sender:nil];
I tried to run it when I wanted it, but it did not work.
I am waiting for help in this regard.Thank you.
You have to check the pan gesture's direction:
- (BOOL)revealControllerPanGestureShouldBegin:(SWRevealViewController *)revealController {
if ([revealController.panGestureRecognizer velocityInView:revealController.view].x < 0) {
// pan direction left, should open right side
// ...
return NO;
}
return YES;
}
SWIFT 4
func revealControllerPanGestureShouldBegin(_ revealController: SWRevealViewController!) -> Bool {
let point = revealController.panGestureRecognizer().location(in: self.view)
if revealController.frontViewPosition == FrontViewPosition.left && point.x < 50.0 {
print("YES YES YES YES RRRRIIIIGGGGHHHHTTTT")
return false
}
else if revealController.frontViewPosition == FrontViewPosition.right {
print("YES YES YES YES LLLLEEEEFFFFTTTT")
return true
}
return false
}
I'm using UIKeyCommand to map certain shortcuts (for example "b", arrow keys, "t", "p", etc.) to a functionality inside my UIViewController subclass. The app is kind of a vector graphics software, which allows addition of text objects inside the canvas. The problem arises when a textView or textField inside the view controller is being edited. While it gets the first responder status, it doesn't receive the shortcut keys (for example writing "beaver" will result in "eaver").
Is there a correct way to handle shortcut keys AND use text objects inside a single view controller?
The solution I found to work best is to go through the responder chain to find the active responder and then check whether it is a UITextField/UITextView or something else. In case it is, return nil from the - (NSArray *)keyCommands method, otherwise return the shortcuts.
Here's the code itself:
#implementation UIResponder (CMAdditions)
- (instancetype)cm_activeResponder {
UIResponder *activeResponder = nil;
if (self.isFirstResponder) {
activeResponder = self;
} else if ([self isKindOfClass:[UIViewController class]]) {
if ([(UIViewController *)self parentViewController]) {
activeResponder = [[(UIViewController *)self parentViewController] cm_activeResponder];
}
if (!activeResponder) {
activeResponder = [[(UIViewController *)self view] cm_activeResponder];
}
} else if ([self isKindOfClass:[UIView class]]) {
for (UIView *subview in [(UIView *)self subviews]) {
activeResponder = [subview cm_activeResponder];
if (activeResponder) break;
}
}
return activeResponder;
}
#end
And this goes inside the keyCommands method:
- (NSArray *)keyCommands {
if ([self.cm_activeResponder isKindOfClass:[UITextView class]] || [self.cm_activeResponder isKindOfClass:[UITextField class]]) {
return nil;
}
UIKeyCommand *brushTool = [UIKeyCommand keyCommandWithInput:#"b"
modifierFlags:kNilOptions
action:#selector(brushToolEnabled)
discoverabilityTitle:NSLocalizedString(#"Brush tool", #"Brush tool")];
UIKeyCommand *groupKey = [UIKeyCommand keyCommandWithInput:#"g"
modifierFlags:UIKeyModifierCommand
action:#selector(groupKeyPressed)
discoverabilityTitle:NSLocalizedString(#"Group", #"Group")];
UIKeyCommand *ungroupKey = [UIKeyCommand keyCommandWithInput:#"g"
modifierFlags:UIKeyModifierCommand|UIKeyModifierShift
action:#selector(ungroupKeyPressed)
discoverabilityTitle:NSLocalizedString(#"Ungroup", #"Ungroup")];
return #[groupKey, ungroupKey, brushTool];
}
My solution was to override canPerformAction:withSender: and return false if the the view controller (that has the shortcut keyCommands) is not the first responder. This makes the walk down the responder chain unsuccessful in finding a target that accepts the key command and instead the key press is sent to the first responder as UIKeyInput as normal and the character appears in the text field. e.g.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
if(action == #selector(brushKeyCommand:)){
return self.isFirstResponder;
}
return [super canPerformAction:action withSender:sender];
}
I have eight UIImageViews that I want to fade if the UITapGestureRecognizer that is associated with it is activated. I have the all recognizers hooked up to this IBAction:
- (IBAction)disableDie:(id)sender {
NSLog(#"%#", sender);
NSLog(#"%ld",[(UIGestureRecognizer *)sender view].tag);
}
I thought I could do it with a loop like this:
- (IBAction)disableDie:(id)sender {
for (UIImageView *numberImage in self.diceOutletArray) {
if (numberImage == sender) {
numberImage.alpha = 0.65;
}
}
NSLog(#"%#", sender);
NSLog(#"%ld",[(UIGestureRecognizer *)sender view].tag);
}
But nothing happens to the UIImageView that was pressed, but the message's are printed. I have used the diceOutletArray in other loops and it works.
The sender is a UITapGestureRecognizer, not a UIImageView, and
therefore numberImage == sender will never be true.
Try this instead:
- (IBAction)disableDie:(UIGestureRecognizer *)sender {
for (UIImageView *numberImage in self.diceOutletArray) {
if (numberImage == sender.view) {
numberImage.alpha = 0.65;
break;
}
}
}
You don't actually need the loop at all though, this would work fine as well:
- (IBAction)disableDie:(UIGestureRecognizer *)sender {
sender.view.alpha = 0.65;
}
The gesture recognizer is the sender, not the view. You should see that in the printout of sender. You need to get the recognizer's view (assuming that it's attached directly to its image view).
Once you have that, you don't really need to go and find another pointer to the view: you already have it. It's just called sender.view instead of mumbleMumbleImageView.
Just send setAlpha: to that pointer.
I have multiple UIView, then after add and remove button on UIView in last want check my view is empty or not.
above image 3rd view is empty how to check programmatically
this view is empty or not.
You can check the amount of subviews in that specific view:
if([theView.subviews count] == 0) {
// View does not contain subviews
}
If you have multiple UIViews inside a parent view and you wish to figure out which views are empty, then loop over the parent subviews and check each if empty or not:
for(UIView * view in parentView.subviews) {
if([view isKindOfClass:[UIView class]] && [view.subviews count] == 0) {
// We found an empty UIView...
// Can you identify this view?
// If you need to do something with it, do it here.
}
}
Try This:
extension UIView {
var isViewEmpty : Bool {
return self.subviews.count == 0 ;
}
}
Paste extension code outside of the viewController class.
After removing button from view, every time check for isViewEmpty as below,
//if you don't have the object of view, you can get view as below,
let view = bottonToRemove.superview;//this will give you obejct for check
//your code to remove button from the view
if view.isViewEmpty {
//implement your logic for if view is empty
}else{
//view not empty
//do you stuff
}
UIView has a property subViews. This will return you an array of all subViews. If the array is null or has count zero then there is no subview in it.
i think as per your requirement you need check there is any button in view or not you can try
BOOL isEmpty = true;
for (UIButton *btn in viewTest.subviews) {
if ([btn isKindOfClass:[UIButton class]]) {
isEmpty = false;
break;
}
}
if (isEmpty == false) {
// there is button in view
}
else
{
// there no button in view
}
Seems like you are checking on the UINavigationBar,
for (UIView *view in self.navigationController.navigationBar.subviews) {
if(view)
//Do your thing
}
I have a custom subclass of MKPinAnnotationView that displays a custom call out. I want to handle touch events inside that annotation.
I have a working solution (below) but it just doesn't feel right. I have a rule of thumb that whenever I use performSelector: .. withDelay: I'm fighting the system rather than working with it.
Does anyone have a good, clean workaround for the aggressive event handling of MKMapView and annotation selection handling?
My current solution:
(All code from my annotation selection class)
I do my own hit testing (without this my gesture recognisers don't fire as the Map View consumes the events:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event; {
// To enable our gesture recogniser to fire. we have to hit test and return the correct view for events inside the callout.
UIView* hitView = nil;
if (self.selected) {
// check if we tpped inside the custom view
if (CGRectContainsPoint(self.customView.frame, point))
hitView = self.customView;
}
if(hitView) {
// If we are performing a gesture recogniser (and hence want to consume the action)
// we need to turn off selections for the annotation temporarily
// the are re-enabled in the gesture recogniser.
self.selectionEnabled = NO;
// *1* The re-enable selection a moment later
[self performSelector:#selector(enableAnnotationSelection) withObject:nil afterDelay:kAnnotationSelectionDelay];
} else {
// We didn't hit test so pass up the chain.
hitView = [super hitTest:point withEvent:event];
}
return hitView;
}
Note that I also turn off selections so that in my overridden setSelected I can ignore the deselection.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated; {
// If we have hit tested positive for one of our views with a gesture recogniser, temporarily
// disable selections through _selectionEnabled
if(!_selectionEnabled){
// Note that from here one, we are out of sync with the enclosing map view
// we're just displaying out callout even though it thinks we've been deselected
return;
}
if(selected) {
// deleted code to set up view here
[self addSubview:_customView];
_mainTapGestureRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(calloutTapped:)];
[_customView addGestureRecognizer: _mainTapGestureRecogniser];
} else {
self.selected = NO;
[_customView removeFromSuperview];
}
}
It's the line commented 1 that I don't like but it's also pretty hairy at the end of theta delayed fire. I have to walk back up the superview chain to get to the mapView so that I can convince it the selection is still in place.
// Locate the mapview so that we can ensure it has the correct annotation selection state after we have ignored selections.
- (void)enableAnnotationSelection {
// This reenables the seelction state and resets the parent map view's idea of the
// correct selection i.e. us.
MKMapView* map = [self findMapView];
[map selectAnnotation:self.annotation animated:NO];
_selectionEnabled = YES;
}
with
-(MKMapView*)findMapView; {
UIView* view = [self superview];
while(!_mapView) {
if([view isKindOfClass:[MKMapView class]]) {
_mapView = (MKMapView*)view;
} else if ([view isKindOfClass:[UIWindow class]]){
return nil;
} else{
view = [view superview];
if(!view)
return nil;
}
}
return _mapView;
}
This all seems to work without and downside (like flicker I've seen from other solutions. It's relatively straightforward but it doesn't feel right.
Anyone have a better solution?
I don't think you need to monkey with the map view's selection tracking. If I'm correctly interpreting what you want, you should be able to accomplish it with just the hitTest:withEvent: override and canShowCallout set to NO. In setSelected: perform your callout appearance/disappearance animation accordingly. You should also override setHighlighted: and adjust the display of your custom callout if visible.