I'm trying to control a UIView that comes out from the side of the screen when a user swipes left or right. Since I want the view to follow the users finger I am using a pan gesture.
All of the is working okay however the code is growing as I am looking for speed of swipe and when touches end or start to get that information. The reason I am not using a slider-out navigation type of setup, is due to the fact that this view has to be part of UIWindow so it sits onto of my UITabBar, not under it.
The problem I have is stopping the view from moving past a certain point. This is the code I tried:
In the pan gesture method:
if (self.filterView.frame.origin.x <=36){
gesture.enabled = NO;
}
This works however as the code does; it disables the gesture and swiping back no longer works. I tried this as well:
if (self.filterView.frame.origin.x <=36){
return;
}
however, this means when I swipe back, nothing happens obviously as this code executes first to check the position.
if (self.filterView.frame.origin.x <=36){
self.filterView.frame = CGRectMake(37, 0, 300, 500);
return;
}
This was the closest match - however a bug is present where it resets the view - which is correct as that's what the code does. It's not very efficient and doesn't look good.
What is a better way to solve this problem? I just want self.filterView to not go bast a certain point.
You want to check if the view has passed the point, like you are doing. Then you want to manually keep it at that point.
// this checks if the view's origin along the x-axis has gone less than or equal to 36.0f
if (CGRectGetMinX(self.filterView.frame) <= 36.0f)
{
CGRect currentFrame = self.filterView.frame;
currentFrame.x = 37.0f;
self.filterView.frame = currentFrame;
return;
}
Related
I am trying to simulate a deck of cards, where a swipe over the screen makes the next card enter the display from the right, covering the card that is currently visible. I know how to program this using two view controllers and custom segues, but I want to keep it simple. I therefore want to stay within one and the same view controller, copy the current view into a temporary view, draw the new view, and then animate the new view to drift in from the right and cover the old one. Here's my attempt:
- (IBAction)SwipeLeft:(id)sender
{ UIView *sv = self.view;
[self Layout]; // this is where the next card is drawn
UIView *dv = self.view;
dv.center = CGPointMake(sv.center.x + sv.frame.size.width, sv.center.y);
sv.center = CGPointMake(sv.center.x, sv.center.y);
[UIView animateWithDuration:0.3
animations:^
{ dv.center = CGPointMake(sv.center.x, sv.center.y);
sv.center = CGPointMake(sv.center.x - sv.frame.size.width, sv.center.y);
}];
NSLog(#"Swipe left");
}
When I run this code, I do see the new card enter the screen appropriately, however, the 'old' view dissappears immediately and turns into a black screen before the animation starts.
How can I correct this?
Clearly this code does not work, as both pointers point at the same memory location. I decided to solve my problem in a different way. Rather than trying to copy a View object, I create a screen shot, copy the UIImage into a View object, cover the screen under the copy of the old screen when I draw the new screen and than animate that View object. Probably a smarter solution. It works great.
I am trying to implement a view with sliding side menus, such as with PKRevealController in iOS 6.1. A simple demo of this issue with source code on github is here, however you might not need to grab it if you already understand gestureRecognizer delegate implementation.
The problem I see is that two gestures that my users will want to use are going to be mutually confused for each other. The UITableView in the center (main screen) of the application should be able to use the swipe-right gesture to delete, but I still want a swipe that occurs across the top navigation area to result in exposing the side menus.I also intend to show other things than just the Table view, and at runtime I plan to swap out the main view with a different view, whenever a user selects a button on one of the side menus. This is kind of like a "hidden side tray UITabBarController" that I'm going for, but I want the side bars to be revealed only when the main "front view" controller is NOT a UITableView or its subviews.
Right now, using the demo sources that comes with PKRevealController, and adding deletion support to the main view's UITableView, no slide gesture to delete a row is possible. (You have to add one table view method to enable deletion support in the UITable view, which I did add.)
This was asked here, but the answer stated is incomplete, and as seen below, does not work for me and I have no idea why, because it appears that this delegate method is not invoked at any time where I return a YES, and yet it goes ahead anyways and begins a gesture.
Update The answer in the previous question is also wrong, as compared to the WIKI/FAQ answer I placed below.
I have only modified the class PKRevealController.m by adding this:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
UIView *view1 = otherGestureRecognizer.view;
UIView *view2;
if (view1) {
view2 = view1.superview;
};
if ([gestureRecognizer.view isKindOfClass:[UITableView class]])
{
return NO;
}
// Co-operate by not stealing gestures from UITableView.
if ([view1 isKindOfClass:[UITableView class]]) {
return NO;
}else if ([view1 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else if (view2 && [view2 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else
{
return YES; // NEVER GETS HIT. BREAKPOINT HERE!
}
}
What confuses me is that at no point does the return YES code above get hit (I have a breakpoint on it) and yet, the Gesture controller is still stealing the gesture.
Note: I have made an evil hack, but I thought that I could prevent this cleanly. Here is my evil hack:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.revealPanGestureRecognizer)
{
CGPoint translation = [self.revealPanGestureRecognizer translationInView:self.frontViewContainer];
BOOL begin = (fabs(translation.x) >= fabs(translation.y));
// BEGIN EVIL HACK
if (_topLimitY > 0) {
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
if (location.y>_topLimitY) // _topLimitY = 55 for instance.
begin = NO;
}
// END EVIL HACK.
return begin;
}
else if (gestureRecognizer == self.revealResetTapGestureRecognizer)
{
return ([self isLeftViewVisible] || [self isRightViewVisible]);
}
return YES;
}
Right now in my evil hacked demo, I have set the topLimitY property (that I added to PKRevealController's properties) to 55, which allows me to swipe on the nav bar area of the front view, but not on the table view which takes up the rest of the demo.
Note that I plan to have multiple main views, and only want to defeat the gesture recognition on the whole main area if the view is a UITableView or some sub-view thereof. That is why I call my hack above a hack. Because I thought you could tell the gesture recognizer to go away and not bother you, and yet it doesn't work, it doesn't even invoke the shouldRecognize method, it just goes ahead and does the next thing in its list of things to do.
I should really read the WIKI first shouldn't I?
This is a FAQ, it says so right here:
When instantiating the controller pass this option in your options dictionary:
NSDictionary *options = #{
PKRevealControllerRecognizesPanningOnFrontViewKey : #NO
};
This will disable pan-based reveal for the entire front view. Now, you can use the revealPanGestureRecognizer and add it to any view you desire to be panned on that doesn't interfere with your table view, to enable gesture based reveal.
I'd advise (if working with a table based environment with swipe'able cells) you, to add the revealPanGestureRecognizer to your front view controller's navigation bar (which it most likely has):
[self.navigationController.navigationBar addGestureRecognizer:self.revealController.revealPanGestureRecognizer];
And voilĂ - panning doesn't interfere with your table view anymore.
more info at:
https://github.com/pkluz/PKRevealController/issues/76
Thank you Wiki. If only I had read it all first.
The above completely answers my question and was already there on the wiki. I'm answering my own question because it seems Google always comes to Stackoverflow first, and that might help other confused developers in the future.
Update If the above thing blows up when you try it, it's probably being done too early. Here's a slightly more robust version of the above fix:
// Additional gesture recognition linkups. The underscore variables here
// are implementation-section ivars in my app-delegate, that I have already
// checked are valid and initialized, and this is the last thing in my app delegate
// didFinishLaunch... method, before the return YES:
UIGestureRecognizer *rec = _revealController.revealPanGestureRecognizer;
if (rec) {
[_frontViewNavController.navigationBar addGestureRecognizer:rec];
}
Use This :
self.revealController.frontViewController.revealController.recognizesPanningOnFrontView = YES;
I am attempting to create a menu system similar to the target app for iPhone. Here is what the menu looks like:
(source: iclarified.com)
I have been searching the internet and have not found a lot of information. To the best of my knowledge, I will have to create a UIView that is the menu, offset the view and detect when it is touched to slide up. These buttons in the menu view will have to load other views just like a tab bar controller would while keeping the menu tab to slide up at the bottom. Slide menu up, tap button for where you want to go, load that view while sliding the button back down.
The animations seem straight forward, its making it function like the tab bar controller and always stay in view and also how to detect the sliding of the menu (maybe something with UIScrollView)?
Any help would be great, no idea where to start.
Steps up to 5 contain the creation of the subview itself, but if you are only interested on the dragging events read from step five, which is about the panGestureRecognizer.
Create a custom UIView, I have also created a delegate protocol to inform the viewController when an action occurs on the subview. Depending on how much complicated your subview will be, you may want to create a xib file with the same name for that. Let's call it filterView from now on.
Create two methods like "hide" and "show" on the filterView. If you do not want to have a button (namely the small button with the arrow, I ll call it dropdownButton) to close and open the filterView, you may skip this step, but I strongly recommend to implement them. What you have to do is to animate filterView.frame.origin.y to
[[UIScreen mainScreen] bounds].size.height - dropDownButton.frame.height to hide the filterView
[[UIScreen mainScreen] bounds].size.height - filterView.frame.size.height to show it.
If requested I can also send code for that animations.
3.
Open the xib file for the UIViewController and add a UIView, so that only its dropdown button will be visible. Click the view and change its class to filterView using the rightmost pane in the interface builder. At this step you should be able to see your filtersView's tip on the bottom of the page if you did everything correctly. If not, the filterView.xib file is probably not correctly connected to filterView source code files.
4
.
Import the FilterView in the ViewController and connect as IBOutlet & synthetize it. Implement the delegate protocol if you have written one. If you have buttons on the filterView, you will need it later on. The delegate protocol should include optional messages like
-(void) featuresButtonTappedOnFilterView: (FilterView *) filterView;
and in the implementation you should do what you have to do in the viewController, like opening another viewController.
5
. On the viewControllers xib file create a panGestureRecognizer and add it to your filterView. PanGestureRecognizer is a gestureRecognizer which will handle the dragging events. At first the whole filterView will be able to dragged around by clicking any point on it, we will get to that later. Connect the gestureRecognizer as IBOutlet and create an IBAction (preferably with a better name) like:
-(IBAction)_panRecogPanned:(id)sender;
In the viewDidLoad method of the ViewController dont forget to set the delegate as:
[_panRecog setDelegate:self];
6
. Implement other states for more power. but stateChanged will be sufficient at first.
- (IBAction)_panRecogPanned:(id)sender {
switch ([(UIPanGestureRecognizer*)sender state]) {
case UIGestureRecognizerStateBegan: { } break;
case UIGestureRecognizerStateChanged: {
if ( [((UIPanGestureRecognizer *)sender) locationInView:_filterView].y > dropDownButton.frame.height )
return; // Only drag if the user's finger is on the button
CGPoint translation = [_panRecog translationInView:filterView];
//Note that we are omitting translation.x, otherwise the filterView will be able to move horizontally as well. Also note that that MIN and MAX were written for a subview which slides down from the top, they wont work on your subview.
CGRect newFrame = _panRecog.view.frame;
//newFrame.origin.y = MIN (_panRecog.view.frame.origin.y + translation.y, FILTER_OPEN_ORIGIN_Y);
//newFrame.origin.y = MAX (newFrame.origin.y, FILTER_INITIAL_ORIGIN_Y);
newFrame.origin.y = _panRecog.view.frame.origin.y + translation.y;
_panRecog.view.frame = newFrame;
[_panRecog setTranslation:CGPointMake(0, 0) inView:self.view];
} break;
//Remember the optional step number 2? We will use hide/ show methods now:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
//CGPoint velocity = [_panRecog velocityInView:_panRecog.view];
//Bonus points for using velocity when deciding what to do when if the user lifts his finger
BOOL open;
/*
if (velocity.y < -600.0) {
open = NO;
}
else if (velocity.y >= 600.0) {
open = YES;
} else
*/
if ( _panRecog.view.frame.origin.y > (FILTER_OPEN_ORIGIN_Y + FILTER_INITIAL_ORIGIN_Y) / 2 ) {
open = YES;
}
else {
open = NO;
}
if (open == YES) {
[_filterView show];
}
else {
[_filterView hide];
}
} break;
default:
break;
}
}
Note: My filter view was on the top of the page instead of the bottom, as it is the subview will move out of the page borders. Comment out the MAX and MIN statements to fix that, I am to lazy to write them by myself. The code is modified after copy&paste, it may (and probably will) contain typos.
I think you're trying to make it too complicated - just make a view and position in such a way that it is mostly off the screen, with just a tip showing up. When you touch the tip, move the view to be fully visible, and put that movement within an animation block.
The menu items could be just UIButtons with custom graphics on that view.
Don't think complex, logic is very simple. set its height and width in such a way that it is off the screen. try this approaches and then give your feedback. check http://blogsbits.com this method will help. hit and try method can be use to check these variables.
I have a UITextview, becomes first responder and keyboard is presented. Presently, I have buttons in inputAccessoryView toolbar that exchange the text forward and reverse through an array of strings.
I have the process working with no issues, so of course I am inclined to break it. My wish is to slide the textview left and right like a carousel to make it more clear to the user that next or previous string is coming and going. The current system simply replaces the text with no animation.
My first thought was to create a UINavigationController, give it an array of UIViewControllers that present the UITextviews. The navigation controllers view is only as big as the textview and I add it as a subview to my full view (which is itself in a navigation controller). I got this working fairly completely, the navigation bar is hidden and it looks no different than the original textview except that the textview now slides off to the right or left, depending if I am pushing or popping.
The problem with that is that the keyboard slides off along with the dismissed view controller, then the new textview in the new controller becomes first responder and the keyboard returns. Close, but no cigar.
I considered using page view controller but it seems it will have the same issue. I think I may have to go back to the single textview and animate the whole process directly with static screen grabs. That is completely beyond my experience level and I am think there must be a simpler way.
Can anyone suggest a simple way to keep that keyboard present while the views are swapped as described? Suggestions on other angles of attacking this?
Seems like an awful lot of overhead for a simple animation.
Try something like this (assuming ARC):
typedef enum _eDirection
{
rightToLeft = -1,
leftToRight = 1
} eDirection;
- (void) animateTextFields:(eDirection)direction
{
CGFloat distance = self.window.bounds.size.width;
UITextField *oldTextField = thisView.textField;
CGRect oldTFRect = oldTextField.frame;
CGRect newTFRect = CGRectOffset(oldTFRect, -direction * distance, 0);
UITextField *newTextField = [[UITextField alloc] initWithFrame:newTFRect];
[newTextField setText:#"whatever"];
[self addSubview:newTextField];
[UIView animateWithDuration:0.3f
animations:^{
[oldTextField setFrame:CGRectOffset(oldTextField.frame, direction * distance, 0)];
[newTextField setFrame:CGRectOffset(newTextField.frame, direction * distance, 0)];
} completion:^(BOOL finished) {
[self setTextField:newTextField];
[self removeSubview:oldTextField];
[newTextField becomeFirstResponder];
}];
}
(Disclaimer: as with all code typed off the top of one's head, etc., etc.)
This method would create a new textField, animate it horizontally onto the screen while moving the existing one off the screen in the direction you give, and then assigns the new textField as the current one and makes it the firstResponder.
I have a similar problem as this post
I have a scrollview where more data is visible to the user if the user scrolls to the left or scrolls to the bottom. But I am unable to find a way to tell the user that more content is available on scroll. It is not intuitive and only by experimenting, the user will find out about the scroll option.
Is there any way I can show/tell the user that scrolling is available.
Using [scrollView flashScrollIndicators] is not exactly useful as it just flashes the indicators and it is so quick that the user would surely fail to see it.
It would be great if someone can help me with this.
This is the way I did it...
I put an image on my scroll view ( for example an arrow pointing left). So using the function mentioned below, whenever the user loads the view, the image would show (telling that more content is there in the right). When the user comes to the right most of the scrollview, the image would disappear and when he scrolls again to the left, it would reappear...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView1 {
if (scrollView1.contentOffset.x == scrollView1.contentSize.width - scrollView1.frame.size.width)
{
// reached the bottom
NSLog(#"reached right");
self.imageView.hidden = YES;
}
else
{
self.imageView.hidden = NO;
}
}
i not sure but may be u can set a timer to flash indicators so that it stays there.......