Pull Menu in CocoaTouch - iphone

I am attempting to make a UIView/UIControl that people can drag up and reveal a text box, and then drag down to hide it. However, I have yet to find a method to make this "fluid" - it always seems to stop at random places and doesn't allow any more movement. At the moment I am using a UIView for the top part of the view and here is the current code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == topMenuView) {
CGPoint location = [touch locationInView:self.superview];
CGPoint locationInsideBox = [touch locationInView:self];
CGPoint newLocation = location;
newLocation.x = self.center.x;
newLocation.y = newLocation.y + (self.frame.size.height - locationInsideBox.y) / 2;
if ((self.superview.frame.size.height - newLocation.y) < (self.frame.size.height / 2) && (self.superview.frame.size.height - newLocation.y) > -32)
{
self.center = newLocation;
}
return;
}
}
Any help would be much appreciated!

I would use a pan gesture recognizer. The following code will simply move the view up and down along with the user's finger. If you want to limit how far up it moves, have it snap to place or have momentum you'll need to add to it.
UIView * view; // The view you're moving
CGRect originalFrame; // The frame of the view when the touch began
- (void) pan:(UIPanGestureRecognizer *)pan {
switch (pan.state) {
case UIGestureRecognizerStateBegan: {
originalFrame = view.frame;
} break;
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded: {
CGPoint translation = [pan translationInView:view];
CGRect frame = originalFrame;
frame.origin.y += translation.y;
view.frame = frame;
} break;
}
}

Removing this line may well help you:
newLocation.y = newLocation.y + (self.frame.size.height - locationInsideBox.y) / 2;

What you're trying to accomplish is really nothing more than a scrollable view, so I would recommend using a UIScrollView.
Put your UIView in a UIScrollView with transparent background, and place the UIScrollView on top of your textbox. Set the correct contentSize and you're good to go.

use a uiview animation block to update the frame of the sliding view corresponding with the touch point recieved. set the animation blocks duration to something really short like .01 or lower.

I'd recommend splitting the issue in two:
Implement the view which has the text box at the bottom - You will just have to implement your own custom view/view controller.
Add your view as a subview of a UIScrollView.
This is a good tutorial which demonstrates proper initialization of UIScrollView and embedding content in it.
Custom views/controllers are a bit of a broader subject :)

Related

Zooming a background image without zooming the overlay

I'm a beginner to the iOS development, and I'm trying to display an image (a map in fact) which need to be zoomable and pannable. My problem is that I don't know how to add an overlay which will follow the pan to always represent the same location, but won't be scaled by the zoom. There will be several overlays, and each one must be clickable.
To zoom and pan the image (map), I added my UIImageView in a UIScrollView and it works great, but I have no idea how to add this feature.
I found this thread but it is not really the same problem since his overlay is static :
UIScrollview with two images - Keeping 1 image zoomable and 1 image static (fixed size)
I've developped the same application in android, and to make this work I was converting a pixel of the map in screen coordinates thanks to the transformation matrix, and I overrode the onDraw method to display it, but I don't know how to do the same on iOS, and I don't know if this is the better solution.
Thanks for any help.
Ok so I found a way to do it, if it can helps anybody :
I added my overlay in the scrollView, with the background image (the map).
+CustomScrollView
----> UIImageView (map)
----> OverlayImageView (overlay)
In order to zoom, the custom scrollview need a delegate with the following methods :
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
//The background image with the map
return mapView;
}
//When a zoom occurs, move the overlay
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIImageView* overlayView = [scroll.subviews objectAtIndex:1];
float x;
float y;
float width;
float height;
//keep the width and height of the overlay
width = overlayView.frame.size.width;
height = overlayView.frame.size.height;
//Move it to stay over the same pixel of the map, and centers it
x = (self.overlay.point.x * scroll.zoomScale - width/2);
y = (self.overlay.point.y * scroll.zoomScale - height/2);
overlayView.frame = CGRectMake(x,y,width,height);
}
With this, we are saying that the zoom only occurs on the background image, but as the overlay is in the UIScrollView, it pans with it. So the only thing we need to care is to move the Overlay when the zoom change, and we know it with the scrollViewDidZoom method.
To handle the touch events, we override the touchEnded:withEvent: of CustomScrollView and we forward it to the overlay if there is only one tap. I don't show the OverlayImageView since it only override this same method (toucheEnded:withEvent:) to handle a touch on it.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
// Coordinates in map view
CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]];
//forward
if(touch.tapCount == 1){
OverlayImageView* overlayView = [self.subviews objectAtIndex:1];
CGPoint newPoint = [touch locationInView:overlayView];
BOOL isInside = [overlayView pointInside:newPoint withEvent:event];
if(isInside){
[overlayView touchesEnded:touches withEvent:event];
}
}
// zoom
else if(touch.tapCount == 2){
if(self.zoomScale == self.maximumZoomScale){
[self setZoomScale:[self minimumZoomScale] animated:YES];
} else {
CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];
[self zoomToRect:zoomRect animated:YES];
//[self setZoomScale:[self maximumZoomScale] animated:YES];
}
[self setNeedsDisplay];
}
}
Hope this will help.
You can put an imageView on top as an overLay and set its userInteractionEnabled property to NO. Then you have to pan it programmatically.

Creating a custom UIScrollView Scroller

Okay, I have been all over the internet trying to find the answer to this, I am trying to create a custom UIScrollView scroller.
Here is a link to a picture of the scroller
http://postimage.org/image/cg2jvde9z/
http://postimage.org/image/ko0mp3kdj/
(that white box is just for me to test the scrollview)
I have gotten the scroller to work, although I need help figuring out how many pixels the scrollview should move every time the scroller is pushed
here is some code
-(IBAction)scroller_bar:(id)sender withEvent:(UIEvent *)event{
UIButton *button_sent = (UIButton *)sender;
UITouch *touch = [[event touchesForView:button_sent] anyObject];
CGPoint previousLocation = [touch previousLocationInView:button_sent];
CGPoint location = [touch locationInView:button_sent];
CGFloat delta_x = location.x - previousLocation.x;
float left_side = button_sent.center.x + delta_x;
float right_side = button_sent.center.x + delta_x;
if (right_side > button_sent.center.x) {
scroll_right = YES;
}
else{
scroll_right = NO;
}
if (left_side <= 13 + 50.5 || right_side >= 307 - 50.5) {
}
else{
button_sent.center = CGPointMake(button_sent.center.x + delta_x,
button_sent.center.y);
if (scroll_right) {
[selection_scroll setContentOffset:CGPointMake(selection_scroll.contentOffset.x - (Help HERE!), selection_scroll.contentOffset.y) animated:YES];
}
else{
[selection_scroll setContentOffset:CGPointMake(selection_scroll.contentOffset.x + (Help HERE!), selection_scroll.contentOffset.y) animated:YES];
}
}
}
In the above code you should see a '(Help HERE!)',
that is where i am trying to figure out how many pixels i should move the scroll view. This is a bit of a confusing question, hope you understand.
Thanks :)
I don't know whether APPLE will approve this or not but the code written below can change the scollView 's scroller images
for (UIImageView *img in yourscrollView.subviews) {
[img setImage:[UIImage imageNamed:#"your custom scroller image"]];
}

Test for a pinch on UIImageView (not pinch and zoom - just test for pinch)

This isn't another one of "those" questions, don't worry.
Basically I want to test for a pinch and then run an animation. Here is the code I currently have:
// test for a pinch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if([allTouches count] == 2){
//there are two touches...
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex:0];
UITouch *secondTouch = [[allTouches allObjects] objectAtIndex:1];
CGPoint firstPoint = [firstTouch locationInView:self.superview];
CGPoint secondPoint = [secondTouch locationInView:self.superview];
int X1 = firstPoint.x;
int Y1 = firstPoint.y;
int X2 = secondPoint.x;
int Y2 = secondPoint.y;
// lets work out the distance:
// sqrt( (x2-x1)^2 + (y2-y1)^2 );
preDistance = sqrtf( (float)pow((X2-X1),2) + (float)pow((Y2-Y1),2) );
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if([allTouches count] == 2){
//there are two touches...
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex:0];
UITouch *secondTouch = [[allTouches allObjects] objectAtIndex:1];
CGPoint firstPoint = [firstTouch locationInView:self.superview];
CGPoint secondPoint = [secondTouch locationInView:self.superview];
int X1 = firstPoint.x;
int Y1 = firstPoint.y;
int X2 = secondPoint.x;
int Y2 = secondPoint.y;
// lets work out the distance:
// sqrt( (x2-x1)^2 + (y2-y1)^2 );
postDistance = sqrtf( (float)pow((X2-X1),2) + (float)pow((Y2-Y1),2) );
// lets test now
// if the post distance is LESS than the pre distance then
// there has been a pinch INWARDS
if(preDistance > postDistance){
NSLog(#"Pinch INWARDS");
// so we need to move it back to its small frame
if(CGRectEqualToRect(self.frame, largeFrame) && !CGRectIsEmpty(self.smallFrame)){
// we can go inwards
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.frame = smallFrame;
[UIView commitAnimations];
}
}
else {
NSLog(#"Pinch OUTWARDS");
// so we need to move it back to its small frame
if(!CGRectEqualToRect(self.frame, largeFrame)){
// we can go outwards
// set org frame
smallFrame = self.frame; // so we can go back later
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.frame = largeFrame;
[UIView commitAnimations];
}
}
}
}
All it does is measure the distance between the two touches if there is a multi touch, then it measures the distance again when the touches end. If the difference between the two is positive then it was an outwards pinch, and if it was negative it was an inwards pinch.
Depending on the direction of the pinch the image view will scale bigger or smaller to and from set CGRects (this is why I don't want the "normal" pinch and zoom functionality). I just want it to be a gesture to scale an image up and down again.
However, this isn't working very well... it doesn't always pick up pinches. I don't know if this is because the view its in is also detecting touches (only single pinches), but still...
Anyway, I was wondering if anyone has a better way of detecting pinches? I've currently subclassed the UIImageView to create my ImageController class which has these touch methods in them. All I need to know is a) they are touching this image and b) which direction they've pinched in.
Thanks
Check out UIPinchGestureRecognizer in the documentation. I haven't used it, but seems like it's just what you need.
Anyway, I was wondering if anyone has a better way of detecting pinches?
If you're targeting iOS 3.2 and upwards you can use gesture recognizers - there's no good reason not to. If you need to target pre 3.2, then your above approach (or varients therein, using touchesBegin, etc) is the only way, so it's just tweaking the algorithm.
If you can, I'd strongly recommend using UIPinchGestureRecognizer. It will make your life considerably easier, as it handles all the details. If you want to detect both the scale and direction of the pinch you can combine UIPinchGestureRecognizer with a UIRotationGestureRecognizer - the former will just give you the scale of a pinch, not the relative angle.
If you can live without 3.1.x devices I would recommend that you use the UIPinchGestureRecognizer. It's very simple to use, but the problem is that you're only going to be able to run it on iOS devices running 3.2 and upwards.
For some reason you are using locationInView to identify the position of the touches. Have you tried identifying the touches location in the current view (the actual image view)? Calling superview means that you will get the position of the touches in the view that contains your image view.

Making images appear... How?

Could someone please tell me how to make an image appear when the user taps the screen and make it appear at the position of the tap.
Thanks in advance,
Tate
UIView is a subclass of UIResponder, which has the following methods that might help: -touchesBegan:withEvent:, -touchesEnded:withEvent:, -touchesCancelled:withEvent: and -touchesMoved:withEvent:.
The first parameter of each of those is an NSSet of UITouch objects. UITouch has a -locationInView: instance method which should yield the position of the tap in your view.
You could create an initial star and just move it every time view is touched.
I'm not sure what you're end result will look like.
Note:
This code will give you 1 star that moves with a tap
Here is my code:-
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1:
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:myView];
myStar.center = point;
break;
}
default:
break;
}
}
It seems implied from the question that you want the user to be able to tap anywhere on the screen and have an image drawn where they tap? As opposed to tapping in a designated place and having the image appear there?
If so, you're probably going to have to go with a custom view. In that case, you'd do something like the following:
Create a subclass of UIView.
Override the touchesBegan method. Call [[touches anyObject] locationInView:self] (where touches is the first argument to the method, an NSSet of UITouch objects) to get the location of the touch, and record it.
Override the touchesEnded method. Determine the location touches ended at using the same method as in step 2.
If the second location is near the first, you'll want to place your image at that location. Record that location and call [self setNeedsDisplay] to cause the custom view to be redrawn.
Override the drawRect method. Here, if the location has been set in step 4, you can use the UIImage method drawAtPoint to draw your image at the selected location.
For further details, this link might be worth a look. Hope that helps!
EDIT: I've notice you've asked essentially the same question before. If you're not happy with the answers given there, it's generally considered better to "bump" the old one, perhaps by editing it to ask for further clarification, rather than create a new question.
EDIT: As requested, some very brief sample code follows. This is probably not the best code around, and I haven't tested it, so it may be a little iffy. Just for clarification, the THRESHOLD allows the user to move their finger a little while tapping (up to 3px), because it's very difficult to tap without moving your finger a little.
MyView.h
#define THRESHOLD 3*3
#interface MyView : UIView
{
CGPoint touchPoint;
CGPoint drawPoint;
UIImage theImage;
}
#end
MyView.m
#implementation MyView
- (id) initWithFrame:(CGRect) newFrame
{
if (self = [super initWithFrame:newFrame])
{
touchPoint = CGPointZero;
drawPoint = CGPointMake(-1, -1);
theImage = [[UIImage imageNamed:#"myImage.png"] retain];
}
return self;
}
- (void) dealloc
{
[theImage release];
[super dealloc];
}
- (void) drawRect:(CGRect) rect
{
if (drawPoint.x > -1 && drawPoint.y > -1)
[theImage drawAtPoint:drawPoint];
}
- (void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
touchPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
CGPoint point = [[touches anyObject] locationInView:self];
CGFloat dx = point.x - touchPoint.x, dy = point.y - touchPoint.y;
if (dx + dy < THRESHOLD)
{
drawPoint = point;
[self setNeedsDisplay];
}
}
#end

Making a UIImageView appear when screen is tapped [duplicate]

Could someone please tell me how to make an image appear when the user taps the screen and make it appear at the position of the tap.
Thanks in advance,
Tate
UIView is a subclass of UIResponder, which has the following methods that might help: -touchesBegan:withEvent:, -touchesEnded:withEvent:, -touchesCancelled:withEvent: and -touchesMoved:withEvent:.
The first parameter of each of those is an NSSet of UITouch objects. UITouch has a -locationInView: instance method which should yield the position of the tap in your view.
You could create an initial star and just move it every time view is touched.
I'm not sure what you're end result will look like.
Note:
This code will give you 1 star that moves with a tap
Here is my code:-
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1:
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:myView];
myStar.center = point;
break;
}
default:
break;
}
}
It seems implied from the question that you want the user to be able to tap anywhere on the screen and have an image drawn where they tap? As opposed to tapping in a designated place and having the image appear there?
If so, you're probably going to have to go with a custom view. In that case, you'd do something like the following:
Create a subclass of UIView.
Override the touchesBegan method. Call [[touches anyObject] locationInView:self] (where touches is the first argument to the method, an NSSet of UITouch objects) to get the location of the touch, and record it.
Override the touchesEnded method. Determine the location touches ended at using the same method as in step 2.
If the second location is near the first, you'll want to place your image at that location. Record that location and call [self setNeedsDisplay] to cause the custom view to be redrawn.
Override the drawRect method. Here, if the location has been set in step 4, you can use the UIImage method drawAtPoint to draw your image at the selected location.
For further details, this link might be worth a look. Hope that helps!
EDIT: I've notice you've asked essentially the same question before. If you're not happy with the answers given there, it's generally considered better to "bump" the old one, perhaps by editing it to ask for further clarification, rather than create a new question.
EDIT: As requested, some very brief sample code follows. This is probably not the best code around, and I haven't tested it, so it may be a little iffy. Just for clarification, the THRESHOLD allows the user to move their finger a little while tapping (up to 3px), because it's very difficult to tap without moving your finger a little.
MyView.h
#define THRESHOLD 3*3
#interface MyView : UIView
{
CGPoint touchPoint;
CGPoint drawPoint;
UIImage theImage;
}
#end
MyView.m
#implementation MyView
- (id) initWithFrame:(CGRect) newFrame
{
if (self = [super initWithFrame:newFrame])
{
touchPoint = CGPointZero;
drawPoint = CGPointMake(-1, -1);
theImage = [[UIImage imageNamed:#"myImage.png"] retain];
}
return self;
}
- (void) dealloc
{
[theImage release];
[super dealloc];
}
- (void) drawRect:(CGRect) rect
{
if (drawPoint.x > -1 && drawPoint.y > -1)
[theImage drawAtPoint:drawPoint];
}
- (void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
touchPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
CGPoint point = [[touches anyObject] locationInView:self];
CGFloat dx = point.x - touchPoint.x, dy = point.y - touchPoint.y;
if (dx + dy < THRESHOLD)
{
drawPoint = point;
[self setNeedsDisplay];
}
}
#end